From 41ba82eadaab335aa915f12fef9282ea885dc227 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 00:00:24 +0000 Subject: [PATCH 001/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-96489.yml | 5 ----- html/changelogs/AutoChangeLog-pr-96792.yml | 6 ------ html/changelogs/AutoChangeLog-pr-96895.yml | 4 ---- html/changelogs/AutoChangeLog-pr-96972.yml | 7 ------- html/changelogs/AutoChangeLog-pr-96990.yml | 4 ---- html/changelogs/AutoChangeLog-pr-96991.yml | 4 ---- html/changelogs/archive/2026-07.yml | 21 +++++++++++++++++++++ 7 files changed, 21 insertions(+), 30 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-96489.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96792.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96895.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96972.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96990.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96991.yml diff --git a/html/changelogs/AutoChangeLog-pr-96489.yml b/html/changelogs/AutoChangeLog-pr-96489.yml deleted file mode 100644 index ecda5a970c4a..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96489.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Lotte103" -delete-after: True -changes: - - map: "Added Thermomachines and Plasma Cannister to the HFR Room on Catwalk" - - map: "Replaced glass windows with reinforced plasmaglass windows in Atmos and Ordnance on Catwalk. Improving safety and allowing Atmos to operate the gas turbine without breaching the Atmos pump room." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96792.yml b/html/changelogs/AutoChangeLog-pr-96792.yml deleted file mode 100644 index 869038da9d06..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96792.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Fghj240" -delete-after: True -changes: - - qol: "Ghosts can see alien embryo stage and all virus symptoms when health scanning" - - code_imp: "Health scanners now support multiple scan levels" - - bugfix: "Fixes medical kiosks not being able to identify advanced disease cures." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96895.yml b/html/changelogs/AutoChangeLog-pr-96895.yml deleted file mode 100644 index 64262d8aee53..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96895.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "lelandkemble" -delete-after: True -changes: - - rscadd: "Added craftable anti-mech tripwires. A mech that foolishly steps on one will fall down and be immobilized temporarily. Don't let it fall on you!" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96972.yml b/html/changelogs/AutoChangeLog-pr-96972.yml deleted file mode 100644 index 7993d517696a..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96972.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "Fghj240" -delete-after: True -changes: - - rscdel: "Removed ImmunoSilence and its trait" - - balance: "Aggressive healing has much lower stats" - - map: "Added sepsisillin pills to virology" - - spellcheck: "Clarified the description of aggressive healing" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96990.yml b/html/changelogs/AutoChangeLog-pr-96990.yml deleted file mode 100644 index 2eb0bbe1e2fb..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96990.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "TealSeer" -delete-after: True -changes: - - bugfix: "fixed FaxBond not sending notifications when faxes are received" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96991.yml b/html/changelogs/AutoChangeLog-pr-96991.yml deleted file mode 100644 index 706af4414a08..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96991.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "LT3" -delete-after: True -changes: - - bugfix: "Tram signs only update on signals from their linked tram" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index ea8c07f1dd5c..f6e09ed6ec7e 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -521,3 +521,24 @@ vinylspiders: - bugfix: fixed an issue that would prevent an emped mech from being able to melee attack a target +2026-07-17: + Fghj240: + - qol: Ghosts can see alien embryo stage and all virus symptoms when health scanning + - code_imp: Health scanners now support multiple scan levels + - bugfix: Fixes medical kiosks not being able to identify advanced disease cures. + - rscdel: Removed ImmunoSilence and its trait + - balance: Aggressive healing has much lower stats + - map: Added sepsisillin pills to virology + - spellcheck: Clarified the description of aggressive healing + LT3: + - bugfix: Tram signs only update on signals from their linked tram + Lotte103: + - map: Added Thermomachines and Plasma Cannister to the HFR Room on Catwalk + - map: Replaced glass windows with reinforced plasmaglass windows in Atmos and Ordnance + on Catwalk. Improving safety and allowing Atmos to operate the gas turbine without + breaching the Atmos pump room. + TealSeer: + - bugfix: fixed FaxBond not sending notifications when faxes are received + lelandkemble: + - rscadd: Added craftable anti-mech tripwires. A mech that foolishly steps on one + will fall down and be immobilized temporarily. Don't let it fall on you! From ffc4b692f662b8f19f9f34968d4bf5ea585b06e8 Mon Sep 17 00:00:00 2001 From: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Date: Fri, 17 Jul 2026 03:47:05 -0700 Subject: [PATCH 002/445] Fixes weird banding on mineral turfs adjacent to space (on the bottom z level) (#96997) ## About The Pull Request You need to block emissives for starlight to color you, and this turf isn't well, doing that. So we should do that. This is hot code, which is why I have guard checks like I do. It's about 0.2-0.3 seconds of init on my machine, somewhere in there at least. ## Why It's Good For The Game This bugs me a lot ## Changelog :cl: fix: Starlight will light rock walls adjacent to space... better. /:cl: --- code/game/turfs/closed/minerals.dm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code/game/turfs/closed/minerals.dm b/code/game/turfs/closed/minerals.dm index b16e8b45bdf4..4421661118a1 100644 --- a/code/game/turfs/closed/minerals.dm +++ b/code/game/turfs/closed/minerals.dm @@ -204,6 +204,17 @@ scan_state_overlay.pixel_y = 224 scan_overlay.add_overlay(scan_state_overlay) +/turf/closed/mineral/set_smoothed_icon_state(new_junction) + var/old_junction = smoothing_junction + . = ..() + if(new_junction != ALL_SMOOTHING_JUNCTIONS || (!isnull(old_junction) && old_junction != new_junction)) + update_appearance(UPDATE_OVERLAYS) + +/turf/closed/mineral/update_overlays() + . = ..() + if(smoothing_junction != ALL_SMOOTHING_JUNCTIONS) + . += fast_emissive_blocker(src) + /turf/closed/mineral/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) if(turf_type) underlay_appearance.icon = initial(turf_type.icon) From c87370c57e427b8e5335c11b2cba35547727c166 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 10:47:25 +0000 Subject: [PATCH 003/445] Automatic changelog for PR #96997 [ci skip] --- html/changelogs/AutoChangeLog-pr-96997.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96997.yml diff --git a/html/changelogs/AutoChangeLog-pr-96997.yml b/html/changelogs/AutoChangeLog-pr-96997.yml new file mode 100644 index 000000000000..fbd243f35985 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96997.yml @@ -0,0 +1,4 @@ +author: "LemonInTheDark" +delete-after: True +changes: + - bugfix: "Starlight will light rock walls adjacent to space... better." \ No newline at end of file From ca0e695d571fae4a146adb3add295ea66caac8a2 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:00:25 +0000 Subject: [PATCH 004/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-96997.yml | 4 ---- html/changelogs/archive/2026-07.yml | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-96997.yml diff --git a/html/changelogs/AutoChangeLog-pr-96997.yml b/html/changelogs/AutoChangeLog-pr-96997.yml deleted file mode 100644 index fbd243f35985..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96997.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "LemonInTheDark" -delete-after: True -changes: - - bugfix: "Starlight will light rock walls adjacent to space... better." \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index f6e09ed6ec7e..dcfc09495936 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -532,6 +532,8 @@ - spellcheck: Clarified the description of aggressive healing LT3: - bugfix: Tram signs only update on signals from their linked tram + LemonInTheDark: + - bugfix: Starlight will light rock walls adjacent to space... better. Lotte103: - map: Added Thermomachines and Plasma Cannister to the HFR Room on Catwalk - map: Replaced glass windows with reinforced plasmaglass windows in Atmos and Ordnance From 98703ba2f5f96906c0a58b210bed0ddaf1aed3de Mon Sep 17 00:00:00 2001 From: Happyowl93 <231794227+Happyowl93@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:43:45 +0200 Subject: [PATCH 005/445] [FIX] arm delimbing no longer prevents you from using your good arm (#97001) ## About The Pull Request Change in https://github.com/tgstation/tgstation/pull/96637 made it so if you loose one arm and you have the wrong one selected, you can no longer switch to your good remaining arm. This change fixes that and tracks the actual number of serviceable hands in your mob currently. ## Why It's Good For The Game Bugfix ## Changelog :cl: fix: People with missing limb can again use the remaining good one. /:cl: Co-authored-by: pepe --- code/modules/mob/living/living.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 2d0a31982fc0..0e76510f14e8 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -2581,7 +2581,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/perform_hand_swap(held_index) //safeguard for one-handed mobs lol - if(num_hands == 1) + if(length(held_items) == 1) held_index = 1 return ..() From 4a49b8f4666d65d885d2ff6051fa7b027df9b83c Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:44:05 +0000 Subject: [PATCH 006/445] Automatic changelog for PR #97001 [ci skip] --- html/changelogs/AutoChangeLog-pr-97001.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97001.yml diff --git a/html/changelogs/AutoChangeLog-pr-97001.yml b/html/changelogs/AutoChangeLog-pr-97001.yml new file mode 100644 index 000000000000..49872deaba24 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97001.yml @@ -0,0 +1,4 @@ +author: "Happyowl93" +delete-after: True +changes: + - bugfix: "People with missing limb can again use the remaining good one." \ No newline at end of file From a814b1c8e4d6760c9c73744f764d5c7b45e3fb9e Mon Sep 17 00:00:00 2001 From: krookodilehunter <103150968+krookodilehunter@users.noreply.github.com> Date: Fri, 17 Jul 2026 08:45:48 -0500 Subject: [PATCH 007/445] You can pick up chicks now (and their moms) (#96873) ## About The Pull Request For some reason, you couldn't pick up chickens or chicks. Now you can! They can go in bags and even on your head! A side effect of this change is baby chicks won't grow up until you drop them on the ground, or else they would just disappear upon growing up. In-hand sprites by [mms142](https://github.com/mms142): chick black brown white ## Why It's Good For The Game Chicks and chickens are small and cute and they deserve to be held sometimes. ## Changelog :cl: add: Chickens can be picked up and held now! Sprites by mms142 /:cl: --- .../basic/farm_animals/chicken/chick.dm | 5 ++++- .../basic/farm_animals/chicken/chicken.dm | 4 ++++ icons/mob/inhands/pets_held_lh.dmi | Bin 11585 -> 13295 bytes icons/mob/inhands/pets_held_rh.dmi | Bin 11660 -> 13440 bytes 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chick.dm b/code/modules/mob/living/basic/farm_animals/chicken/chick.dm index 3c888145005a..44d2c4a00ee3 100644 --- a/code/modules/mob/living/basic/farm_animals/chicken/chick.dm +++ b/code/modules/mob/living/basic/farm_animals/chicken/chick.dm @@ -27,6 +27,8 @@ maxHealth = 3 pass_flags = PASSTABLE | PASSGRILLE | PASSMOB mob_size = MOB_SIZE_TINY + held_lh = 'icons/mob/inhands/pets_held_lh.dmi' + held_rh = 'icons/mob/inhands/pets_held_rh.dmi' gold_core_spawnable = FRIENDLY_SPAWN ai_controller = /datum/ai_controller/basic_controller/chick @@ -56,6 +58,7 @@ AddElement(/datum/element/pet_bonus, "chirp") AddElement(/datum/element/swabable, CELL_LINE_TABLE_CHICKEN, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) + AddElement(/datum/element/can_be_held) if(!isnull(grow_as)) // we don't have a set time to grow up beyond whatever RNG dictates, and if we somehow get a client, all growth halts. AddComponent(\ @@ -71,7 +74,7 @@ /// We don't grow into a chicken if we're not conscious. /mob/living/basic/chick/proc/ready_to_grow() - return (stat == CONSCIOUS) + return (stat == CONSCIOUS) && !istype(loc, /obj/item/mob_holder) /// Variant of chick that just spawns in the holodeck so you can pet it. Doesn't grow up. /mob/living/basic/chick/permanent diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm b/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm index 533f1c1a75b3..b8dbefd78ab2 100644 --- a/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm +++ b/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm @@ -31,6 +31,8 @@ GLOBAL_VAR_INIT(chicken_count, 0) maxHealth = 15 pass_flags = PASSTABLE | PASSMOB mob_size = MOB_SIZE_SMALL + held_lh = 'icons/mob/inhands/pets_held_lh.dmi' + held_rh = 'icons/mob/inhands/pets_held_rh.dmi' gold_core_spawnable = FRIENDLY_SPAWN ai_controller = /datum/ai_controller/basic_controller/chicken @@ -60,6 +62,8 @@ GLOBAL_VAR_INIT(chicken_count, 0) AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) AddElement(/datum/element/swabable, CELL_LINE_TABLE_CHICKEN, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) AddElement(/datum/element/animal_variety, "chicken", pick("brown", "black", "white"), modify_pixels = TRUE) + AddElement(/datum/element/can_be_held) + AddComponent(\ /datum/component/egg_layer,\ /obj/item/food/egg/organic,\ diff --git a/icons/mob/inhands/pets_held_lh.dmi b/icons/mob/inhands/pets_held_lh.dmi index 1a864db13410ab81807ccc1c538b6cb29593ca6e..ec564d01fc202722a7d6a7ee6cc3b246c57544ee 100644 GIT binary patch literal 13295 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 Bxtjn0 literal 11585 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�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 diff --git a/icons/mob/inhands/pets_held_rh.dmi b/icons/mob/inhands/pets_held_rh.dmi index 39332a7e18e41bc9f38b74cd7dfb096f8b859bf0..77b4b8a00e44243a70721125966b260ace84be76 100644 GIT binary patch literal 13440 zcmcJ0WmsHMlV;-s59UF0d(LIcMQ8vx=T8YdrcpHC2vspv>>tOnJ zqZb78=dp@>&idBgJlpZc*0+<230?Qg*MsSAYVAGUxJUcTUClS31|$kX_ZLFjeu3G6 z8FKH=$GDmtxV3L~dEpf!Er22BN9CR-2=p2x z^WnXUYx?o3hmMN#ljzyJj554R2xJ|BMdFJRtD8Q#aa+7r*k*pdiJtwuwq1jzgFzR-!Sg)?%g6~I5dtK?m7i^U3!a9ToRcj zyZMMXiKNeaitI&1WKWulAya+2`k)N<#THLRMR-m1p^6@aLKP+n^ATB*8C6U?#7Gf@ zD~=Zq8-x8z1J5@AyYCn1Z2&#Nzc1bR?OcFwCGVUN1sju7j?(iLN|Vqc!XY;wU^p^; zVxej2u_P?F#Ri8eCf`hpZp^w0X9A(YKYTU(HcBC}ziG z1|j=Xp17?os|q8&ZR(u*;#c%XHS_yYO2`^3Qf<1PAG#`T63ozxocHzyN=0sAEKlJ; zzUk>?6mkyA9E#x)Z$&C7v(MEJ?)G+fH-YCG@OHpU1Y=5idJ5UvI>ofMawV;-=zRTL zF8tI4xgi!pH?T)xrubMZvaWnwPt)woghUM2fp5FI2|-JVi}PPYobw?#i3Xd8FR}`O zKa%+DMEMvQ{jzPvLePc(!u=E)8me<}`R1lz_fGb2f!G?VG_oS&!)va8S=avsv-l5| zT7g{Th)mBs_*j}mI7WjoNWYR(HXXj2E}XU09FUHBSb?py>So`a68=&jg9{i)tzi$M z%SJcCqWGs$1?D)dGSib{`Rc%IjV#_1Sp$CYnC^=E{n`h(6RJjF<($Pel8`KJ%5NM$ z$3V*09H17!+MEMZU7y>%Am3nS-5(uRP@0hei(w`0>gx+!MQIM1+lrR;#ykaKVq%&; z%zvr@9;2pS7~`!aZfUKK0#!$*%1*=3*0XOq_>=qfg$P9mDl^!18}64tRll z52H^?gxGNtPEO3{%gMP&B4;kHFD@>`H8qJDu_Cyd5;^?*{TG?^Ge4Xyh^<~fK_P*( ze?Pw zH_ezcnzLZRjK=7-g4~j7kI`G!rCy(k7B1CEWTx#V{J_|P+YOZxwSlcEt?()W_;fQ9wn-@;qCv;X&>!(opWS?KJ>LVM! z-_sTIrpCs;X?lghkw3PQCFPig5LQD1wUTu{TYsOMpYK>^vA>X3QDIlN96W%y3G6C@ zh@*QOq}Sw_$8OY%tSeXKN)i68#v*#!OnxKp$>08AgrRY!Edta1D2e6(5T#ZbwiK&L{AJghL?Z*z(nx{VZ3#V!u#5wN zyuH0g$UCd|2Uhdg{TWGQ)-<{h%Vo(5*F2Sc zV{@;gESn;!Xh_9w1mEC(9oX(l|C*04`CQNOFs`H=&-i3Tyu!hl1H8B#vT_F_kj_7= zqRUZOr+O&OdvaFv*L1r5k60x1pb`X%lWObkUug;)B#C4v?%c^5cM?^57*v;j`-~~n z>MVQlIb&FJ9l$OCg3stps+ffzdsAX7D%gUYG`#0Zm($PCjR`@6J^}g{)eRfnIfdwe zLVwXzL;)3+7H+#dDxf#@uX95<^D<|=NzprLWD}7$BZ@_QMkbhdJ1hB&cQ_!g%=kB} zxMhW4epN7-%0g~W2~!;tX$!<6rLZL?cA}T%RttiTM9!KPA%u`wL&NGoyQBwXrSX{y zE%{UlrvoOc+`k_&#l(@_RwgJVUjJXN`9Hv*|ICa3&zAC~3LCz?WkX1+DW6gVIc_VF z4eEqZo7_t#)VN4kp19Uh&)MAs{9yL>%x13J8bF$=Pl7?nhF9`4+i2w&_d-QjXVnISk~GS9;gbMSPXk^Mtj@0{b40B{ka8&1bMx)sG9V{|>Kh zxtLQL@+TNe>k+}Y>?$$TvR}mQ-_1LH)<$UKilo*v@mwNHZvO-xv@7huW#HJjnz9LYRW5tmA^;+=dd1V_3L?k^r@N!pUn^_EKr!VNxx%)l$$U1 zw~u8#pXxDyvQ0=mRN|k4TzDFq>e(#KoJjHWI%M2S6TH;RhYiLj?7sWe7F(eurV(My z#-RL`i{NcaB=Y{vhw+X4%QGC>lqFP9P>^(Sagm;xnd`4yVP)kPY^zzz5-MFJ5OG0M z!1~`_*~U!eu&r~^Eq8eac{;s!dFj@O_k19byNgqa%QCi%-QHfuQV8+sV~Hh-X_u|3 zC(}J2F&1}E{Vy&!fNOes#$A^}gTov8pV@^(0HKh6bX~Dpk)oV0I*v}E+X;e^F64%b zrJl~~L7A&`k5%R}Dq*W;t9mwrFXFSpdjZ`Ndrwk5a6E;ORkT5eYh_|)M(we}YN5dR zvE+aksbp6{d?ExyCgeczo*K5&^64$B^Nu5faD`%Y(l_*p-9P+Cp3ia%D}rV!1tWcc zQ7npA3i0CDAE_U=!wxiw&!V6IEnbHhFE%*~uGKf#M&qL1CB`3xb|7W1Lij(*mMkqg z?KQ)4w%h*tlN?-A!>KH(MsDNbcE4x;_?HA^(9?MkElYCO&hF9uao2neb8*WhMQF?F zqp@Z5iWf#-9+rcMqoB&GhAl%E9?pC_e^0VXZ*AtKT5sId7J;o#4;XLt9x&oy1-a|v zkJ^P2kWT%w6g85gkF3>oWX2`3&V#b(ED+5BV_R808WCuz#zTm53JjZy5Qv5e%Vm`t z&HKgWzsw;9GGDxa85;JHmXt)Oh#eJoap5s)=VQf7A`yAwajDEAc7H49U_tys+fN*$ zJv+VUI#Ctr48j7V+%i&jcI-`)xzk7gkbq?0&3Q$TElFQEy``LNPcd8^@ac`if0!3K zoNsu2%ssnb9v!0+Kr17G`vdqZ`qV?2`J>7~e`4=Y_^Ox#eJ7|anf0uPFUvo6VvjS$iFQU|So`dg* z4e4)*4m!|W;@McGv?@_+K5W6ibxYhm9Mv#LvnYvgP7Dj`9-Zn|=n&0k9v;ASlu$nv zxx1^8&ax!puf@uAb~7--+M(vY`w$xiGlb9Fd-3=5^TxA?(fL+ZTCwC)L`7gSvD|9h zK(MQn>OsnE{)E{h;!m69`jJVi_!F(6OZZ4BYNvVRA6bpPahD$gaO-TA6Ia{4vePfrCc<$_g_`)6pxc8fmUz{y06%TEa&Ef$m&Qh9u6yN zf^6fYqdJ6f6ogI2h2ab2GlnwVhbRWtGJn^UzU2lBHJR~`E~eNVu5%bL-&PK-J&;@y zu@jipmu+bhh*86ybtjcrwiTl3mm6t!i!ao6v_^D|^Jg0mu>NLjh5Pfq4!}7R{6Fsp zqI6vo6Sg5cK3#_u9b>)2x5LjyHzFE-DV%0=B-6n4-TCJFy6+E4&!}w&!Y(IT`mx8@ z0o~r;SR#7>Fl17S5Z`O-8^3&jy;h&{FmrfNGIzBo&(`%Tudqr?C9*RSS_zrN`FqQr zI$^x$czj@Ki!QKsbd4{SQUdASIpQA{l|$vGg=aa}_ilyWDdV0^JXoa5w+0;Y=Oy!4 z2)*I0*sX>EuBOwpkptK1O2EChfBBsQ>#FozPdOJB7hyn0M@MJf9$Dv%qq8sDAYj!g zY&lCUD%WdE9Zlzt)6~|Ue@H1SD?52}05!3@yV{S^rD8ibr)Uw^^HYdE*$0S&<0<@3 zwAyW{#+=E>9ziy?mMDzjLL>8ZRbryl3*-!0gb*(SDw z(jDLx2q8wyu6kFW(N*q$`0%8pbL_3`icS>%TR*Jp*Q@id8w#W*A1w!OPrbo+WF9;8)vp^bb&Q@ULel@T2 zz%Kk?1x&gD+TeIqeZ3TL>ivxI-XZ<@k+o-^u-Z`2hv%u>rDoxra=S9kstwrI;>CI~ zZ{{iW?%adh9>egY+mK^A%VD&94&jEirq5?c6)ECkF7@SAlFmbn+k-56wah>*Wy{OG z!EtSh744}F)Ir&G*}rbrPt4a^n5^a%MzSM>jj`Tv8LP)te9WoCg!F8f;RFIL%P*K} z9eVF4Ei-63l1dHgoVikWupdMIEay|V1Wo)sS_hXT@UlOvxl)TW=ftF>oYqz{PScUj zQq7t#dU}|n(J1r`3|)WPJesU?+B|MiX=rFB9+Q26Q?-;Zcr=yUkAaHMc#h2Q3T-i2 zAP=3I7go6-{%5Ufy=1h{43==?%phPp%g*~>{VS9`Xfc;K9rlGx_7 zh6hbp)_s2Fu`@~zJ0^N(HOFkd+MJ*b$vMe{g%tpS6@J#@H&m}=s6o*>O#=c0)|2$* z9ZHrPR*AQ7h0%4OfY8dwno&V#gMK*HpL$VN6q+K!_UP=N$+3H84psdftw$4!f0%exY~yY)=fZmX=b+SuIm8%=AlFBx3U z6!w6pGcyDE#?^4%94#8pux%d9mZUr#DWKPNc8b}0aU)f&Z8A?Rq!<9Ao$&Y#A74Mz zAM-nF0(>BP?-KuNd@v0m>!}9@q)WC$ZJ?K{xOIAe#5r=gXqHSQkQv8|Mz~^Tv_;!9 zAfk{F$(t`Tm^;_8F7oQ-j+m-zSVd9!07*Jb*wB#OeDnBp4Jl|H(4W?u23>*{X7)aS zy@1-<5_C|x?l8N6#>jgQ#gdkr{Ubva42j`C&3AWq$tGz;%TXK^GI;E4O0Et%ueEu| ze#;7kz0+n3Cx33u&2hDz!X*n^jR3K2$&2UdLhB|g*Z@9bT&~sON46Rc3Md0vW5D99% z$QCa$;2%w*^m3RTx3sm`Ux_n^1IbMiPqF2Co5kbh29|g0BvU#NIb;q>f3xptRv}#T z-mk<*f}cpx|39UHXg@6^8eAn+#N|uE{QeOf99$~sw{jiofOt(7vEQd?{bgOcIx2eQ zIw1r$<`otemZ_B$llI5Z>(Qe26pT^VVnOG>(j&y2gvuUJh58s0ygM9^Jp?oUPrSdE zoYp5(32VUOQiyPsyO?q{F+qbkesVb8ojECyPhOMZISbjD{-b{gWqItM#HbF;-|j}F zga?2n?7u;A%C~%gYvP$L@_1~lh6kn)NtH#9`ElfCR-bHdK1L2@0XElNTW5#QC6;UJ z9dEu5sg(Djr_=7nM%Oz|&cM{$&}Ra;A%_;~us!_wdspM-y1KgAS#CN9(^qwTv6A{0 zf!L>zWKL67dEEybxL`~!EB(}^$!qC_WK}ja?o&A5l~|pZZGqUgM-T5{w(P$`e`-bZ z)~_ZZMNH3{+^x>UM!th82v;4}B-cGOP|^We7Z$xo($dn>Z%G7fPorc|OD;fMJRiJb z+&nxu?dpv}#7H(A0Qn*H0l5=WY`+Ak(8@mnZaYIE~*3_Hjo1f*&Rn;_Q8= zZnn~{O#7E#dY;noFT{K2;sFfZ!v$CZfz;I07Ot~og@zn&H|BgQgRv+NSSf9x!MXEx z>>$AXZjkJtg6tz50|7aO>Lw^CZnS_v1VdGuo~RDT%P+Y?>9r!s6Xrp^0}HB<*+y5(Vxc^j*ErSfyoq_suJK)qa;As%o> zy;Ve*QBidi%6fWHd+#bw^Hd`Ct;zwao=qlxOHXJZ>kuu?$H56I?& zOXoTv0vSv-8WRY@;d$Loqr_Q}t+@J<10VbyLhidp>H@5dxYOG=BKL1EhAu#$EYA)? zMQLiBY7@*P8@y_hC~RjgT!7dxInFY~Hq}O8ymYd8?12t)Pr5Uhx*X32hbq)TSY%A4 zfT(8+l2)?H)4R?gwlclR>N&C5*P$A?Cd}enF9lX%O-V`d3l2uAAt)A4QwPDQzE{so zLHTMrXQ}+y08TIB)foUX2!P<}#Z^On-pSQo5caZcnlSk?NT(834q&Zt2 ziC=5oirvN+7HFM9{edmY2$1|_TR3m?Mv7(hv}k`)`>A@OcI+_A-Bl>JRc1`Fg6D-y zh!>82Fvsy9IawmkN*~d=Uy^8FkrlF)3vTSe;DOcJ0FM5dxarX}UI(Z0pUc~3=%N$w{WqiWnihw2 z+HYJYUt2F;a2gR}bo;qg^(V$=-274lA5ShyOgX&-IQr2eAJaFIt83zRt>(jgsvrrs zCYpYXt4ArE8Y9wz2lg7uScQ8dXJA z4FvdJ`y$eF+lcad?Dg?HTDHMc#k(KF-(~hx5GNI;lSjaN9cQk9bb*UNA zH`2X7KY&zWh~j?R6)}#n!WeWl7TXvzESrRCjWYkF%)QSBl?FG)rylF$zZSdX|3jm` zcAb=y7cl-KKyRN2I?DRVmPY9@`6M2Zc-gsSMkPP}-v zGSd0z_L%gDmvQb7{gb9g&YhXE#LGR!szeQa+s!Dw4Haj|)|M(~2F*b+xn~!jPdNH} zXgiByNeZ7JT(KTn^ecW!N}sFz*S3_<<2Ma}AZi$kwH)WUA=J55jhXN%Je71ywN>4> zSyB}{nzs;zocVeS<3I~Ij|*mPwvu1PRvdi2oW+!sNN+GTcgN(mk_7I`_T&yKBw*gXALe$EWH2y16-4^ccm3YG&GY~=;NcRf-|Ny0I28< zb9e9V%$~|>cZ2I0Z3zLk*dOm)XKkDUFcC;MMh!q=RD>iXz^QP^QWOmX0|BJN+E~ys z&N7S7Dlfl(Jd-MmG=T7}hfOj;toUj##H*ZmK`9-0{hvSovdw=LXMW!`AA?#TIyUzJ zq3-XE?jE^8GL;@j#C_##mckJK(P?fS_iiYG2_2OGIYnu2JUlGS9w=sj#7uU83Ql<3 zD^!56?UW=;Uvv615{G~xO zV--GCFHGzw)vQP^XHB_}$|by@3Z16z*0#1lAl`umy?gi0QrHywaD8_^KAgw=|mN!ChXTRL^wH%>DG_j>L55COUnh z-G%D?XFRs!kD)__dX~!?s^$Y>gN#dF+?X{pxkH_$;~1*lhZ_6;H2rQEC-x}NOV#noO2U^Sg*o4jW-Eg1*lmInT`HAsx!9)JOr1OCVZ?4H_}-d2joxf^2!gK(|H=BEQC}A(oxK zF{T=rndeancPlw5nEdjh@`aE*lbsJVb8wk=ABIo%3=qE3(_fj9UtZ^%>!~5aP5e`Z4eacqvcwfeNjF-I%J2l zC4?f7BhG)+c!fRh?5S8;qh?E)YzdtrI~FpV_*k7j}HF4J(k_r5!JDGaUSS` z%j#L2CD(iEp@C+85^@H02}1DHh`TBKZny0Nj*tbY?5 ziyJ{9taX-tp{^;>g~j=txKEI60PsfHpGEHPqd;Q zSXo(T`$c(p2!LdVDe*I~KPn!c`mwym^xoI}2{B)QZVJYc@$X5`_CGlWU=jXt>O!XK z^_$c*_%9oO{@-ZpKii48v@_Qbldo9Zbl})&6#rBl#kF|r3Vhq|Bx#nT_~EfUQBU^( zKq|<5Fo#-9l@B2@E5sj3#LUqXT&N*Rb4hzTg@M~1*;Q5&?bonX(*skHG_n-Dn+^1o zFay=22F#8=P&SllW5pY(H`SFUU0p_V|1`#;XUxe1>Nsn2G8L3T$ZUy-40^vs=a^L# zx-pN(sv9X`vG=?o+)!T*Z}amw!O@WgE9v(P;n9}~D@7ZPXs|lybOomm zv>ut3w0>|9M#$==eC$nAWOcvzQv!)ruezoN29)Ci!BpU5-mTX;p7;sp(QU{f^?XAf z?N}W~d5KaUsrx;CwWn!LZc&(QzQ0`TFvG6PMOW+=U?R!p z(x?7c@-F|hWnrU^V`Lyx)1W{|V9+0+0K)&c7;zY+cQ$8@@&_8KM2zla!K zW;E$pmml8mvgsUf_eW?t!Gn@hpBR@4z`a14vt-pyW~tVqhmncNIf=(+$^ITe$Z7X; zo$qu~Mn+jn$kSp!njrWC)#bFDP^$E9L0YAgG#)^dy>NQbhOu;h<2l|UfXz9Y1vF?< zlys5-n=Q<1bzKH`Pu}2A!>O$n{@CTV7_ET9X>ijG&B2v!V~Yk1NW`^=G}Wr%N9sIm zksFJp@XdTlO7m0wvpa`X;VF`fLBO4{&RX27gy`>v&d30w-0gAIa{i1?`+IQCmxU_r)zXn#=NUm3az8$n(Wgbh(eO=?zJb+-t&MW5AO`s6-#eBqcb z+Yzh|KH6#iSq%C# z9WOXGx^ifOgam8g2;2a1I9Z{NR=Cr{jE)t|K>nO!Y|k6wSd{uYb9#lh z9D(=zI%c~02OC>xnUsC!!1-3KT!+EcUBs$t1AO{NxNC5c7_`296yb4uLOeUqX*R|V zUCbhK1hP=ObLvjm5SB66olF#30*=SqC5+Z*CFCcMV?^BWJ1#o))qZxoH@i1-fxcKB zgY0*F4A4X_IA*{b#Km!bI^f^ZQ8F}EBrRl&xE~WwgFBXF$*cwk-TWe;$PkVwHZ)(< zCIoW2H4zkLSAZdX5xlt6!Fxtlhu|*QoGjoUD6+^C;!>f z!t(v4CzbQRZb$u}dnLzj#w(&q))ETnZqWKM(fJ~!1AgjLG4^Fo6=Db}(;Yz8l{L*amMh04j zFYe+QG^TL9-X8h##f+>2D!0c|!K>}XM`Iv1#UvNN1KP+yzS-H??*7?DG<0;Fby^sp zFD0|<#C~>d*NNDal=^0AK!FPe^o^2(G|MJE-1|~(^UG-((Jz#(sV8hXBEcC5?Zc>c z=s3?*Y9=K#u@p(SEQ1LfZkMT$g ztzm#BpnoADnb(Bhd>%yRAbgJG&(|VR%@0bD;T)SEvylkaA*Zya&c@&S+V6>n4M6<^ z1I7}+KMH}bpACU=1|L3AtV~TJXL6s_h$uBjPVp88@UC0lRXtb-?5rIDqJ=#H4jl`s zC3f-+y9#kZTEP&2eJkfiJUVHrSLra6(7SRx6P_`b+jItZB-lmw5arWl z_e9`G0kZw3j(|o-peg0^c~@DdZDe=cz_R};NgyyASw)aR)L=34(*KKm{;t#VM=mxV zIznJ$Jf4{fz-gQN^*e|d(K|q=>kYbKSC2zNkdIcHlyAHb*xGiGJMsw{Y$#qOwUwV_ zd|m8vn+PaMNs8_`aS7wWIbe@Gg3jo>Yai5eNdB2@`Yd{ddDMZ`!106NmgGf7-sANX ztMbhcf{X%^L`P<_5?FvlI=lRlN>=|gITchv<44Cb1QDP$S-Z%h>yz&mbiP~JmVILF zy1sXqwbg#(oGdjV3bBYfgQH;aSFQ~uvd!liEt0J${^B+zdp&T>tiojyeBi)xf2-B7 z`j!lVgAWeA*r&8R7JNaK%6{W7@EN;e$5ugTxn$Oo#l0oDn8B})XqUprL1v|RJ66(; zQ28&Va@@NTOUC|)j$9zXiW$Ix>i>%;YU0O^&H$mYk#n=@key$||9WHoKajAoiQ|Ma zwmo7=>G0LgPf+~p7ew14ejOC2vmDm%`};fW7gzwe5SCytn7mz)08|^gXsdbww?3@X z--@a9L_ohD5Rr|XqmTkY5#u}fy^)k268B?(cC;BGUzuXgw9JDF#83b>(FnT$C$um! zx+zyYU&NOf3TQpv1Xk@YH&g()^!YQopvw_acxdRxk~AG1-M6EKs@?b$g#^?;a%lU8)MN?DLxqh+1P9G%Z4vBgJgxrSH5K1_0 zgAm#XS%?Ie;n6@R!N1|c#T_RD=-*VV^E>o*vUm+a&#PFu#GBCG;Mq%axlmS3jUcfN z+gHwOy6Pwp^beTGQ=;PKRR`nrOpw`xO)aap@uQ2}3wnPEc0l<_Ib+zp9@n8>qJYds zi(nvGfD8lbM{4MXW_dc?*b_8>y(eF=&Vvu_*5{({Ze2}CR#r(xCBUa(*Jy}}^ihzT zP*PIT^wb}(sj2DXr%!%B>%EeaQk}u(&vMP#{mC2zDE7$cXy0OuS<`su91y#v3wsdc zNJrTCTe?$9TBN(vLpPkm+q@s|&wcl< zd)HfUt#kIwd^^6FFJ~rPSy2iTjRXw<08AO_k17BFNA>(iMFs$XAh60GY_`9uX}Nqf zb2f3Z`r=~cU=IKu8PQW5_P^LMkR})Sk>dYJq?^vD@S+ORX|h{J#)l6jeE99?{D4Kw z|8iP-{oC#1Iz{J1@-Z@`;k{DAC{sZ2-@#%lZZ|V{^ua(YdSj8Kn>h4W@aSzE;d_}@ zWOB&p41brAEQXCXj+BtbOY#Wx44aNq=dVUunc0#g6Gw^En<@;rZZ7lF$W&6*xQt6V zLeVZKyI+sAsGugm`;51OmRru} zhaJBW41JW{3lkw)v4u+Gc!pZf8Q5t?eE zI-&}3Uzh_j7VjTnVfZbnbV~0KeAlWqqlgnuE==h!eqYGL)AsfEf5Q>mK22OErwS5m zVf06(uFVB#f`&ri!*FOw(Wu12Z2xN@ZIc~$1@%l4DH4tpWD7<2Qo{J)zE2SBzNZY` zV^LWe+_;l$fin5?T2YFmw78L())0nkWix(n_OWmC)GC14@nCZfK$PJ6QeTTnAD`le zK1I8dbgP;y!+y{)`S-nCBntIcsgI=`wf=g%c^<(QSF{zI@3H){xwX|vTeGUFssbEv zlKU-}z|JjoP3O*HiGEh&*B#wTWtoviG59>_Iz2hi6gu)~V(0X_>a2Z-KZ!{xpKAN) zN#Syk++CMlMUFyU8DgfMbb|h|rIK279$7>C%O3L(xuxx)I+mJA(VuKgKidz2=?$GcPSUg zZ9q}a)(0}xQZ+qz4HdxC!-MMmdwdECisq4gb#& z^_>hzp~ow!V>~d?t2rt_w0!2=G9tZpZbrJTd+!bbL^KV_PE8FrvupbGS&;%6i!5w@ zUTb+}8jW%#V<0~rpp)kcznB}krx2ZGIgItfQEM4Nd=0*7oz)6j1#+>OHgOv zaf}0gY#EZBl43c(iZY-Nm$L>74-eNnMDQ22=%)%A3hoS(Mvccr!lC}iO&}VCv!D#% zikXVTef<9eRLDo8$|}xJ$`|ICIGibGY4u^&f0CKDgqIc!Z7LKKJLl^SXV$1T7-KbP zx2%c7Il}KGQ2@f3am5IPF{RR9`t4DOm1|a;+i5&zTF9JB2cqU&p9N|wDd-#B4fht{)x_??~)}*N8#=4}Z@ZvWe@8e9tU(2?z-K-WZ5jJ>#%Od+FH1%TMfc zO3-+Hwnrjnd}|BYiv5G4B;$(_+bHoSDMG#7UY;(K)F47OKj>U=e~QgfD7xBa@Pl;u zXJTM|cgt+&0shnRLHH{c9sfaXLmwZXPg+`G=RV)e=iW&uE8~!Y-wqhSc&B%=P$$Jv z4EHLWda}kuijILHU$@C#tJQ_6$#<3$TH5+=GUY>eejKmkCSnK{`S9q$FHk%S;rI;f z7ZoJ$C(0ZxN2%l&>ISEs=s%UJRk)w{CRJ^1ZW1&>`(fdDc;lk7A}!JW!mWImj1%SF z=?EP2^#44H7AJNyECcx%7LEQ+>ZUm7DHLYEe*N0fYAum}Lj>806e;}V@MU$9^%D## zt*E%GQ`c$2w$kca?(zVV!^>%o1JYvc2C(Y4suA(Kl;jqv3C2pa@Ysacy|7@fa2Lr? z;6=DGXYovI2-D?pAZAtJ3S*iNBA@79S?O3jS15~|U%o&D?ey+q&tyED`~PmJMLI(> z11l@!8zSe#9ONYLQ9*WV8e+RP^tDO{qDd{{TU~?g`@bB{R;F+C+FUlc9H``44{0x* zu0q+p{}EwSv-c|Jg(4w^M5^f(896#S+G;OBfL4+pEH-WAhJBvwNwQ|M=+uvcqRnk> zTjQAmGrm98&q@JcrOn+U3!qoZV%BT^(vz`kUGVn+F;g-0_jd-l)~8GX_x0oBmA^4l?HaXZxtm10|JOnm<%O;l)ddA+Jtu@ z7C_#JT$Sic>-zO}BT7%dR8uv*DAs{M&I=h1ge#$}(fkr*XI41b0k~o$4F8P)_umhN|H+d!K&jdrR?ugZ3sd(A=HkOgFG{r_#mN>L zXHnQOzrr*2$7`j(OK3P!aSeUvhT@eFq=;%-vlT7i%6r3<@>(pcSIR)e z?a{ODSBm&U4`ZW`%oH@*u514KHE%s7^BqiZiBLSWx7ZEDu&^9{`*Kq z<(UEv)snE0e*a1H)d2d7SAyvcqNk^y^td&^>u8-ey5&2E59ywAQzjlL>+K7lL23fV z(#ebV33GU5*IN);cO}_FuLEtCOUoOJJDdrHLfFJ=Aad&sRl8$YCjE_Uxr3(?ldCOn z4>%gG=G1DoP*N|CSBaYKNC*1l^hE?77gbM**x^{rytUc6-ppEJ8I3=Bs>sgS97H_*Dw^Q#Zjz8&AGec<-Qd$n;}M90z-+N7;OC*K zXx}YDYcSJkU%Jn#axnV7eV|O67epzLV9!)-UVu*X1*1zx8ga)!%AupHv;Z*Kx64Pi zJW0VFgVpTz4*o(@Dt*cJwcL|0(VU{>dW*&cRpC$A!Nvv^r!I@6lt%mdSVTxj?A2!1XkWHD0@{rjA} zWQKn06ZsYW1ZMP3m3wLB*xB@rF5vwU3+>ucObu{PxV?cSRFU zci5ONRrP4zZl8IeN|NgCmYowcT^*lBk?YUF71DWi``b}88GdXY60#nTC+r`@lmgPm z6@m?X^z)Ji{AGSg)y4NCrH~Oa$RmG&>|HF!CV5{$KL2Lhe|DG3+qv2Ye6|nUESztJ z1}lr6?ZCDM=gqJ@V#Zf>og!y6!b{qMy{n!J%f-6S@7Ef6Y-0#qyyaxCr`Nvb^fTxb z6zsbAtK$H&*`Z~o6s zhBmA0kQ^?Y+62QOpVIj-b?Rgh#EYTij#3`OnkcqMz?ujOr^rNn7c6x!1-7DK9XK+0`e%&-kAAS9>A0I&$^CDl z(9;~K#hX2vSty?+8WjBc{5My!RHFE=aiEWj>jTjvVj^AKM6UmGm-Wj&^_PA&H?H^% zb#Zk343v@oDrE`k^IxpJX_i?s42z6>zvHMxFsBD)klk!phEmVsgA6S!2u5B#@iVu- zHzRn<)aGzLZZ5;h$~rnSfdaC*s>gFSCX~6U3M; zng78J0gclC`EF05Qcj{jj11-fXA&ljl{5L`+p-e3H0lNe9bI4EL&@tyIjo%dvp~im z)K4XGaX7PO>Te^1&|x^2!IJS8bX4QNnPUxST#U>T^Cv1t;@wDW8_UJX~&yY&4$yWUzmjNZ|w0}z(@ zNF*R20M=~MFLJ+PADUWNpodVAwCU?$yD$y4XEH%LuW;PUpbX=r4fsPl)`#Tfa|()c zn|b%hvvv!SN2N`Zvw!242yA;$K8finN$Dwd1iR{9pEEKoT&8}82Z9O|`(QtFQDri< ztXl9|+B_Xg>x}!+uIIX74dZ*-YHC4wc{K9b!sPEQ#y@`gguCe*w#N;H_Xl<5g{P-y z*aJ0%goK2o&~>#Rv|QN`RYv%I8Ha@d#&Uej@?$73WeqV!wZnjTbV#bx?tm|>@~5>b zr)|1$U<^e6i_q08ylPm4OyDAs?(im7Rttj2$^;nM0wlLWj!NApo!jqR9V`eD02LLL z+_Llc9EtG!BC&ntU7K}&k0Ue!MNF>!z87&GXJ`ZZ3cZ0S*v&KbK}@Ovj)xCVM#G6l zwyR`4mB*v0?`vF-X01p1O~kAl($w3>(uvHC1+G&0oawo3=c!5?C4^^($JqLQbPOW> znjXV|%gmOtxt+`;(z54TKHOObZOLF;`yKB_^u< zN>q}`_2#<5nuA>S3@3eYb?sklIBK5Rw%Z%e45*7l2f7#fkp$e2IhimtDztju#Q63p z6LVVPGg zE}g3{8e7mJkJWW|xBkKb&oV4(@6fOLi|71rxkXi4rlkj<5)vBQCSM|%)|?KpIcZ#_ zpDA%zRLA%8^DD2wS6wPS;?p)otOp~6c;F@noobPh$$g6^sRAO_zu!K4yWtjVKgP2M z4oMUigrv7-Upah3@G?Glw9>EwH$P5-crxzEhwzvQ0YXbG#(xUq1R`Dfc$bL6u z!(g!9{Xh9HAC}AC(U&mu!*5DFehi;ukoz&|{Aiu)d9geC+r4=sP1CLo#YzjR4WT6~ zM5+}gN}6GiTdXlb@wyn}A(aX)}6$R6x@LmU{SKzYvmR%V90jH46%!ZaKwfz}(yG2DZN>ELJ|w0vX< zGllZs?9EDcz+Fzo{eFJU%r2!Dq@}OZup>@)3-fm^j(P)OQnLYa0kbK0m77OZ>Z^{` zwh6AFzSD!NRUg4Q`H}pBPrf48*dAV9@6XO3YW%c1uw|81OVsq(j753v+&01T-mZ8| zQE-@eXLN=Vhx;AbCv+O^n{GC(=>X|h;LxH9)?FF>6=7d*)Sed!0|hA2gvDqkSMAL5 zDlxSY*48$YSA^>H{|GK2s*Yc*uL(>UYw6&cJwJYryy`PRWH0g^N~@(tL&LfQmR(pU z!X`M5OoYsDa3@Tk7!NqO_pY71U`k!r+_tv17|F$kUg=WW$L$>*3&4!;?%v+uZGuu?G+M-p#Ffa5|LG&L)Zdt81~l$^5GsCW!a#XZ%KYc{Po#DKAaz z$DEUl&gF3r6$`*_sf^OMqc!St-&>OTi5LBAYR4fvvVW^*u&S<}rTGfQ6$!{#B{G2Q z*##2j+npLes?C$yNsbOG1$S^L2-x^B+viE)83m z74ERfYxfpn7foB~?qYg*dEGX68O>hDt`SW1N`{zp>+(2q(rSA|cHeOdLGDuty876yf*sHGAF>k4g-zuC?6qpO>&R$}T+1@Qrg6!#*sn_XC%o zIoh`|bawaGxlauBX5*LI{a&y*yAT;T4eTo~`K~7#9jpB;C_r3ru*kpd<~Qxij7~_v za~{?awTE$z5ELGNsh=?yjP~x5p96Tw}?*kM_fINv;;sdJmRB38xat7R254hJBaZk+E zMRx*6XGs1Hw??zfdaW)Yx+}@LbW}$>;@#1PZ&>^q)xF?=&NP8UBnQE74>EJg*Hehc z8_Xf8pq5q08~d?cShuT1EMlZZ z7#b>>ExPps8PbxGA%5pF*5cq-L+&u=B|qGnOroaZV!wwi>L)1uD57*lf$%I;7jkyE zy9sM{s!geOvU8>5%=I3NbDf^98yrBiO@rl+iCd5i8)7!0p^-3KXLn!bDtLDzZx5s5 zt{!G$XyF^dRVeejV76OBZ29-^nOxR^`>#UDFVnddQ1TA|{RlRTn8z*2!!+oxllt}1ny>Yge1bB;R0Y?*7i+NMP zTl~d~Jca|JxT%#1ITHdwrK$*3`l+9j)y6ntrhggX!z4wNxJjU#deJoCYCk6a|J%x` zLjJ9nXb`6)x_az{L=!h_)d%Q6*L3G!)G(Z2Vxz8f7PZp(Y0>q^J7syyigxk0ne#W3 z0MWg0x37CNv>2fI`1$MQd_d{9?&Vl9>Pyt;X6HEJPoMD8_P%(DR4fbEYW1H-1xqoK z@4;_gqGJML;s_WcUq@xt9@T7fW&6jakc_szeFW}-#RRPf&WcZD2%t-!@W^70#(6!T zQ1WCE>1(LreGA+RUwd%sV_^E{o-|kysQ&cLe4w@dKzGEm?;JV)okc%8WqiE&B>rf@ zY!qy9&w9(NJX*5!&66MwL{LP)Z2aDa!<*HwV%g+_@!IS_|BIIo6eP+I92JrrNRc4M zd)MRGAzIvd*OnLK8EHMx%)GUni3LLg$DW}ZDeWu$k|Od_y4Su1Tn{-eZo)pXwpw-8 zL7AB>va+&g!+qLMC;-eZ<7--7)j#ihicf39P+~yQkGx@OeEd~Upkd?wH6ivod~7jW z-m|P~?hh1_`-e#|^2;(HK&Xwc5P3 zhwO9i;k4ygb+=}99zPQMo>`5~uA<28OnRvEShzhn#BE<)Ax$s!5%@Jbi(K3lqw~A& z_@FvpV{viO$a-yDaAbCvUdm5@w_+{4Un#Lu5K96q(_OU57{WGaeeaP3u_({@)Mt!h z;K;RIQ`E_Q!ajUYO%4bBJm3%Ku+rzp{wh?Hyu{5+hChX%6*Tex%+fqs_Fwg^8Niid5S`Q-iQ6$tqpYknoXJ5=_o z!bCvCCFL<-VZ!ghuex_&0NKpU>}O3)&-If*|FS{;D9gx-1u3j&ckIRc<<*}|co%ye zcGnk#;^uefJB(~>)t7ZUBgqIr)qyPL$ny7<^}~NrRpSa{b|e6MqZEqXFcV5bp@`%^ z=b7%rO>Z@z(N^YTs&lYXTog+l6{vPrwD9mh3qxWzUkV{Db(vGjDR#wt(?_29n(fse z93uB`N3~f~&oaIFK%$ zi+Xd_h86%08JXVJgtL>=ProN$K-BZj2k$NNP%NsbaLLn)Y*0i|kuJb@CQ zj4XTWq)z*DLPZZ_yOBE9VVWYjI>-}!VFw7_*;y)gLvcdE5cKymV*N-?o90pmx@|_q5KyY7x?ze&2WIUoLsqEwtBh7 zMkuAP=mEqg<@PHMe5-3p_5LBw%yBVPk&J_FeZ>jcztd2Z!mPpTO&mpszD*;#Z=Z zoXV!uHDo1H2zvQf8b3=cottIj;GlF!spojLBQl-mJpk}Hn6DqNw3*Kce7$4@^C;!R zMf4tB)jv-gRuy{Mck|~A+1|!l)goEKLup(#$Y7lS2gs^yj#(S3CE$rnP4puJIU41} z8IMPvh&b(BFSDvT-Uwlv*b2s>rc13o;rbk&35p>g{&2NAgnfAkg2}~C@g9ixg0d{* z+tI;7X?w-24gwZ5L(Qo@|EGAede9W|J)bEf4$!GIx3c1jS}UKgv%r3Y9$}-Z%2ak) zdB44VC`S~15-gy=Z+)zZ2&b-7E;P2;+C=2=-i^WR9zYY(d~>$tK~=Ljc&z_w@I`XD zAWEb++U!#%jJ}#qRSKP)zKaU9M=_{7XJDz%dkeMM%T)TXtTpVTS!mS$9hBYOt-iF7 zSzaxx=NJI@q>?AgHQBx|CR*sW++s%mTECrM zt(V!VGyXgGO!M_!9j|Q-&n5D;9fP!V`*E!0Z%zx&0RThu`7D4n=?YiG8WL>~r+fsA zhK9PpG5>$GH~$|BD2@X*8V$iQYjIvfF6xh|Q6zBB@fmnnkr&k6y1U42$hy#Gl|90y zE^w1n>fvTx3TUA`n9!xWEP28X)>Som6ju!5G?!H9p?Lac3-$EXntlbq?Vs(l-RkBA z4#eGu{(M0_Y&327v4Xeb1?FTn_p6^UMYb3h>wElI^p|)cL2>soSpngD4DiYa1m!Q4 z_|2o4CburIX8xTqM&snUNRlJmpEnD84`)zjo4e-9QVO~T!?S@lY5pDIG9_u=XdF)l zwKVgO>d(?|{tAja7lL4#P;Bxe>pqN@G|d=yikAo~M3tT~I0rh;Pq;>z$~o(x)HvSw ziy`<=mMBwk;F4nR$BL>l;)uzcO>^YDvw+_C!ENx@*za!^o>j)&W9ELAC&(Lvn0j*`MvE3~hcd>*W?Jt!=9-(-NkK5P$Zi@} zTG_{CSWHYz#2RcQnau+1@q(^n&aM_jcMZBjH1(t1LB02FEHZF(S#zFtdKpx{OInXl zcO`(<7Oh9=a|RB8$BZ7I8GOp8d9IW8Q*kTYW8Rrj~WhvlQTv3;xXX1k_1KsUp?~wJ>1GL+>geaPKJ-EW=+T3lsK0U3)A`QGLL^&fShnx|i@Uo! zJsZEfJ9DFhUCg;3C2MYvYeD31i%Dm#{x#VCG`K*_UKi1IS$aTx(l+YYeE~jZlYyqA zV_-nRPp~X$V35_2{^Q>-Y!QO<^K%UF1C)4pG!=$jt3P2E=jZIkeW-PfejRHFWhA1Q zGUCSq)JpLASYo=hw1Qj(op|STYaEVKt9AEpkw~yQa(I7{$NhI=xx^hXdxL+jJqljy94R!_u2VfF@#YxF|)a zJ$YPbf#5B-cLJVCN_7*$#g&^;rEP=_28Q!ACp<9^%=MQewuOIvL%W>SS__ zN<}7lDVp51WPRnHGqPr9)|!38-k>w)B69NC>9U8&x?zz98W~>N`Ju9dbzA2q-l5|g zO#CTmsA)AW-*aK+m96$&aC1?oJV_U3C0Jnv*?IN+958y+-^2*EWk zd3lVZsX}xZ^u&*rk1y2*LMJ>5Y-?>rJDRKJsdxeb+k1QH z!aaVCmW%uAP8ToYDY^NA0hg$Ofq{Jf zpPcnc0SrJV#$W;>s9>xRWiU}*AfdKQt%97rcm2qs-jpjx>}_DVPpx)>x_=l=wA}>7 zoIvEw^E7nV>%cXM_T`dB3SI5_yOKHCZb(|?nto#wSllL%3w~Db=zOu!o~L z%0_>1{hg(z57HX+lirbz*!aC;#qao}S1*F;r8H`>4Y5rLSFt}|ml1I9PJ~C@v^F;K zBUmoYN+Safs~u=OJUs1AS6YQTN(pI2BG06^TXK>B-C>4ZgQ}IUzNE8)>-#muIxNqS zy-(wL#!|k)mmG3&^(Lrutug{3R6e74eKC&_Hu`cu?G8fPTC|6>MPiPG6Y6})+S1d5 z@2VRHn@ah3ug~AvUTFu@${AL;t^HVIv1EU+$b{qcF;4*i>Qeka?;5zoR2VdLs^}UU z8!Ot|m%r3BH0;#k;^E=FL6Vmsi5~cZ3;hemghol4ts^B*&e*7Gk^aWY_}k*j>g_=f zkjkJS#PUv5=h;{jNPzdBhmJE#!tD_|lzKINCVn5vfxgU^zc-%~mO4^00*C5e>pkFk zHX>$KaRdHcT?e7fmtNAH7n}(vg8GoouH_;2%cdQ7^b`)CrpB+uMr=2v(|7ZecNEK_+9m#x zC|WWwCx=SwZIBa4UdI|~EbA5wJ$AKj(rnr9nN;)8U@aaWCA>6>2ezpjI>*wtNxPlo z##_#(SED%Gj&dOdgL?AgAEa*9{^S8=*a5ifbk!@{d(=X-t->QqV!wZj9b?w9CTT?^k3HIDzpREMO= zeI4z{qsfqC>F=v-YaYj=w_kY;jwCLdWEf$U7&`jI{Pix9{?1qCYLGO|Lx=voT+S< znzZ{*NjjC>Q9~7N2rqXk1`vKLP+1>Frv2`bLHv-Q{@_$m6~6ofsaJ=9+`el zXI_ZAuETfhX&y*fcvn}KI(L03B;`CK>Pk+Pa5TdQ3vL|Vd|WuxFs+rbwWfyzlFizSzB7E_pdZxS%-izN-Tz!;gc5rHX<8eJQ1TH=6g#dn=8V9d1mW}P z4xKVR;?fA>T~V^A@e28K8gf<@Njo8L@WZ2R|7H@xP From 6b52b564a50e4f3091470529c683587e5de15d49 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:46:08 +0000 Subject: [PATCH 008/445] Automatic changelog for PR #96873 [ci skip] --- html/changelogs/AutoChangeLog-pr-96873.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96873.yml diff --git a/html/changelogs/AutoChangeLog-pr-96873.yml b/html/changelogs/AutoChangeLog-pr-96873.yml new file mode 100644 index 000000000000..4c9cb21e5ff6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96873.yml @@ -0,0 +1,4 @@ +author: "krookodilehunter" +delete-after: True +changes: + - rscadd: "Chickens can be picked up and held now! Sprites by mms142" \ No newline at end of file From 926bf40c5a2a62fd21b1a55b19d17fcd7711d644 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:00:25 +0000 Subject: [PATCH 009/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-96873.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97001.yml | 4 ---- html/changelogs/archive/2026-07.yml | 4 ++++ 3 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-96873.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97001.yml diff --git a/html/changelogs/AutoChangeLog-pr-96873.yml b/html/changelogs/AutoChangeLog-pr-96873.yml deleted file mode 100644 index 4c9cb21e5ff6..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96873.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "krookodilehunter" -delete-after: True -changes: - - rscadd: "Chickens can be picked up and held now! Sprites by mms142" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97001.yml b/html/changelogs/AutoChangeLog-pr-97001.yml deleted file mode 100644 index 49872deaba24..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97001.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Happyowl93" -delete-after: True -changes: - - bugfix: "People with missing limb can again use the remaining good one." \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index dcfc09495936..2e6afab9ea52 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -530,6 +530,8 @@ - balance: Aggressive healing has much lower stats - map: Added sepsisillin pills to virology - spellcheck: Clarified the description of aggressive healing + Happyowl93: + - bugfix: People with missing limb can again use the remaining good one. LT3: - bugfix: Tram signs only update on signals from their linked tram LemonInTheDark: @@ -541,6 +543,8 @@ breaching the Atmos pump room. TealSeer: - bugfix: fixed FaxBond not sending notifications when faxes are received + krookodilehunter: + - rscadd: Chickens can be picked up and held now! Sprites by mms142 lelandkemble: - rscadd: Added craftable anti-mech tripwires. A mech that foolishly steps on one will fall down and be immobilized temporarily. Don't let it fall on you! From cf86765eb6933d0e68e2cc6143ef13e5039bd514 Mon Sep 17 00:00:00 2001 From: CabinetOnFire Date: Fri, 17 Jul 2026 20:49:48 +0200 Subject: [PATCH 010/445] Replaced our NPC AI with Behavior Trees. (#96628) ## About The Pull Request This PR replaces our current NPC AI with a [behavior tree system](https://en.wikipedia.org/wiki/Behavior_tree_(artificial_intelligence,_robotics_and_control)). Behavior trees are a common way of creating AI in which you place nodes in a tree structure to define what actions an AI should take. ### What our old system did: AI controllers defined a list of /datum/ai_planning_subtree types in behavior_nodes. Each subtree was a self-contained unit that could call queue_behavior() to fire off /datum/ai_behavior actions. The controller iterated subtrees in order, each one deciding independently whether to queue something and deciding whether the next subtree would run. This has a few issues: 1. There's no real structure; you are just defining a list of things to try in order. 2. There was a loooot of subtrees that were basically the same as another but with some slight modification 3. It was hard to understand. ### New system Controllers now define a single json file describing a tree of nodes. The tree is composed of structural composites: Sequence - do A, then B, then C (and so on) Selector - try A, if it fails try B, then C (and so on) Parallel - run A and B simultaneously, with configurable failure/success policies and or looping behavior Subplan - loop a child continiously Along that we also have "Decorators". These are nodes that basically check a condition (E.g.; do we have a combat target). These decorators can be used to gate behavior and are re-useable across behavior trees. They also have a concept known as "Observers". Which lets them cancel lower priority behavior in case their condition changes (Which we check whenever a signal fires that fits that specific decorator). This makes the AI much more responsive to change in environment. For behaviors, we still use the ai_behavior datums. These are the actual behaviors such as "Move to X", "Attack X". The only major change is that these can no longer sleep() since they now run in the ai_controller. Lastly, we now also have subtrees, except now they are essentially pieces of behavior tree that can be re-used, or even overriden at runtime or as a variable. Allowing for making modular AI made out of several smaller trees. ### Variables You can set variables on these nodes directly via the extension (see below), which should reduce the need to make subtypes of behaviors by a lot. All of these vars are saved on the JSON and will be applied at runtime. If you are using subtrees, you can also assign "bindings" to these variables, which will allow instances of the subtree to override those variables. ### Extension Since a tree structure with variables becomes hard to parse in a JSON, I've made a VSCode extension to edit these JSONs: https://marketplace.visualstudio.com/items?itemName=BehaviorTreeG.behaviortreeg https://github.com/CabinetOnFire/BehaviorTreeG image This extension allows you to edit the behavior tree JSONs, and browse through all the behaviors/decorators/subtrees we have If you'd like more info on how to build these AI check out the learn_ai.md. I will also make a tutorial to go over more depth on what the system offers because I kind of suck at doing technical write-ups. ### Targetting Targetting has been changed to. I've made a new acquire_targets behavior that takes a target_source (what am I targetting) and targetting_strategy (what does the candidate need to fulfill to be considered a target). This allows us to make composites targetting combinations to reduce the amount of specific find_and_set esque behaviors we had before. Not everything is ported to this system but that would be a longer term goal. ### Building I've added a new build_bt script that converts all the behavior tree JSONs into compiled versions. Why is this needed? Because I wanted to keep using defines in behavior trees, so we need a way to convert this into literal values before we send it to DM. This script runs on compile and should also run in CI (If I didn't fuck that up!). This saves to a new build/ folder. ### Content I've ported every single AI in the game to this system (except raptors, Kobsa is working on those so should be in soon!), so I do expect some bugs to come out of this. But I also fixed some issues that have probably been in the game for a long time such as: - Fixed penguins being unable to fish - Fixed bileworms not being able to devour people - Fixes goldgrubs not grubbing gold (they could not mine!) - Lizards actually eat food they find Either way, I'd reccomend a long TM on this. ## Why It's Good For The Game 1. (Hopefully) a better development experience for making AI 2. Less copy-paste for behaviors, we should be able to re-use more pieces to make behavior 3. Behavior trees is a more common pattern in making AI, so it should be easier to find resources to find out how to do things. ## Changelog :cl: CabinetOnFire, Iamgoofball, SmartKar, Ben10omintrix refactor: Replaces our AI system with behavior trees, porting all datum/ai to it /:cl: I will add this PR with more details down the line. I think I got the big picture but its a big PR, so sorry if I missed something important. --------- Co-authored-by: Iamgoofball Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> --- .github/workflows/check_bt_compiled.yml | 24 + .vscode/extensions.json | 3 +- .vscode/tasks.json | 9 +- .../ai/babies/make_babies.bt.compiled.json | 1 + .../datums/ai/bane/bane.bt.compiled.json | 1 + .../consider_venting.bt.compiled.json | 1 + .../escape_captivity.bt.compiled.json | 1 + ...escape_captivity_pacifist.bt.compiled.json | 1 + .../find_paper_and_write.bt.compiled.json | 1 + .../go_for_swim.bt.compiled.json | 1 + .../random_speech_loop.bt.compiled.json | 1 + .../run_away_from_target.bt.compiled.json | 1 + ...from_target_run_and_shoot.bt.compiled.json | 1 + .../skittish_brawler_combat.bt.compiled.json | 1 + .../tip_reaction.bt.compiled.json | 1 + .../pet_command_attack.bt.compiled.json | 1 + .../pet_command_attack_dog.bt.compiled.json | 1 + ...et_command_attack_minebot.bt.compiled.json | 1 + ..._attack_ranged_glockroach.bt.compiled.json | 1 + .../pet_command_beehive.bt.compiled.json | 1 + .../pet_command_breed.bt.compiled.json | 1 + .../pet_command_fetch.bt.compiled.json | 1 + .../pet_command_fish.bt.compiled.json | 1 + .../pet_command_follow.bt.compiled.json | 1 + .../pet_command_mine_walls.bt.compiled.json | 1 + .../pet_command_move_to.bt.compiled.json | 1 + .../pet_command_play_dead.bt.compiled.json | 1 + ...pet_command_protect_owner.bt.compiled.json | 1 + ...t_owner_ranged_glockroach.bt.compiled.json | 1 + .../pet_command_scatter.bt.compiled.json | 1 + .../pet_command_stay.bt.compiled.json | 1 + .../pet_command_swirl.bt.compiled.json | 1 + ..._command_targeted_ability.bt.compiled.json | 1 + ...ommand_untargeted_ability.bt.compiled.json | 1 + .../simple_ability.bt.compiled.json | 1 + .../simple_ability_combat.bt.compiled.json | 1 + .../simple_ability_melee.bt.compiled.json | 1 + ...mple_ability_melee_combat.bt.compiled.json | 1 + .../simple_ability_ranged.bt.compiled.json | 1 + ...ple_ability_ranged_combat.bt.compiled.json | 1 + .../simple_ability_retaliate.bt.compiled.json | 1 + ..._ability_retaliate_combat.bt.compiled.json | 1 + .../simple_capricious.bt.compiled.json | 1 + .../simple_capricious_combat.bt.compiled.json | 1 + .../simple_fearful.bt.compiled.json | 1 + .../simple_fearful_combat.bt.compiled.json | 1 + .../basic_mobs/simple_goon.bt.compiled.json | 1 + .../simple_hostile.bt.compiled.json | 1 + .../simple_hostile_combat.bt.compiled.json | 1 + ...ile_combat_with_retaliate.bt.compiled.json | 1 + .../simple_hostile_obstacles.bt.compiled.json | 1 + ..._hostile_obstacles_combat.bt.compiled.json | 1 + .../basic_mobs/simple_ranged.bt.compiled.json | 1 + .../simple_ranged_combat.bt.compiled.json | 1 + .../simple_ranged_retaliate.bt.compiled.json | 1 + ...e_ranged_retaliate_combat.bt.compiled.json | 1 + .../simple_retaliate.bt.compiled.json | 1 + .../simple_retaliate_combat.bt.compiled.json | 1 + .../simple_skirmisher.bt.compiled.json | 1 + .../simple_skirmisher_combat.bt.compiled.json | 1 + .../simple_skittish.bt.compiled.json | 1 + .../simple_skittish_combat.bt.compiled.json | 1 + .../ai/basic_mobs/talk.bt.compiled.json | 1 + .../ai/bots/bot_patrol.bt.compiled.json | 1 + .../bot_respond_to_summon.bt.compiled.json | 1 + .../bot_salute_authority.bt.compiled.json | 1 + .../datums/ai/cursed/cursed.bt.compiled.json | 1 + .../datums/ai/dog/dog.bt.compiled.json | 1 + .../datums/ai/dog/dog_corgi.bt.compiled.json | 1 + .../ai/dog/dog_harassment.bt.compiled.json | 1 + .../datums/ai/generic_hunger.bt.compiled.json | 1 + .../generic_play_instrument.bt.compiled.json | 1 + .../basic_find_target.bt.compiled.json | 1 + .../capricious_pick_target.bt.compiled.json | 1 + .../climb_tree.bt.compiled.json | 1 + .../find_food.bt.compiled.json | 1 + .../find_partner.bt.compiled.json | 1 + .../find_stealable_object.bt.compiled.json | 1 + .../forage_and_retaliate.bt.compiled.json | 1 + .../move_to_and_eat.bt.compiled.json | 1 + .../move_to_and_hunt.bt.compiled.json | 1 + .../move_to_reinforce.bt.compiled.json | 1 + .../pick_retaliate_target.bt.compiled.json | 1 + .../random_walk.bt.compiled.json | 1 + .../skittish_and_speak.bt.compiled.json | 1 + .../steal_and_flee.bt.compiled.json | 1 + .../ai/hauntium/haunted.bt.compiled.json | 1 + .../datums/ai/monkey/monkey.bt.compiled.json | 1 + .../ai/monkey/monkey_combat.bt.compiled.json | 1 + .../monkey_find_weapon.bt.compiled.json | 1 + .../monkey/monkey_serve_food.bt.compiled.json | 1 + .../monkey_shenanigans.bt.compiled.json | 1 + .../vending_machine.bt.compiled.json | 1 + .../robot_customer.bt.compiled.json | 1 + .../antagonists/netguardian.bt.compiled.json | 1 + .../domains/crewman.bt.compiled.json | 1 + .../domains/crewman_hostile.bt.compiled.json | 1 + .../crewman_hostile_ranged.bt.compiled.json | 1 + .../domains/crewman_ranged.bt.compiled.json | 1 + .../living/basic/alien/alien.bt.compiled.json | 1 + .../basic/alien/basic_alien.bt.compiled.json | 1 + .../living/basic/alien/drone.bt.compiled.json | 1 + .../alien/lay_alien_egg.bt.compiled.json | 1 + .../alien/melee_alien_combat.bt.compiled.json | 1 + .../alien/plant_alien_weeds.bt.compiled.json | 1 + .../living/basic/alien/queen.bt.compiled.json | 1 + .../ranged_alien_combat.bt.compiled.json | 1 + .../basic/alien/sentinel.bt.compiled.json | 1 + .../blob_minions/blob_spore.bt.compiled.json | 1 + .../blob_minions/blob_zombie.bt.compiled.json | 1 + .../blob_minions/blobbernaut.bt.compiled.json | 1 + .../blood_drunk_miner.bt.compiled.json | 1 + .../blood_drunk_miner_combat.bt.compiled.json | 1 + .../boss/thing/thing_aoe.bt.compiled.json | 1 + .../boss/thing/thing_boss.bt.compiled.json | 1 + .../boss/thing/thing_melee.bt.compiled.json | 1 + .../living/basic/bots/bot.bt.compiled.json | 1 + .../clean_pet_target.bt.compiled.json | 1 + .../bots/cleanbot/cleanbot.bt.compiled.json | 1 + .../living/basic/bots/dedbot.bt.compiled.json | 1 + .../basic/bots/ed209/ed209.bt.compiled.json | 1 + .../ed209/ed209_syndicate.bt.compiled.json | 1 + .../bots/firebot/firebot.bt.compiled.json | 1 + .../bots/honkbots/honkbot.bt.compiled.json | 1 + .../honkbots/honkbot_slip.bt.compiled.json | 1 + .../hygienebot/hygienebot.bt.compiled.json | 1 + .../basic/bots/medbot/medbot.bt.compiled.json | 1 + ...ot_find_and_announce_crit.bt.compiled.json | 1 + .../medbot_treat_patient.bt.compiled.json | 1 + .../bots/mulebot/mulebot.bt.compiled.json | 1 + .../bots/repairbot/repairbot.bt.compiled.json | 1 + .../repairbot_emagged.bt.compiled.json | 1 + .../repairbot_find_target.bt.compiled.json | 1 + .../repairbot_repair_target.bt.compiled.json | 1 + .../basic/bots/secbot/secbot.bt.compiled.json | 1 + .../bots/vibebot/vibebot.bt.compiled.json | 1 + .../living/basic/clown/clown.bt.compiled.json | 1 + .../constructs/artificer.bt.compiled.json | 1 + .../constructs/juggernaut.bt.compiled.json | 1 + .../cult/constructs/proteon.bt.compiled.json | 1 + .../cult/constructs/wraith.bt.compiled.json | 1 + .../basic/cytology/vatbeast.bt.compiled.json | 1 + .../farm_animals/bee/bee.bt.compiled.json | 1 + .../bee/find_hive.bt.compiled.json | 1 + .../bee/pollinate_target.bt.compiled.json | 1 + .../bee/queen_bee.bt.compiled.json | 1 + .../transition_hive_status.bt.compiled.json | 1 + .../chicken/chick.bt.compiled.json | 1 + .../chicken/chicken.bt.compiled.json | 1 + .../farm_animals/cow/cow.bt.compiled.json | 1 + .../cow/cow_moonicorn.bt.compiled.json | 1 + .../cow/cow_wisdom.bt.compiled.json | 1 + .../farm_animals/deer/deer.bt.compiled.json | 1 + .../farm_animals/goat/goat.bt.compiled.json | 1 + .../forage_for_goose_food.bt.compiled.json | 1 + .../farm_animals/goose/goose.bt.compiled.json | 1 + .../goose/goose_calm.bt.compiled.json | 1 + .../gorilla/gorilla.bt.compiled.json | 1 + .../basic/farm_animals/pig.bt.compiled.json | 1 + .../basic/farm_animals/pony.bt.compiled.json | 1 + .../farm_animals/rabbit.bt.compiled.json | 1 + .../basic/farm_animals/sheep.bt.compiled.json | 1 + .../basic/festivus_pole.bt.compiled.json | 1 + .../heretic/raw_prophet.bt.compiled.json | 1 + .../heretic/rust_walker.bt.compiled.json | 1 + .../basic/heretic/stalker.bt.compiled.json | 1 + .../basic/heretic/star_gazer.bt.compiled.json | 1 + .../ice_demon/ice_demon.bt.compiled.json | 1 + .../ice_demon_afterimage.bt.compiled.json | 1 + .../ice_demon_combat.bt.compiled.json | 1 + .../ice_demon_flee_from_fire.bt.compiled.json | 1 + .../ice_whelp/ice_whelp.bt.compiled.json | 1 + .../icemoon/polar_bear/polar.bt.compiled.json | 1 + .../basic/icemoon/wolf/wolf.bt.compiled.json | 1 + .../basic/illusion/escape.bt.compiled.json | 1 + .../basic/illusion/retaliate.bt.compiled.json | 1 + .../basic/jungle/human_trap.bt.compiled.json | 1 + .../jungle/leaper/leaper.bt.compiled.json | 1 + .../mega_arachnid.bt.compiled.json | 1 + .../mega_arachnid_combat.bt.compiled.json | 1 + .../jungle/seedling/seedling.bt.compiled.json | 1 + .../seedling/seedling_meanie.bt.compiled.json | 1 + .../basilisk/basilisk.bt.compiled.json | 1 + .../bileworm/bileworm.bt.compiled.json | 1 + .../brimdemon/brimdemon.bt.compiled.json | 1 + .../goldgrub/babygrub.bt.compiled.json | 1 + .../goldgrub/goldgrub.bt.compiled.json | 1 + .../goldgrub/grub_eat_target.bt.compiled.json | 1 + .../lavaland/goliath/goliath.bt.compiled.json | 1 + .../gutlunch_baby.bt.compiled.json | 1 + .../gutlunch_milk.bt.compiled.json | 1 + .../gutlunch_warrior.bt.compiled.json | 1 + .../hivelord/hivelord.bt.compiled.json | 1 + .../lavaland/legion/legion.bt.compiled.json | 1 + .../legion/legion_brood.bt.compiled.json | 1 + .../legion/legion_monkey.bt.compiled.json | 1 + .../lobstrosity/lobstrosity.bt.compiled.json | 1 + .../lobstrosity_calm.bt.compiled.json | 1 + .../lobstrosity_capricious.bt.compiled.json | 1 + .../basic/lavaland/mook/bard.bt.compiled.json | 1 + .../mook/bard_find_targets.bt.compiled.json | 1 + .../mook/bard_play_music.bt.compiled.json | 1 + .../mook/chief_find_targets.bt.compiled.json | 1 + .../chief_issue_commands.bt.compiled.json | 1 + .../chief_manage_village.bt.compiled.json | 1 + .../generic_mook_behavior.bt.compiled.json | 1 + .../lavaland/mook/go_mining.bt.compiled.json | 1 + .../mook/heal_injured.bt.compiled.json | 1 + .../basic/lavaland/mook/mook.bt.compiled.json | 1 + .../lavaland/mook/support.bt.compiled.json | 1 + .../support_find_targets.bt.compiled.json | 1 + .../mook/tribal_chief.bt.compiled.json | 1 + .../mook/worker_find_targets.bt.compiled.json | 1 + .../node_drone/node_drone.bt.compiled.json | 1 + .../raptor/baby_raptor.bt.compiled.json | 1 + .../raptor/care_for_young.bt.compiled.json | 1 + .../raptor/raptor_ai.bt.compiled.json | 1 + .../raptor/raptor_find_food.bt.compiled.json | 1 + .../raptor/raptor_flee.bt.compiled.json | 1 + .../raptor_heal_injured.bt.compiled.json | 1 + .../raptor_play_with_owner.bt.compiled.json | 1 + .../raptor/raptor_trough.bt.compiled.json | 1 + .../lavaland/tendril/tendril.bt.compiled.json | 1 + .../lavaland/watcher/watcher.bt.compiled.json | 1 + .../basic/minebots/minebot.bt.compiled.json | 1 + .../minebots/minebot_combat.bt.compiled.json | 1 + .../minebots/minebot_mining.bt.compiled.json | 1 + .../basic/pets/cat/cat.bt.compiled.json | 1 + .../basic/pets/cat/cat_bread.bt.compiled.json | 1 + .../basic/pets/cat/cat_cake.bt.compiled.json | 1 + .../cat/cat_decorate_donuts.bt.compiled.json | 1 + .../pets/cat/cat_find_food.bt.compiled.json | 1 + .../pets/cat/cat_haul_food.bt.compiled.json | 1 + .../pets/cat/cat_hunt_mice.bt.compiled.json | 1 + .../cat/cat_reside_in_home.bt.compiled.json | 1 + .../cat/cat_turn_off_stove.bt.compiled.json | 1 + .../basic/pets/cat/kitten.bt.compiled.json | 1 + .../basic/pets/dog/guarddog.bt.compiled.json | 1 + .../living/basic/pets/fox.bt.compiled.json | 1 + .../basic/pets/fox_docile.bt.compiled.json | 1 + .../pets/gondolas/gondola.bt.compiled.json | 1 + .../basic/pets/orbie/orbie.bt.compiled.json | 1 + .../parrot/parrot_ai/parrot.bt.compiled.json | 1 + .../parrot_ai/parrot_ghost.bt.compiled.json | 1 + .../parrot_ai/parrot_hoard.bt.compiled.json | 1 + .../parrot_ai/perching.bt.compiled.json | 1 + .../pets/penguin/penguin.bt.compiled.json | 1 + .../penguin/penguin_baby.bt.compiled.json | 1 + .../pets/pet_cult/pet_cult.bt.compiled.json | 1 + .../living/basic/pets/sloth.bt.compiled.json | 1 + .../basic/revolutionary.bt.compiled.json | 1 + .../cybersun_ai_core.bt.compiled.json | 1 + .../dark_wizard.bt.compiled.json | 1 + .../ruin_defender/fleshblob.bt.compiled.json | 1 + .../living_floor.bt.compiled.json | 1 + .../ruin_defender/mad_piano.bt.compiled.json | 1 + .../mimic/mimic_animator.bt.compiled.json | 1 + .../mimic/mimic_copy.bt.compiled.json | 1 + .../mimic/mimic_crate.bt.compiled.json | 1 + .../mimic/mimic_gun.bt.compiled.json | 1 + .../ruin_defender/skeleton.bt.compiled.json | 1 + .../ruin_defender/stickman.bt.compiled.json | 1 + .../stickman_ranged.bt.compiled.json | 1 + .../wizard/wizard.bt.compiled.json | 1 + .../ruin_defender/zombie.bt.compiled.json | 1 + .../zombie_stupid.bt.compiled.json | 1 + .../pet_command_attack_slime.bt.compiled.json | 1 + .../basic/slime/ai/slime.bt.compiled.json | 1 + .../basic/snails/snail.bt.compiled.json | 1 + .../basic/space_fauna/ant.bt.compiled.json | 1 + .../space_fauna/bear/bear.bt.compiled.json | 1 + .../carp/basic_carp_tree.bt.compiled.json | 1 + .../space_fauna/carp/carp.bt.compiled.json | 1 + .../carp/carp_combat.bt.compiled.json | 1 + .../carp/carp_flee.bt.compiled.json | 1 + .../carp/carp_migration.bt.compiled.json | 1 + .../carp/carp_passive.bt.compiled.json | 1 + .../carp_passive_selection.bt.compiled.json | 1 + .../carp/carp_pet.bt.compiled.json | 1 + .../carp/carp_ranged.bt.compiled.json | 1 + .../carp_retaliate_selection.bt.compiled.json | 1 + .../carp_target_selection.bt.compiled.json | 1 + .../cat_butcherer.bt.compiled.json | 1 + .../changeling/headslug.bt.compiled.json | 1 + .../eyeball/eyeball.bt.compiled.json | 1 + .../space_fauna/faithless.bt.compiled.json | 1 + .../space_fauna/garden_gnome.bt.compiled.json | 1 + .../basic/space_fauna/ghost.bt.compiled.json | 1 + .../hivebot/hivebot.bt.compiled.json | 1 + .../hivebot/hivebot_mechanic.bt.compiled.json | 1 + .../hivebot/hivebot_ranged.bt.compiled.json | 1 + .../hivebot_ranged_rapid.bt.compiled.json | 1 + .../relay_to_hive_partner.bt.compiled.json | 1 + .../killer_tomato.bt.compiled.json | 1 + .../space_fauna/lightgeist.bt.compiled.json | 1 + .../meteor_heart.bt.compiled.json | 1 + .../basic/space_fauna/morph.bt.compiled.json | 1 + .../space_fauna/mushroom.bt.compiled.json | 1 + .../paper_wizard.bt.compiled.json | 1 + .../regal_rat/regal_rat.bt.compiled.json | 1 + .../basic/space_fauna/roro.bt.compiled.json | 1 + .../space_fauna/snake/banded.bt.compiled.json | 1 + .../space_fauna/snake/snake.bt.compiled.json | 1 + .../space_fauna/spaceman.bt.compiled.json | 1 + .../giant_spider.bt.compiled.json | 1 + .../giant_spider_pest.bt.compiled.json | 1 + .../giant_spider_retaliate.bt.compiled.json | 1 + .../giant_spider_weak.bt.compiled.json | 1 + .../spiderlings/spiderling.bt.compiled.json | 1 + .../young_spider.bt.compiled.json | 1 + .../statue/stares_at_people.bt.compiled.json | 1 + .../statue/statue.bt.compiled.json | 1 + .../suspicious_mannequin.bt.compiled.json | 1 + .../supermatter_spider.bt.compiled.json | 1 + .../wumborian_fugu.bt.compiled.json | 1 + .../basic/stoats/stoat.bt.compiled.json | 1 + .../basic/trader/trader.bt.compiled.json | 1 + .../mob/living/basic/tree.bt.compiled.json | 1 + .../basic/trooper/burst.bt.compiled.json | 1 + .../basic/trooper/peaceful.bt.compiled.json | 1 + .../trooper/peaceful_burst.bt.compiled.json | 1 + .../basic/trooper/ranged.bt.compiled.json | 1 + .../basic/trooper/shotgunner.bt.compiled.json | 1 + .../basic/trooper/trooper.bt.compiled.json | 1 + .../trooper/trooper_ranged.bt.compiled.json | 1 + .../basic/turtle/turtle.bt.compiled.json | 1 + .../basic/vermin/axolotl.bt.compiled.json | 1 + .../basic/vermin/butterfly.bt.compiled.json | 1 + .../cockroach/cockroach.bt.compiled.json | 1 + .../cockroach_aggro.bt.compiled.json | 1 + .../cockroach_glockroach.bt.compiled.json | 1 + .../cockroach_mobroach.bt.compiled.json | 1 + .../living/basic/vermin/crab.bt.compiled.json | 1 + .../basic/vermin/eat_cable.bt.compiled.json | 1 + .../basic/vermin/eat_cheese.bt.compiled.json | 1 + .../living/basic/vermin/frog.bt.compiled.json | 1 + .../frog_engage_target.bt.compiled.json | 1 + .../basic/vermin/lizard.bt.compiled.json | 1 + .../mothroach/mothroach.bt.compiled.json | 1 + .../basic/vermin/mouse.bt.compiled.json | 1 + .../basic/vermin/mouse_rat.bt.compiled.json | 1 + .../play_instrument_on_floor.bt.compiled.json | 1 + .../basic/vermin/space_bat.bt.compiled.json | 1 + .../vermin/suicide_frog.bt.compiled.json | 1 + .../basic/vermin/trash.bt.compiled.json | 1 + code/__DEFINES/MC.dm | 7 - code/__DEFINES/ai/ai.dm | 59 +- code/__DEFINES/ai/ai_blackboard.dm | 58 +- code/__DEFINES/ai/behavior_trees.dm | 72 ++ code/__DEFINES/ai/bot_keys.dm | 39 +- code/__DEFINES/ai/carp.dm | 2 + code/__DEFINES/ai/haunted.dm | 2 + code/__DEFINES/ai/monkey.dm | 16 +- code/__DEFINES/ai/monsters.dm | 60 +- code/__DEFINES/ai/pet_commands.dm | 4 + code/__DEFINES/ai/pets.dm | 4 + code/__DEFINES/ai/slime.dm | 2 + code/__DEFINES/ai/tourist.dm | 4 + code/__DEFINES/ai/trader.dm | 2 + code/__DEFINES/ai/ventcrawling.dm | 2 + code/__DEFINES/basic_mobs.dm | 6 + .../dcs/signals/signals_ai_controller.dm | 6 +- .../dcs/signals/signals_mob/signals_mob_ai.dm | 3 + .../signals/signals_mob/signals_mob_basic.dm | 2 +- code/__DEFINES/dcs/signals/signals_object.dm | 2 + code/__DEFINES/monkeys.dm | 5 +- code/__DEFINES/robots.dm | 1 - code/__DEFINES/subsystems.dm | 7 +- code/_globalvars/lists/basic_ai.dm | 19 - code/controllers/subsystem/ai_controllers.dm | 156 +++- .../subsystem/ai_controllers_low_priority.dm | 6 + .../subsystem/ai_idle_controllers.dm | 10 - .../subsystem/movement/movement_types.dm | 8 +- .../subsystem/processing/ai_behaviors.dm | 40 - .../subsystem/processing/ai_idle_behaviors.dm | 19 - .../unplanned_ai_idle_controllers.dm | 4 - .../subsystem/unplanned_controllers.dm | 39 - .../actions/mobs/create_legion_skull.dm | 2 +- code/datums/ai/README.md | 8 +- code/datums/ai/_ai_behavior.dm | 161 +++- code/datums/ai/_ai_bt_composites.dm | 307 ++++++++ code/datums/ai/_ai_bt_decorators.dm | 242 ++++++ code/datums/ai/_ai_bt_node.dm | 109 +++ code/datums/ai/_ai_bt_subtree.dm | 88 +++ code/datums/ai/_ai_controller.dm | 729 ++++++++++-------- code/datums/ai/_ai_planning_subtree.dm | 10 - code/datums/ai/_item_behaviors.dm | 73 +- code/datums/ai/babies/babies_behaviors.dm | 58 +- code/datums/ai/babies/babies_subtrees.dm | 30 - code/datums/ai/babies/make_babies.bt.json | 31 + code/datums/ai/bane/bane.bt.json | 43 ++ code/datums/ai/bane/bane_behaviors.dm | 9 - code/datums/ai/bane/bane_controller.dm | 2 +- code/datums/ai/bane/bane_subtrees.dm | 16 - .../ai/basic_mobs/base_basic_controller.dm | 7 +- .../basic_ai_behaviors/basic_attacking.dm | 155 ++-- .../basic_ai_behaviors/befriend_target.dm | 18 +- .../basic_ai_behaviors/call_reinforcements.dm | 50 ++ .../basic_ai_behaviors/clear_key.dm | 14 - .../basic_ai_behaviors/climb_tree.dm | 35 - .../consider_venting.bt.json | 46 ++ .../basic_ai_behaviors/emote_with_target.dm | 28 - .../basic_ai_behaviors/find_flee_location.dm | 37 + .../basic_ai_behaviors/find_parent.dm | 50 +- .../interact_with_target.dm | 29 - .../basic_ai_behaviors/nearest_targeting.dm | 13 +- .../basic_ai_behaviors/pick_up_item.dm | 44 -- .../basic_ai_behaviors/play_dead.dm | 25 + .../basic_ai_behaviors/pull_target.dm | 22 - .../run_away_from_target.dm | 76 -- .../set_travel_destination.dm | 10 - .../basic_ai_behaviors/step_towards_turf.dm | 74 -- .../basic_ai_behaviors/stop_and_stare.dm | 34 +- .../targeted_mob_ability.dm | 82 +- .../basic_ai_behaviors/targeting.dm | 192 +++-- .../basic_ai_behaviors/tipped_reaction.dm | 20 +- .../basic_ai_behaviors/travel_towards.dm | 53 -- .../basic_ai_behaviors/unbuckle_mob.dm | 11 - .../basic_ai_behaviors/use_mob_ability.dm | 39 + .../basic_ai_behaviors/ventcrawling.dm | 271 ++++--- .../basic_ai_behaviors/wounded_targeting.dm | 11 - .../basic_ai_behaviors/write_on_paper.dm | 22 +- .../basic_subtrees/attack_adjacent_target.dm | 35 - .../basic_subtrees/attack_obstacle_in_path.dm | 85 -- .../basic_subtrees/call_reinforcements.dm | 68 -- .../basic_subtrees/capricious_retaliate.dm | 66 +- .../basic_mobs/basic_subtrees/climb_tree.dm | 15 - .../basic_mobs/basic_subtrees/drag_items.dm | 60 -- .../ai/basic_mobs/basic_subtrees/enrage.dm | 51 +- .../basic_subtrees/escape_captivity.bt.json | 82 ++ .../basic_subtrees/escape_captivity.dm | 75 +- .../escape_captivity_pacifist.bt.json | 50 ++ .../basic_subtrees/express_happiness.dm | 44 -- .../ai/basic_mobs/basic_subtrees/find_food.dm | 43 -- .../find_paper_and_write.bt.json | 73 ++ .../basic_subtrees/find_paper_and_write.dm | 25 +- .../basic_mobs/basic_subtrees/find_parent.dm | 31 - .../find_targets_prioritize_traits.dm | 6 - .../ai/basic_mobs/basic_subtrees/fishing.dm | 44 -- .../basic_mobs/basic_subtrees/flee_target.dm | 39 - .../basic_subtrees/generic_hunger.dm | 2 + .../basic_subtrees/generic_play_instrument.dm | 2 + .../basic_subtrees/go_for_swim.bt.json | 70 ++ .../basic_mobs/basic_subtrees/go_for_swim.dm | 68 +- .../basic_subtrees/maintain_distance.dm | 119 --- .../basic_mobs/basic_subtrees/mine_walls.dm | 68 -- .../basic_subtrees/move_to_cardinal.dm | 83 +- .../opportunistic_ventcrawler.dm | 20 - .../basic_subtrees/play_with_owners.dm | 21 - .../prepare_travel_to_destination.dm | 23 - .../basic_subtrees/random_speech_loop.bt.json | 19 + .../basic_subtrees/random_speech_loop.dm | 2 + .../basic_subtrees/ranged_skirmish.dm | 50 -- .../run_away_from_target.bt.json | 50 ++ .../basic_subtrees/run_away_from_target.dm | 8 + ...run_away_from_target_run_and_shoot.bt.json | 69 ++ .../ai/basic_mobs/basic_subtrees/run_emote.dm | 31 - .../basic_subtrees/shapechange_ambush.dm | 41 - .../basic_subtrees/simple_attack_target.dm | 33 - .../simple_find_nearest_target_to_flee.dm | 25 - .../basic_subtrees/simple_find_target.dm | 26 - .../simple_find_wounded_target.dm | 6 - .../skittish_brawler_combat.bt.json | 88 +++ .../basic_subtrees/skittish_brawler_combat.dm | 4 + .../basic_subtrees/sleep_with_no_target.dm | 35 - .../basic_subtrees/speech_subtree.dm | 242 ------ .../basic_subtrees/stare_at_thing.dm | 12 - .../basic_subtrees/target_retaliate.dm | 94 --- .../basic_subtrees/targeted_mob_ability.dm | 34 - .../teleport_away_from_target.dm | 55 -- .../basic_subtrees/tip_reaction.bt.json | 13 + .../basic_subtrees/tipped_subtree.dm | 10 - .../basic_subtrees/travel_to_point.dm | 21 - .../basic_subtrees/use_mob_ability.dm | 33 - .../ai/basic_mobs/generic_controllers.dm | 155 ++-- .../ai/basic_mobs/pet_commands/fetch.dm | 143 ---- .../pet_commands/pet_command_attack.bt.json | 48 ++ .../pet_command_attack_dog.bt.json | 48 ++ .../pet_command_attack_minebot.bt.json | 48 ++ ...t_command_attack_ranged_glockroach.bt.json | 46 ++ .../pet_commands/pet_command_beehive.bt.json | 23 + .../pet_commands/pet_command_breed.bt.json | 64 ++ .../basic_mobs/pet_commands/pet_command_bt.dm | 128 +++ .../pet_commands/pet_command_fetch.bt.json | 107 +++ .../pet_commands/pet_command_fish.bt.json | 38 + .../pet_commands/pet_command_follow.bt.json | 25 + .../pet_command_mine_walls.bt.json | 46 ++ .../pet_commands/pet_command_move_to.bt.json | 27 + .../pet_commands/pet_command_planning.dm | 14 - .../pet_command_play_dead.bt.json | 14 + .../pet_command_protect_owner.bt.json | 57 ++ ...nd_protect_owner_ranged_glockroach.bt.json | 55 ++ .../pet_commands/pet_command_scatter.bt.json | 17 + .../pet_commands/pet_command_stay.bt.json | 8 + .../pet_commands/pet_command_swirl.bt.json | 81 ++ .../pet_command_targeted_ability.bt.json | 51 ++ .../pet_command_untargeted_ability.bt.json | 17 + .../pet_commands/pet_follow_friend.dm | 16 - .../pet_commands/pet_use_targeted_ability.dm | 39 - .../ai/basic_mobs/pet_commands/play_dead.dm | 26 - .../ai/basic_mobs/simple_ability.bt.json | 14 + .../basic_mobs/simple_ability_combat.bt.json | 101 +++ .../basic_mobs/simple_ability_melee.bt.json | 14 + .../simple_ability_melee_combat.bt.json | 91 +++ .../basic_mobs/simple_ability_ranged.bt.json | 14 + .../simple_ability_ranged_combat.bt.json | 82 ++ .../simple_ability_retaliate.bt.json | 14 + .../simple_ability_retaliate_combat.bt.json | 70 ++ .../ai/basic_mobs/simple_capricious.bt.json | 14 + .../simple_capricious_combat.bt.json | 66 ++ .../ai/basic_mobs/simple_fearful.bt.json | 10 + .../basic_mobs/simple_fearful_combat.bt.json | 41 + code/datums/ai/basic_mobs/simple_goon.bt.json | 15 + .../ai/basic_mobs/simple_hostile.bt.json | 14 + .../basic_mobs/simple_hostile_combat.bt.json | 98 +++ ...mple_hostile_combat_with_retaliate.bt.json | 107 +++ .../simple_hostile_obstacles.bt.json | 49 ++ .../simple_hostile_obstacles_combat.bt.json | 109 +++ .../ai/basic_mobs/simple_ranged.bt.json | 14 + .../basic_mobs/simple_ranged_combat.bt.json | 77 ++ .../simple_ranged_retaliate.bt.json | 14 + .../simple_ranged_retaliate_combat.bt.json | 69 ++ .../ai/basic_mobs/simple_retaliate.bt.json | 14 + .../simple_retaliate_combat.bt.json | 87 +++ .../ai/basic_mobs/simple_skirmisher.bt.json | 14 + .../simple_skirmisher_combat.bt.json | 92 +++ .../ai/basic_mobs/simple_skittish.bt.json | 10 + .../basic_mobs/simple_skittish_combat.bt.json | 41 + code/datums/ai/basic_mobs/talk.bt.json | 5 + .../target_sources/_target_source.dm | 106 +++ .../target_sources/held_items_then_oview.dm | 11 + .../target_sources/held_items_typed.dm | 13 + .../target_sources/mobs_in_oview.dm | 20 + .../target_sources/near_village_humans.dm | 12 + .../target_sources/oview_single_type.dm | 58 ++ .../target_sources/oview_typed_from_bb_key.dm | 19 + .../range_turfs_typecache_visible.dm | 34 + .../basic_mobs/target_sources/slime_source.dm | 11 + .../target_sources/turfs_in_oview.dm | 23 + .../_targeting_strategy.dm | 38 +- .../targeting_strategies/accessible_cable.dm | 14 + .../targeting_strategies/ally_mob.dm | 10 + .../targeting_strategies/baby_raptor.dm | 11 + .../basic_targeting_strategy.dm | 103 ++- .../targeting_strategies/beamable_hydro.dm | 11 + .../befriendable_cultist.dm | 13 + .../targeting_strategies/cat_food.dm | 12 + .../targeting_strategies/chargeable_apc.dm | 14 + .../targeting_strategies/conscious_human.dm | 13 + .../targeting_strategies/conscious_mob.dm | 11 + .../targeting_strategies/conscious_snail.dm | 13 + .../targeting_strategies/damaged_eyes.dm | 17 + .../targeting_strategies/damaged_machine.dm | 11 + .../targeting_strategies/dead_mob.dm | 20 + .../targeting_strategies/decorated_donut.dm | 11 + .../dont_target_friends.dm | 9 +- .../targeting_strategies/drillable_ice.dm | 12 + .../targeting_strategies/empty_paper.dm | 11 + .../targeting_strategies/finished_stove.dm | 14 + .../targeting_strategies/food_or_drink.dm | 30 + .../goliath_diggable_turf.dm | 11 + .../targeting_strategies/goose_edible.dm | 11 + .../targeting_strategies/huntable.dm | 12 + .../targeting_strategies/huntable_mouse.dm | 11 + .../targeting_strategies/injured_mob.dm | 19 + .../targeting_strategies/injured_raptor.dm | 6 + .../legged_conscious_human.dm | 13 + .../targeting_strategies/living_not_dead.dm | 12 + .../targeting_strategies/non_stump_tree.dm | 10 + .../targeting_strategies/pickup_item.dm | 10 + .../targeting_strategies/playable_deer.dm | 11 + .../playable_synthesizer.dm | 12 + .../pollinatable_hydro.dm | 11 + .../targeting_strategies/raptor_trough.dm | 10 + .../targeting_strategies/slime_food.dm | 44 ++ .../targeting_strategies/sniffable_hydro.dm | 15 + .../targeting_strategies/stealable_item.dm | 11 + .../targeting_strategies/stocked_beehive.dm | 11 + .../targeting_strategies/treatable_hydro.dm | 19 + .../targeting_strategies/trough_with_ore.dm | 10 + .../targeting_strategies/unbroken_light.dm | 11 + .../targeting_strategies/uncarried_egg.dm | 10 + .../targeting_strategies/unlit_bonfire.dm | 11 + .../targeting_strategies/valid_cat_home.dm | 11 + .../targeting_strategies/valid_kitten.dm | 15 + .../targeting_strategies/walkable_turf.dm | 9 + .../targeting_strategies/water_dispenser.dm | 13 + .../targeting_strategies/with_object.dm | 12 +- .../targeting_strategies/working_machine.dm | 11 + code/datums/ai/bots/bot_decorators.dm | 45 ++ code/datums/ai/bots/bot_patrol.bt.json | 88 +++ .../ai/bots/bot_respond_to_summon.bt.json | 29 + .../ai/bots/bot_salute_authority.bt.json | 29 + code/datums/ai/bots/bot_subtrees.dm | 281 +++++++ code/datums/ai/bt_viewer.dm | 161 ++++ code/datums/ai/cursed/cursed.bt.json | 72 ++ code/datums/ai/cursed/cursed_behaviors.dm | 9 +- code/datums/ai/cursed/cursed_controller.dm | 3 +- code/datums/ai/cursed/cursed_subtrees.dm | 13 - code/datums/ai/dog/dog.bt.json | 44 ++ code/datums/ai/dog/dog_behaviors.dm | 49 -- code/datums/ai/dog/dog_bt.dm | 113 +++ code/datums/ai/dog/dog_controller.dm | 28 +- code/datums/ai/dog/dog_corgi.bt.json | 74 ++ code/datums/ai/dog/dog_harassment.bt.json | 39 + code/datums/ai/dog/dog_subtrees.dm | 38 - code/datums/ai/generic/find_and_set.dm | 223 ------ code/datums/ai/generic/generic_behaviors.dm | 377 --------- code/datums/ai/generic/generic_subtrees.dm | 98 --- .../acquire_injured_target.dm | 15 + .../ai/generic_behaviors/acquire_target.dm | 196 +++++ .../ai/generic_behaviors/ai_interact.dm | 17 + .../ai/generic_behaviors/attack_obstacles.dm | 65 ++ .../ai/generic_behaviors/battle_screech.dm | 10 + .../generic_behaviors/break_out_of_object.dm | 40 + .../ai/generic_behaviors/break_spine.dm | 42 + .../generic_behaviors/cancel_current_plan.dm | 6 + code/datums/ai/generic_behaviors/clear_key.dm | 8 + code/datums/ai/generic_behaviors/consume.dm | 43 ++ .../ai/generic_behaviors/copy_bb_key.dm | 8 + .../ai/generic_behaviors/drag_target.dm | 21 + .../generic_behaviors/drop_all_held_items.dm | 9 + .../ai/generic_behaviors/express_happiness.dm | 53 ++ .../face_target_or_face_initial.dm | 24 + code/datums/ai/generic_behaviors/fail.dm | 4 + .../find_furthest_turf_from_target.dm | 32 + .../ai/generic_behaviors/find_nearby.dm | 21 + .../find_target_facing_turf.dm | 19 + .../generic_behaviors/find_unwebbed_turf.dm | 46 ++ .../find_valid_teleport_location.dm | 28 + code/datums/ai/generic_behaviors/give.dm | 86 +++ .../ai/generic_behaviors/grab_target.dm | 33 + .../ai/generic_behaviors/heal_eye_damage.dm | 22 + .../ai/generic_behaviors/hunt_target.dm | 184 +++++ .../ai/generic_behaviors/issue_pet_command.dm | 41 + .../keep_playing_instrument.dm | 19 + .../ai/generic_behaviors/maintain_distance.dm | 94 +++ .../datums/ai/generic_behaviors/mine_walls.dm | 53 ++ .../ai/generic_behaviors/move_to_target.dm | 55 ++ .../ai/generic_behaviors/perform_emote.dm | 11 + .../generic_behaviors/pick_random_ability.dm | 27 + code/datums/ai/generic_behaviors/pick_up.dm | 28 + .../ai/generic_behaviors/play_instrument.dm | 13 + .../ai/generic_behaviors/random_walk.dm | 111 +++ code/datums/ai/generic_behaviors/resist.dm | 14 + code/datums/ai/generic_behaviors/run_emote.dm | 21 + .../ai/generic_behaviors/set_bb_cooldown.dm | 10 + .../datums/ai/generic_behaviors/set_bb_key.dm | 10 + .../ai/generic_behaviors/setup_instrument.dm | 18 + code/datums/ai/generic_behaviors/speech.dm | 184 +++++ code/datums/ai/generic_behaviors/spin_web.dm | 35 + .../ai/generic_behaviors/stop_dragging.dm | 7 + .../ai/generic_behaviors/stuff_in_disposal.dm | 37 + code/datums/ai/generic_behaviors/succeed.dm | 4 + .../ai/generic_behaviors/target_retaliate.dm | 53 ++ .../ai/generic_behaviors/use_in_hand.dm | 10 + .../ai/generic_behaviors/use_on_object.dm | 12 + .../generic_behaviors/virtual_pick_up_item.dm | 59 ++ code/datums/ai/generic_behaviors/wait.dm | 19 + .../generic_decorators/ability_available.dm | 8 + .../ai/generic_decorators/bb_key_at_least.dm | 8 + .../ai/generic_decorators/bb_key_equals.dm | 7 + .../bb_key_list_min_count.dm | 14 + .../ai/generic_decorators/bb_key_set.dm | 13 + .../ai/generic_decorators/bb_key_true.dm | 7 + .../buckle_target_dangerous.dm | 10 + .../ai/generic_decorators/can_see_target.dm | 17 + .../ai/generic_decorators/check_cooldown.dm | 28 + .../ai/generic_decorators/check_rider_stat.dm | 11 + .../container_attackable.dm | 15 + .../ai/generic_decorators/is_at_distance.dm | 21 + .../ai/generic_decorators/is_dragging.dm | 6 + .../generic_decorators/is_grabbing_target.dm | 20 + .../generic_decorators/is_holding_target.dm | 24 + .../ai/generic_decorators/is_in_vent.dm | 13 + .../generic_decorators/is_target_stunned.dm | 11 + .../ai/generic_decorators/item_inside_pawn.dm | 46 ++ .../ai/generic_decorators/key_in_typelist.dm | 30 + .../keys_different_gender.dm | 30 + .../generic_decorators/mob_stat_at_least.dm | 45 ++ .../generic_decorators/no_humans_watching.dm | 11 + .../generic_decorators/pawn_buckled_to_obj.dm | 18 + .../pawn_contained_in_obj.dm | 18 + .../pawn_farther_than_from_key.dm | 13 + .../pawn_grabbed_by_enemy.dm | 22 + .../ai/generic_decorators/pawn_has_gravity.dm | 13 + .../generic_decorators/pawn_has_trait_from.dm | 16 + .../generic_decorators/pawn_health_below.dm | 20 + .../ai/generic_decorators/pawn_inside_mob.dm | 13 + .../generic_decorators/pawn_is_restrained.dm | 14 + .../ai/generic_decorators/pawn_loc_is_type.dm | 15 + .../pawn_nutrition_below.dm | 10 + .../generic_decorators/pawn_same_z_as_key.dm | 9 + .../generic_decorators/pawn_turf_has_trait.dm | 7 + .../ai/generic_decorators/random_chance.dm | 7 + .../random_chance_from_key.dm | 10 + .../generic_decorators/target_has_reagent.dm | 44 ++ .../ai/generic_decorators/target_has_trait.dm | 43 ++ .../target_health_below_fraction.dm | 42 + .../target_holding_lit_item.dm | 21 + .../target_is_holding_item.dm | 42 + .../ai/generic_decorators/target_is_type.dm | 12 + .../ai/generic_decorators/target_legcuffed.dm | 10 + .../ai/generic_decorators/target_on_ground.dm | 45 ++ .../ai/generic_decorators/true_for_time.dm | 31 + code/datums/ai/generic_hunger.bt.json | 43 ++ .../datums/ai/generic_play_instrument.bt.json | 83 ++ .../basic_find_target.bt.json | 10 + .../ai/generic_subtrees/basic_find_target.dm | 2 + .../capricious_pick_target.bt.json | 18 + .../capricious_pick_target.dm | 4 + .../ai/generic_subtrees/climb_tree.bt.json | 50 ++ code/datums/ai/generic_subtrees/climb_tree.dm | 2 + .../ai/generic_subtrees/find_food.bt.json | 19 + code/datums/ai/generic_subtrees/find_food.dm | 2 + .../ai/generic_subtrees/find_partner.bt.json | 32 + .../ai/generic_subtrees/find_partner.dm | 2 + .../find_stealable_object.bt.json | 88 +++ .../generic_subtrees/find_stealable_object.dm | 2 + .../forage_and_retaliate.bt.json | 92 +++ .../generic_subtrees/forage_and_retaliate.dm | 2 + .../datums/ai/generic_subtrees/make_babies.dm | 2 + .../generic_subtrees/move_to_and_eat.bt.json | 36 + .../ai/generic_subtrees/move_to_and_eat.dm | 2 + .../generic_subtrees/move_to_and_hunt.bt.json | 63 ++ .../ai/generic_subtrees/move_to_and_hunt.dm | 2 + .../move_to_reinforce.bt.json | 30 + .../ai/generic_subtrees/move_to_reinforce.dm | 2 + .../pick_retaliate_target.bt.json | 10 + .../generic_subtrees/pick_retaliate_target.dm | 3 + .../ai/generic_subtrees/random_walk.bt.json | 29 + .../datums/ai/generic_subtrees/random_walk.dm | 2 + .../skittish_and_speak.bt.json | 32 + .../ai/generic_subtrees/skittish_and_speak.dm | 2 + .../generic_subtrees/steal_and_flee.bt.json | 43 ++ .../ai/generic_subtrees/steal_and_flee.dm | 2 + code/datums/ai/hauntium/haunted.bt.json | 107 +++ code/datums/ai/hauntium/haunted_bt_nodes.dm | 83 ++ code/datums/ai/hauntium/haunted_controller.dm | 3 +- code/datums/ai/hauntium/hauntium_subtrees.dm | 24 - .../ai/hunting_behavior/hunting_behaviors.dm | 170 ---- .../ai/hunting_behavior/hunting_cockroach.dm | 2 - .../ai/hunting_behavior/hunting_corpses.dm | 17 - .../ai/hunting_behavior/hunting_lights.dm | 18 - .../ai/hunting_behavior/hunting_mouse.dm | 55 -- .../ai/idle_behaviors/_idle_behavior.dm | 5 - code/datums/ai/idle_behaviors/idle_dog.dm | 21 - code/datums/ai/idle_behaviors/idle_haunted.dm | 15 - code/datums/ai/idle_behaviors/idle_monkey.dm | 37 - .../ai/idle_behaviors/idle_random_walk.dm | 86 --- code/datums/ai/learn_ai.md | 333 +++++--- .../ai/learn_ai_images/behavior_example.png | Bin 0 -> 24461 bytes .../ai/learn_ai_images/bt_editor_example1.png | Bin 0 -> 11728 bytes .../ai/learn_ai_images/decorator_example.png | Bin 0 -> 27708 bytes .../ai/learn_ai_images/parallel_example.png | Bin 0 -> 23879 bytes .../ai/learn_ai_images/selector_example.png | Bin 0 -> 11099 bytes .../ai/learn_ai_images/sequence_example.png | Bin 0 -> 14203 bytes .../ai/learn_ai_images/subplan_example.png | Bin 0 -> 42695 bytes code/datums/ai/monkey/monkey.bt.json | 245 ++++++ code/datums/ai/monkey/monkey_behaviors.dm | 305 -------- code/datums/ai/monkey/monkey_bt_nodes.dm | 402 ++++++++++ code/datums/ai/monkey/monkey_combat.bt.json | 228 ++++++ code/datums/ai/monkey/monkey_controller.dm | 86 +-- .../ai/monkey/monkey_find_weapon.bt.json | 50 ++ .../ai/monkey/monkey_serve_food.bt.json | 132 ++++ .../ai/monkey/monkey_shenanigans.bt.json | 114 +++ code/datums/ai/monkey/monkey_subtrees.dm | 111 --- code/datums/ai/movement/_ai_movement.dm | 23 +- .../movement/ai_movement_basic_avoidance.dm | 15 +- .../ai/movement/ai_movement_complete_stop.dm | 5 +- code/datums/ai/movement/ai_movement_dumb.dm | 13 +- code/datums/ai/movement/ai_movement_jps.dm | 26 +- .../vending_machines/vending_machine.bt.json | 64 ++ .../vending_machine_behaviors.dm | 68 +- .../vending_machine_controller.dm | 25 +- .../ai/robot_customer/robot_customer.bt.json | 125 +++ .../robot_customer_behaviors.dm | 121 +-- .../robot_customer_controller.dm | 6 +- .../robot_customer/robot_customer_subtrees.dm | 23 - code/datums/brain_damage/special.dm | 8 +- code/datums/components/aggro_emote.dm | 2 +- code/datums/components/ai_has_target_timer.dm | 2 +- .../datums/components/ai_listen_to_weather.dm | 2 +- code/datums/components/appearance_on_aggro.dm | 2 +- code/datums/components/connect_range.dm | 2 + code/datums/components/pet_commands/fetch.dm | 28 +- .../components/pet_commands/pet_command.dm | 14 +- .../pet_commands/pet_commands_basic.dm | 80 +- code/datums/components/proficient_miner.dm | 17 +- code/datums/components/revenge_ability.dm | 2 +- code/datums/components/tameable.dm | 3 + code/datums/components/tree_climber.dm | 4 +- code/datums/diseases/verminous_plague.dm | 2 +- code/datums/dog_fashion.dm | 8 +- code/datums/elements/ai_flee_while_injured.dm | 2 - code/datums/elements/ai_retaliate.dm | 2 +- .../datums/elements/ai_target_damagesource.dm | 4 +- .../fields/ai_target_tracking.dm | 49 +- .../proximity_monitor/fields/timestop.dm | 4 +- .../debuffs/slime/slime_leech.dm | 3 +- .../machinery/computer/arcade/orion_event.dm | 2 +- code/game/machinery/dna_scanner.dm | 13 +- code/modules/admin/admin_verbs.dm | 103 +-- code/modules/antagonists/blob/powers.dm | 2 +- .../antagonists/heretic/heretic_knowledge.dm | 3 +- .../antagonists/netguardian.bt.json | 110 +++ .../bitrunning/antagonists/netguardian.dm | 21 +- .../virtual_domain/domains/crewman.bt.json | 118 +++ .../domains/crewman_hostile.bt.json | 118 +++ .../domains/crewman_hostile_ranged.bt.json | 129 ++++ .../domains/crewman_ranged.bt.json | 129 ++++ .../virtual_domain/domains/heretic_hunt.dm | 40 +- .../kinetic_crusher/trophies_fauna.dm | 2 +- .../kinetic_crusher/trophies_megafauna.dm | 2 +- code/modules/mob/inventory.dm | 1 + code/modules/mob/living/basic/alien/_alien.dm | 14 +- .../mob/living/basic/alien/alien.bt.json | 9 + .../mob/living/basic/alien/alien_ai.dm | 99 +-- .../living/basic/alien/basic_alien.bt.json | 60 ++ .../mob/living/basic/alien/drone.bt.json | 9 + .../living/basic/alien/lay_alien_egg.bt.json | 14 + .../basic/alien/melee_alien_combat.bt.json | 47 ++ .../basic/alien/plant_alien_weeds.bt.json | 23 + .../mob/living/basic/alien/queen.bt.json | 9 + .../basic/alien/ranged_alien_combat.bt.json | 48 ++ .../mob/living/basic/alien/sentinel.bt.json | 8 + .../mob/living/basic/blob_minions/blob_ai.dm | 27 +- .../basic/blob_minions/blob_spore.bt.json | 69 ++ .../basic/blob_minions/blob_zombie.bt.json | 42 + .../basic/blob_minions/blobbernaut.bt.json | 14 + .../blood_drunk_miner/_blood_drunk_miner.dm | 9 +- .../boss/blood_drunk_miner/blood_drunk_ai.dm | 52 +- .../blood_drunk_miner.bt.json | 45 ++ .../blood_drunk_miner_combat.bt.json | 132 ++++ code/modules/mob/living/basic/boss/boss.dm | 3 +- .../mob/living/basic/boss/thing/thing.dm | 10 +- .../mob/living/basic/boss/thing/thing_ai.dm | 67 +- .../living/basic/boss/thing/thing_aoe.bt.json | 72 ++ .../basic/boss/thing/thing_boss.bt.json | 70 ++ .../basic/boss/thing/thing_melee.bt.json | 89 +++ .../modules/mob/living/basic/bots/bot.bt.json | 22 + code/modules/mob/living/basic/bots/bot_ai.dm | 283 +------ code/modules/mob/living/basic/bots/bot_hud.dm | 7 +- .../bots/cleanbot/clean_pet_target.bt.json | 28 + .../basic/bots/cleanbot/cleanbot.bt.json | 251 ++++++ .../living/basic/bots/cleanbot/cleanbot_ai.dm | 178 +---- .../mob/living/basic/bots/dedbot.bt.json | 73 ++ code/modules/mob/living/basic/bots/dedbot.dm | 13 +- .../mob/living/basic/bots/ed209/ed209.bt.json | 132 ++++ .../mob/living/basic/bots/ed209/ed209_ai.dm | 26 +- .../living/basic/bots/ed209/ed209_nukie_ai.dm | 9 +- .../basic/bots/ed209/ed209_syndicate.bt.json | 21 + .../living/basic/bots/firebot/firebot.bt.json | 119 +++ .../living/basic/bots/firebot/firebot_ai.dm | 172 ++--- .../basic/bots/honkbots/honkbot.bt.json | 191 +++++ .../mob/living/basic/bots/honkbots/honkbot.dm | 2 +- .../living/basic/bots/honkbots/honkbot_ai.dm | 201 ++--- .../basic/bots/honkbots/honkbot_slip.bt.json | 77 ++ .../basic/bots/hygienebot/hygienebot.bt.json | 109 +++ .../basic/bots/hygienebot/hygienebot_ai.dm | 126 +-- .../living/basic/bots/medbot/medbot.bt.json | 109 +++ .../mob/living/basic/bots/medbot/medbot_ai.dm | 214 ++--- .../medbot_find_and_announce_crit.bt.json | 16 + .../bots/medbot/medbot_treat_patient.bt.json | 28 + .../living/basic/bots/mulebot/mulebot.bt.json | 103 +++ .../mob/living/basic/bots/mulebot/mulebot.dm | 2 +- .../living/basic/bots/mulebot/mulebot_ai.dm | 78 +- .../basic/bots/repairbot/repairbot.bt.json | 32 + .../basic/bots/repairbot/repairbot_ai.dm | 428 +++++----- .../bots/repairbot/repairbot_emagged.bt.json | 71 ++ .../repairbot/repairbot_find_target.bt.json | 143 ++++ .../repairbot/repairbot_repair_target.bt.json | 88 +++ .../living/basic/bots/secbot/secbot.bt.json | 85 ++ .../mob/living/basic/bots/secbot/secbot.dm | 2 +- .../mob/living/basic/bots/secbot/secbot_ai.dm | 43 +- .../living/basic/bots/secbot/super_beepsky.dm | 2 +- .../basic/bots/secbot/super_beepsky_ai.dm | 10 +- .../living/basic/bots/vibebot/vibebot.bt.json | 74 ++ .../living/basic/bots/vibebot/vibebot_ai.dm | 68 +- .../mob/living/basic/clown/clown.bt.json | 28 + .../mob/living/basic/clown/clown_ai.dm | 8 +- .../basic/cult/constructs/artificer.bt.json | 83 ++ .../basic/cult/constructs/construct_ai.dm | 39 +- .../basic/cult/constructs/juggernaut.bt.json | 5 + .../basic/cult/constructs/proteon.bt.json | 14 + .../basic/cult/constructs/wraith.bt.json | 83 ++ .../living/basic/cytology/vatbeast.bt.json | 137 ++++ .../mob/living/basic/cytology/vatbeast.dm | 26 +- .../mob/living/basic/farm_animals/bee/_bee.dm | 2 +- .../living/basic/farm_animals/bee/bee.bt.json | 86 +++ .../basic/farm_animals/bee/bee_ai_behavior.dm | 142 +--- .../basic/farm_animals/bee/bee_ai_subtree.dm | 87 +-- .../living/basic/farm_animals/bee/bee_bt.dm | 132 ++++ .../basic/farm_animals/bee/find_hive.bt.json | 43 ++ .../farm_animals/bee/pollinate_target.bt.json | 27 + .../basic/farm_animals/bee/queen_bee.bt.json | 58 ++ .../bee/transition_hive_status.bt.json | 45 ++ .../basic/farm_animals/chicken/chick.bt.json | 87 +++ .../basic/farm_animals/chicken/chick.dm | 13 +- .../farm_animals/chicken/chicken.bt.json | 5 + .../basic/farm_animals/chicken/chicken.dm | 15 +- .../mob/living/basic/farm_animals/cow/_cow.dm | 2 +- .../living/basic/farm_animals/cow/cow.bt.json | 48 ++ .../living/basic/farm_animals/cow/cow_ai.dm | 18 +- .../farm_animals/cow/cow_moonicorn.bt.json | 96 +++ .../basic/farm_animals/cow/cow_moonicorn.dm | 21 +- .../basic/farm_animals/cow/cow_wisdom.bt.json | 28 + .../basic/farm_animals/cow/cow_wisdom.dm | 11 +- .../basic/farm_animals/deer/deer.bt.json | 372 +++++++++ .../living/basic/farm_animals/deer/deer_ai.dm | 185 ++--- .../basic/farm_animals/goat/goat.bt.json | 26 + .../living/basic/farm_animals/goat/goat_ai.dm | 22 +- .../goose/forage_for_goose_food.bt.json | 11 + .../basic/farm_animals/goose/goose.bt.json | 38 + .../living/basic/farm_animals/goose/goose.dm | 9 +- .../basic/farm_animals/goose/goose_ai.dm | 83 +- .../farm_animals/goose/goose_calm.bt.json | 30 + .../farm_animals/gorilla/gorilla.bt.json | 130 ++++ .../basic/farm_animals/gorilla/gorilla_ai.dm | 17 +- .../mob/living/basic/farm_animals/pig.bt.json | 26 + .../mob/living/basic/farm_animals/pig.dm | 17 +- .../living/basic/farm_animals/pony.bt.json | 43 ++ .../mob/living/basic/farm_animals/pony.dm | 29 +- .../living/basic/farm_animals/rabbit.bt.json | 5 + .../mob/living/basic/farm_animals/rabbit.dm | 41 +- .../living/basic/farm_animals/sheep.bt.json | 5 + .../mob/living/basic/farm_animals/sheep.dm | 14 +- .../mob/living/basic/festivus_pole.bt.json | 122 +++ .../modules/mob/living/basic/festivus_pole.dm | 45 +- .../mob/living/basic/heretic/flesh_stalker.dm | 10 +- .../living/basic/heretic/raw_prophet.bt.json | 14 + .../mob/living/basic/heretic/raw_prophet.dm | 9 +- .../living/basic/heretic/rust_walker.bt.json | 126 +++ .../mob/living/basic/heretic/rust_walker.dm | 32 +- .../mob/living/basic/heretic/stalker.bt.json | 147 ++++ .../living/basic/heretic/star_gazer.bt.json | 19 + .../mob/living/basic/heretic/star_gazer.dm | 24 +- .../basic/icemoon/ice_demon/ice_demon.bt.json | 54 ++ .../ice_demon/ice_demon_afterimage.bt.json | 18 + .../basic/icemoon/ice_demon/ice_demon_ai.dm | 105 +-- .../ice_demon/ice_demon_combat.bt.json | 127 +++ .../ice_demon_flee_from_fire.bt.json | 37 + .../basic/icemoon/ice_whelp/ice_whelp.bt.json | 284 +++++++ .../basic/icemoon/ice_whelp/ice_whelp.dm | 1 - .../basic/icemoon/ice_whelp/ice_whelp_ai.dm | 152 +--- .../basic/icemoon/polar_bear/polar.bt.json | 40 + .../basic/icemoon/polar_bear/polar_bear.dm | 10 +- .../living/basic/icemoon/wolf/wolf.bt.json | 120 +++ .../mob/living/basic/icemoon/wolf/wolf_ai.dm | 23 +- .../mob/living/basic/illusion/escape.bt.json | 28 + .../mob/living/basic/illusion/illlusion_ai.dm | 28 +- .../mob/living/basic/illusion/illusion.dm | 2 +- .../living/basic/illusion/retaliate.bt.json | 28 + .../living/basic/jungle/human_trap.bt.json | 5 + .../living/basic/jungle/leaper/leaper.bt.json | 122 +++ .../living/basic/jungle/leaper/leaper_ai.dm | 35 +- .../mega_arachnid/mega_arachnid.bt.json | 96 +++ .../jungle/mega_arachnid/mega_arachnid_ai.dm | 74 +- .../mega_arachnid_combat.bt.json | 147 ++++ .../basic/jungle/seedling/seedling.bt.json | 286 +++++++ .../basic/jungle/seedling/seedling_ai.dm | 156 +--- .../jungle/seedling/seedling_meanie.bt.json | 106 +++ .../living/basic/jungle/venus_human_trap.dm | 9 +- .../basic/lavaland/basilisk/basilisk.bt.json | 116 +++ .../basic/lavaland/basilisk/basilisk.dm | 9 +- .../basic/lavaland/bileworm/bileworm.bt.json | 143 ++++ .../lavaland/bileworm/bileworm_actions.dm | 2 +- .../basic/lavaland/bileworm/bileworm_ai.dm | 58 +- .../basic/lavaland/brimdemon/brimbeam.dm | 2 +- .../lavaland/brimdemon/brimdemon.bt.json | 122 +++ .../basic/lavaland/brimdemon/brimdemon_ai.dm | 40 +- .../basic/lavaland/goldgrub/babygrub.bt.json | 93 +++ .../basic/lavaland/goldgrub/goldgrub.bt.json | 238 ++++++ .../basic/lavaland/goldgrub/goldgrub_ai.dm | 328 ++++---- .../lavaland/goldgrub/grub_eat_target.bt.json | 44 ++ .../basic/lavaland/goliath/goliath.bt.json | 171 ++++ .../basic/lavaland/goliath/goliath_ai.dm | 113 +-- .../gutlunchers/gutlunch_baby.bt.json | 81 ++ .../gutlunchers/gutlunch_milk.bt.json | 96 +++ .../gutlunchers/gutlunch_warrior.bt.json | 53 ++ .../lavaland/gutlunchers/gutlunchers_ai.dm | 141 ++-- .../basic/lavaland/hivelord/hivelord.bt.json | 5 + .../basic/lavaland/hivelord/hivelord.dm | 2 +- .../basic/lavaland/hivelord/hivelord_ai.dm | 8 +- .../lavaland/hivelord/spawn_hivelord_brood.dm | 2 +- .../basic/lavaland/legion/legion.bt.json | 102 +++ .../living/basic/lavaland/legion/legion_ai.dm | 55 +- .../lavaland/legion/legion_brood.bt.json | 81 ++ .../basic/lavaland/legion/legion_brood.dm | 2 +- .../lavaland/legion/legion_monkey.bt.json | 119 +++ .../basic/lavaland/legion/legion_monkey.dm | 12 +- .../basic/lavaland/legion/spawn_legions.dm | 2 +- .../lavaland/lobstrosity/lobstrosity.bt.json | 131 ++++ .../basic/lavaland/lobstrosity/lobstrosity.dm | 4 +- .../lavaland/lobstrosity/lobstrosity_ai.dm | 299 +------ .../lobstrosity/lobstrosity_calm.bt.json | 19 + .../lobstrosity_capricious.bt.json | 19 + .../living/basic/lavaland/mook/bard.bt.json | 9 + .../lavaland/mook/bard_find_targets.bt.json | 56 ++ .../lavaland/mook/bard_play_music.bt.json | 55 ++ .../lavaland/mook/chief_find_targets.bt.json | 68 ++ .../mook/chief_issue_commands.bt.json | 53 ++ .../mook/chief_manage_village.bt.json | 150 ++++ .../mook/generic_mook_behavior.bt.json | 178 +++++ .../basic/lavaland/mook/go_mining.bt.json | 213 +++++ .../basic/lavaland/mook/heal_injured.bt.json | 42 + .../living/basic/lavaland/mook/mook.bt.json | 9 + .../mob/living/basic/lavaland/mook/mook.dm | 9 +- .../mob/living/basic/lavaland/mook/mook_ai.dm | 389 +--------- .../mob/living/basic/lavaland/mook/mook_bt.dm | 156 ++++ .../basic/lavaland/mook/support.bt.json | 9 + .../mook/support_find_targets.bt.json | 63 ++ .../basic/lavaland/mook/tribal_chief.bt.json | 10 + .../lavaland/mook/worker_find_targets.bt.json | 72 ++ .../lavaland/node_drone/node_drone.bt.json | 73 ++ .../basic/lavaland/node_drone/node_drone.dm | 43 +- .../basic/lavaland/raptor/baby_raptor.bt.json | 108 +++ .../lavaland/raptor/care_for_young.bt.json | 50 ++ .../basic/lavaland/raptor/raptor_ai.bt.json | 221 ++++++ .../lavaland/raptor/raptor_ai_behavior.dm | 38 - .../lavaland/raptor/raptor_ai_controller.dm | 59 +- .../lavaland/raptor/raptor_ai_subtrees.dm | 79 +- .../lavaland/raptor/raptor_find_food.bt.json | 31 + .../basic/lavaland/raptor/raptor_flee.bt.json | 41 + .../raptor/raptor_heal_injured.bt.json | 36 + .../raptor/raptor_play_with_owner.bt.json | 28 + .../lavaland/raptor/raptor_trough.bt.json | 36 + .../basic/lavaland/tendril/tendril.bt.json | 124 +++ .../living/basic/lavaland/tendril/tendril.dm | 2 +- .../basic/lavaland/tendril/tendril_ai.dm | 35 +- .../basic/lavaland/watcher/watcher.bt.json | 133 ++++ .../basic/lavaland/watcher/watcher_ai.dm | 32 +- .../mob/living/basic/minebots/minebot.bt.json | 107 +++ .../mob/living/basic/minebots/minebot.dm | 6 +- .../mob/living/basic/minebots/minebot_ai.dm | 319 +++----- .../basic/minebots/minebot_combat.bt.json | 112 +++ .../basic/minebots/minebot_mining.bt.json | 193 +++++ .../mob/living/basic/pets/cat/bread_cat_ai.dm | 62 -- .../mob/living/basic/pets/cat/cat.bt.json | 275 +++++++ .../mob/living/basic/pets/cat/cat_ai.dm | 292 +------ .../living/basic/pets/cat/cat_bread.bt.json | 140 ++++ .../mob/living/basic/pets/cat/cat_bt.dm | 259 +++++++ .../living/basic/pets/cat/cat_cake.bt.json | 154 ++++ .../pets/cat/cat_decorate_donuts.bt.json | 29 + .../basic/pets/cat/cat_find_food.bt.json | 30 + .../basic/pets/cat/cat_haul_food.bt.json | 41 + .../basic/pets/cat/cat_hunt_mice.bt.json | 36 + .../basic/pets/cat/cat_reside_in_home.bt.json | 55 ++ .../basic/pets/cat/cat_turn_off_stove.bt.json | 30 + .../mob/living/basic/pets/cat/kitten.bt.json | 151 ++++ .../mob/living/basic/pets/cat/kitten_ai.dm | 67 -- .../modules/mob/living/basic/pets/dog/_dog.dm | 23 +- .../mob/living/basic/pets/dog/corgi.dm | 23 +- .../mob/living/basic/pets/dog/dog_subtypes.dm | 7 +- .../living/basic/pets/dog/guarddog.bt.json | 5 + .../modules/mob/living/basic/pets/fox.bt.json | 133 ++++ code/modules/mob/living/basic/pets/fox.dm | 17 +- .../mob/living/basic/pets/fox_docile.bt.json | 61 ++ .../basic/pets/gondolas/gondola.bt.json | 8 + .../mob/living/basic/pets/gondolas/gondola.dm | 2 +- .../mob/living/basic/pets/orbie/orbie.bt.json | 100 +++ .../mob/living/basic/pets/orbie/orbie_ai.dm | 95 +-- .../parrot/parrot_ai/_parrot_controller.dm | 37 +- .../parrot_ai/ghost_parrot_controller.dm | 37 +- .../pets/parrot/parrot_ai/parrot.bt.json | 190 +++++ .../parrot/parrot_ai/parrot_ghost.bt.json | 119 +++ .../parrot/parrot_ai/parrot_hoard.bt.json | 118 +++ .../pets/parrot/parrot_ai/parrot_hoarding.dm | 117 ++- .../pets/parrot/parrot_ai/parrot_perching.dm | 81 +- .../pets/parrot/parrot_ai/parroting_action.dm | 42 +- .../pets/parrot/parrot_ai/perching.bt.json | 85 ++ .../living/basic/pets/penguin/penguin.bt.json | 154 ++++ .../mob/living/basic/pets/penguin/penguin.dm | 3 +- .../living/basic/pets/penguin/penguin_ai.dm | 62 +- .../basic/pets/penguin/penguin_baby.bt.json | 118 +++ .../basic/pets/pet_cult/pet_cult.bt.json | 210 +++++ .../living/basic/pets/pet_cult/pet_cult_ai.dm | 208 +---- .../living/basic/pets/pet_cult/pet_cult_bt.dm | 83 ++ .../mob/living/basic/pets/sloth.bt.json | 58 ++ code/modules/mob/living/basic/pets/sloth.dm | 19 +- .../mob/living/basic/revolutionary.bt.json | 17 + .../modules/mob/living/basic/revolutionary.dm | 20 +- .../basic/ruin_defender/blob_of_flesh.dm | 6 +- .../ruin_defender/cybersun_ai_core.bt.json | 67 ++ .../basic/ruin_defender/cybersun_aicore.dm | 16 +- .../basic/ruin_defender/dark_wizard.bt.json | 90 +++ .../living/basic/ruin_defender/dark_wizard.dm | 9 +- .../mob/living/basic/ruin_defender/flesh.dm | 11 +- .../basic/ruin_defender/fleshblob.bt.json | 5 + .../basic/ruin_defender/living_floor.bt.json | 62 ++ .../basic/ruin_defender/living_floor.dm | 16 +- .../basic/ruin_defender/mad_piano.bt.json | 8 + .../living/basic/ruin_defender/mad_piano.dm | 9 +- .../living/basic/ruin_defender/mimic/mimic.dm | 10 +- .../basic/ruin_defender/mimic/mimic_ai.dm | 137 ++-- .../mimic/mimic_animator.bt.json | 153 ++++ .../ruin_defender/mimic/mimic_copy.bt.json | 96 +++ .../ruin_defender/mimic/mimic_crate.bt.json | 14 + .../ruin_defender/mimic/mimic_gun.bt.json | 130 ++++ .../basic/ruin_defender/skeleton.bt.json | 101 +++ .../living/basic/ruin_defender/skeleton.dm | 11 +- .../basic/ruin_defender/stickman.bt.json | 5 + .../living/basic/ruin_defender/stickman.dm | 17 +- .../ruin_defender/stickman_ranged.bt.json | 8 + .../basic/ruin_defender/wizard/wizard.bt.json | 107 +++ .../basic/ruin_defender/wizard/wizard_ai.dm | 39 +- .../living/basic/ruin_defender/zombie.bt.json | 28 + .../mob/living/basic/ruin_defender/zombie.dm | 26 +- .../basic/ruin_defender/zombie_stupid.bt.json | 19 + .../mob/living/basic/slime/ai/behaviours.dm | 67 +- .../mob/living/basic/slime/ai/controller.dm | 29 +- .../mob/living/basic/slime/ai/pet_command.dm | 8 +- .../slime/ai/pet_command_attack_slime.bt.json | 46 ++ .../mob/living/basic/slime/ai/slime.bt.json | 212 +++++ .../mob/living/basic/slime/ai/slime_bt.dm | 87 +++ .../mob/living/basic/slime/ai/subtrees.dm | 54 -- .../modules/mob/living/basic/slime/defense.dm | 2 +- .../mob/living/basic/snails/snail.bt.json | 59 ++ .../mob/living/basic/snails/snail_ai.dm | 57 +- .../mob/living/basic/space_fauna/ant.bt.json | 38 + .../mob/living/basic/space_fauna/ant.dm | 9 +- .../basic/space_fauna/bear/bear.bt.json | 215 ++++++ .../space_fauna/bear/bear_ai_behavior.dm | 26 +- .../basic/space_fauna/bear/bear_ai_subtree.dm | 26 +- .../space_fauna/carp/basic_carp_tree.bt.json | 109 +++ .../basic/space_fauna/carp/carp.bt.json | 8 + .../mob/living/basic/space_fauna/carp/carp.dm | 4 +- .../basic/space_fauna/carp/carp_abilities.dm | 2 +- .../basic/space_fauna/carp/carp_ai_actions.dm | 176 +++-- .../space_fauna/carp/carp_ai_migration.dm | 68 +- .../space_fauna/carp/carp_ai_rift_actions.dm | 211 +++-- .../space_fauna/carp/carp_combat.bt.json | 85 ++ .../space_fauna/carp/carp_controllers.dm | 109 +-- .../basic/space_fauna/carp/carp_flee.bt.json | 63 ++ .../space_fauna/carp/carp_migration.bt.json | 114 +++ .../space_fauna/carp/carp_passive.bt.json | 9 + .../carp/carp_passive_selection.bt.json | 25 + .../basic/space_fauna/carp/carp_pet.bt.json | 10 + .../space_fauna/carp/carp_ranged.bt.json | 8 + .../carp/carp_retaliate_selection.bt.json | 11 + .../carp/carp_target_selection.bt.json | 32 + .../basic/space_fauna/cat_butcherer.bt.json | 5 + .../living/basic/space_fauna/cat_surgeon.dm | 10 +- .../space_fauna/changeling/headslug.bt.json | 5 + .../basic/space_fauna/changeling/headslug.dm | 2 +- .../basic/space_fauna/eyeball/eyeball.bt.json | 183 +++++ .../eyeball/eyeball_ai_behavior.dm | 88 --- .../space_fauna/eyeball/eyeball_ai_subtree.dm | 31 +- .../basic/space_fauna/faithless.bt.json | 137 ++++ .../mob/living/basic/space_fauna/faithless.dm | 13 +- .../basic/space_fauna/garden_gnome.bt.json | 114 +++ .../living/basic/space_fauna/garden_gnome.dm | 9 +- .../living/basic/space_fauna/ghost.bt.json | 14 + .../mob/living/basic/space_fauna/ghost.dm | 8 +- .../basic/space_fauna/hivebot/hivebot.bt.json | 119 +++ .../space_fauna/hivebot/hivebot_behavior.dm | 70 +- .../hivebot/hivebot_mechanic.bt.json | 166 ++++ .../hivebot/hivebot_ranged.bt.json | 130 ++++ .../hivebot/hivebot_ranged_rapid.bt.json | 130 ++++ .../space_fauna/hivebot/hivebot_subtree.dm | 63 +- .../hivebot/relay_to_hive_partner.bt.json | 36 + .../basic/space_fauna/killer_tomato.bt.json | 8 + .../living/basic/space_fauna/killer_tomato.dm | 14 +- .../basic/space_fauna/lightgeist.bt.json | 5 + .../living/basic/space_fauna/lightgeist.dm | 9 +- .../meteor_heart/meteor_heart.bt.json | 73 ++ .../space_fauna/meteor_heart/meteor_heart.dm | 6 +- .../meteor_heart/meteor_heart_ai.dm | 42 +- .../living/basic/space_fauna/morph.bt.json | 5 + .../mob/living/basic/space_fauna/morph.dm | 8 +- .../living/basic/space_fauna/mushroom.bt.json | 103 +++ .../mob/living/basic/space_fauna/mushroom.dm | 15 +- .../paper_wizard/paper_wizard.bt.json | 121 +++ .../space_fauna/paper_wizard/paper_wizard.dm | 41 +- .../space_fauna/regal_rat/regal_rat.bt.json | 98 +++ .../regal_rat/regal_rat_actions.dm | 8 +- .../space_fauna/regal_rat/regal_rat_ai.dm | 29 +- .../mob/living/basic/space_fauna/roro.bt.json | 5 + .../mob/living/basic/space_fauna/roro.dm | 7 +- .../basic/space_fauna/snake/banded.bt.json | 92 +++ .../basic/space_fauna/snake/banded_snake.dm | 18 +- .../basic/space_fauna/snake/snake.bt.json | 92 +++ .../living/basic/space_fauna/snake/snake.dm | 17 +- .../basic/space_fauna/snake/snake_ai.dm | 6 - .../living/basic/space_fauna/spaceman.bt.json | 14 + .../mob/living/basic/space_fauna/spaceman.dm | 8 +- .../spider/giant_spider/giant_spider.bt.json | 133 ++++ .../spider/giant_spider/giant_spider_ai.dm | 56 +- .../giant_spider/giant_spider_pest.bt.json | 150 ++++ .../giant_spider_retaliate.bt.json | 78 ++ .../giant_spider/giant_spider_subtrees.dm | 89 --- .../giant_spider/giant_spider_weak.bt.json | 121 +++ .../spider/spiderlings/spiderling.bt.json | 69 ++ .../spider/spiderlings/spiderling.dm | 18 +- .../spider/young_spider/young_spider.bt.json | 149 ++++ .../spider/young_spider/young_spider.dm | 19 +- .../basic/space_fauna/statue/mannequin.dm | 43 +- .../statue/stares_at_people.bt.json | 44 ++ .../basic/space_fauna/statue/statue.bt.json | 125 +++ .../living/basic/space_fauna/statue/statue.dm | 10 +- .../statue/suspicious_mannequin.bt.json | 114 +++ .../space_fauna/supermatter_spider.bt.json | 8 + .../basic/space_fauna/supermatter_spider.dm | 20 +- .../space_fauna/wumborian_fugu/inflation.dm | 4 +- .../wumborian_fugu/wumborian_ai.dm | 23 +- .../wumborian_fugu/wumborian_fugu.bt.json | 86 +++ .../mob/living/basic/stoats/stoat.bt.json | 123 +++ .../mob/living/basic/stoats/stoat_ai.dm | 24 +- .../mob/living/basic/trader/trader.bt.json | 154 ++++ .../modules/mob/living/basic/trader/trader.dm | 2 +- .../mob/living/basic/trader/trader_ai.dm | 88 +-- code/modules/mob/living/basic/tree.bt.json | 8 + code/modules/mob/living/basic/tree.dm | 12 +- .../mob/living/basic/trooper/burst.bt.json | 9 + .../mob/living/basic/trooper/nanotrasen.dm | 2 +- .../mob/living/basic/trooper/peaceful.bt.json | 138 ++++ .../basic/trooper/peaceful_burst.bt.json | 140 ++++ .../mob/living/basic/trooper/ranged.bt.json | 10 + .../living/basic/trooper/shotgunner.bt.json | 10 + .../mob/living/basic/trooper/trooper.bt.json | 118 +++ .../mob/living/basic/trooper/trooper_ai.dm | 117 ++- .../basic/trooper/trooper_ranged.bt.json | 137 ++++ .../mob/living/basic/turtle/turtle.bt.json | 169 ++++ .../modules/mob/living/basic/turtle/turtle.dm | 12 +- .../mob/living/basic/turtle/turtle_ai.dm | 76 +- .../mob/living/basic/vermin/axolotl.bt.json | 5 + .../mob/living/basic/vermin/axolotl.dm | 2 +- .../mob/living/basic/vermin/butterfly.bt.json | 5 + .../mob/living/basic/vermin/butterfly.dm | 3 +- .../basic/vermin/cockroach/cockroach.bt.json | 80 ++ .../vermin/cockroach/cockroach_aggro.bt.json | 145 ++++ .../basic/vermin/cockroach/cockroach_ai.dm | 54 +- .../cockroach/cockroach_glockroach.bt.json | 143 ++++ .../cockroach/cockroach_mobroach.bt.json | 143 ++++ .../mob/living/basic/vermin/crab.bt.json | 126 +++ code/modules/mob/living/basic/vermin/crab.dm | 11 +- .../mob/living/basic/vermin/eat_cable.bt.json | 58 ++ .../living/basic/vermin/eat_cheese.bt.json | 51 ++ .../mob/living/basic/vermin/frog.bt.json | 60 ++ code/modules/mob/living/basic/vermin/frog.dm | 28 +- .../basic/vermin/frog_engage_target.bt.json | 90 +++ .../mob/living/basic/vermin/lizard.bt.json | 35 + .../modules/mob/living/basic/vermin/lizard.dm | 6 +- .../basic/vermin/mothroach/mothroach.bt.json | 81 ++ .../basic/vermin/mothroach/mothroach_ai.dm | 21 +- .../mob/living/basic/vermin/mouse.bt.json | 189 +++++ code/modules/mob/living/basic/vermin/mouse.dm | 57 +- .../mob/living/basic/vermin/mouse_rat.bt.json | 217 ++++++ .../vermin/play_instrument_on_floor.bt.json | 78 ++ .../mob/living/basic/vermin/space_bat.bt.json | 5 + .../mob/living/basic/vermin/space_bat.dm | 7 +- .../living/basic/vermin/suicide_frog.bt.json | 71 ++ .../mob/living/basic/vermin/trash.bt.json | 61 ++ code/modules/mob/living/living.dm | 11 +- .../simple_animal/bot/bot_announcement.dm | 2 +- .../hostile/megafauna/demonic_frost_miner.dm | 2 +- .../hostile/megafauna/hierophant.dm | 2 +- code/modules/mob/mob.dm | 3 - code/modules/paperwork/paper.dm | 2 + code/modules/projectiles/guns/magic/staff.dm | 2 +- .../guns/magic/wands/wand_rebel.dm | 2 +- .../xenobiology/crossbreeding/_mobs.dm | 8 +- code/modules/unit_tests/_unit_tests.dm | 1 - .../ensure_subtree_operational_datum.dm | 64 -- code/modules/unit_tests/mouse_bite_cable.dm | 35 +- tgstation.dme | 278 +++++-- .../interfaces/BehaviorTreeViewer/index.tsx | 642 +++++++++++++++ .../interfaces/BehaviorTreeViewer/types.ts | 41 + tools/build/build.ts | 14 + tools/build_bt.py | 349 +++++++++ tools/ci/run_server.sh | 1 + tools/deploy.sh | 2 + 1272 files changed, 37899 insertions(+), 13464 deletions(-) create mode 100644 .github/workflows/check_bt_compiled.yml create mode 100644 build/behavior_trees/datums/ai/babies/make_babies.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/bane/bane.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_capricious.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_capricious_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_fearful.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_fearful_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_goon.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_hostile.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ranged.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ranged_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_retaliate_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_skittish.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_skittish_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/talk.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/bots/bot_respond_to_summon.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/bots/bot_salute_authority.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/cursed/cursed.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/dog/dog.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/dog/dog_corgi.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/dog/dog_harassment.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_hunger.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_play_instrument.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/basic_find_target.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/capricious_pick_target.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/climb_tree.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/find_food.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/find_partner.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/find_stealable_object.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/forage_and_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/move_to_and_eat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/move_to_and_hunt.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/move_to_reinforce.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/pick_retaliate_target.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/skittish_and_speak.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/steal_and_flee.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/hauntium/haunted.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/monkey/monkey.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/monkey/monkey_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/monkey/monkey_find_weapon.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/monkey/monkey_serve_food.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/monkey/monkey_shenanigans.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/objects/vending_machines/vending_machine.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/robot_customer/robot_customer.bt.compiled.json create mode 100644 build/behavior_trees/modules/bitrunning/antagonists/netguardian.bt.compiled.json create mode 100644 build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman.bt.compiled.json create mode 100644 build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.compiled.json create mode 100644 build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/alien.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/basic_alien.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/drone.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/lay_alien_egg.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/melee_alien_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/plant_alien_weeds.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/queen.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/ranged_alien_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/sentinel.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/blob_minions/blob_spore.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/blob_minions/blob_zombie.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/blob_minions/blobbernaut.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/boss/thing/thing_aoe.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/boss/thing/thing_boss.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/boss/thing/thing_melee.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/bot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/dedbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/firebot/firebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/mulebot/mulebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/secbot/secbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/vibebot/vibebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/clown/clown.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/cult/constructs/artificer.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/cult/constructs/juggernaut.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/cult/constructs/proteon.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/cult/constructs/wraith.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/cytology/vatbeast.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/bee/bee.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/bee/find_hive.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chick.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chicken.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/deer/deer.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/goat/goat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/pig.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/pony.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/rabbit.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/sheep.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/festivus_pole.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/heretic/raw_prophet.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/heretic/rust_walker.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/heretic/stalker.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/heretic/star_gazer.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/polar_bear/polar.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/wolf/wolf.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/illusion/escape.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/illusion/retaliate.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/human_trap.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/leaper/leaper.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/goliath/goliath.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_brood.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/go_mining.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/heal_injured.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/mook.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/support.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/tendril/tendril.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/watcher/watcher.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/minebots/minebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/minebots/minebot_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/minebots/minebot_mining.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_bread.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_cake.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_find_food.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_haul_food.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/kitten.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/dog/guarddog.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/fox.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/fox_docile.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/gondolas/gondola.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/orbie/orbie.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin_baby.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/sloth.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/revolutionary.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/dark_wizard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/fleshblob.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/living_floor.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/mad_piano.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/skeleton.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/slime/ai/slime.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/snails/snail.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/ant.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/bear/bear.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/cat_butcherer.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/changeling/headslug.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/faithless.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/garden_gnome.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/ghost.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/killer_tomato.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/lightgeist.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/morph.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/mushroom.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/roro.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/snake/banded.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/snake/snake.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spaceman.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/statue/statue.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/supermatter_spider.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/stoats/stoat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trader/trader.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/tree.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/burst.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/peaceful.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/peaceful_burst.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/shotgunner.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/trooper.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/trooper_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/turtle/turtle.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/axolotl.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/butterfly.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/crab.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/eat_cable.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/eat_cheese.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/frog.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/frog_engage_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/lizard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/mothroach/mothroach.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/mouse.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/mouse_rat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/space_bat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/suicide_frog.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/trash.bt.compiled.json create mode 100644 code/__DEFINES/ai/behavior_trees.dm delete mode 100644 code/_globalvars/lists/basic_ai.dm create mode 100644 code/controllers/subsystem/ai_controllers_low_priority.dm delete mode 100644 code/controllers/subsystem/ai_idle_controllers.dm delete mode 100644 code/controllers/subsystem/processing/ai_behaviors.dm delete mode 100644 code/controllers/subsystem/processing/ai_idle_behaviors.dm delete mode 100644 code/controllers/subsystem/unplanned_ai_idle_controllers.dm delete mode 100644 code/controllers/subsystem/unplanned_controllers.dm create mode 100644 code/datums/ai/_ai_bt_composites.dm create mode 100644 code/datums/ai/_ai_bt_decorators.dm create mode 100644 code/datums/ai/_ai_bt_node.dm create mode 100644 code/datums/ai/_ai_bt_subtree.dm delete mode 100644 code/datums/ai/_ai_planning_subtree.dm delete mode 100644 code/datums/ai/babies/babies_subtrees.dm create mode 100644 code/datums/ai/babies/make_babies.bt.json create mode 100644 code/datums/ai/bane/bane.bt.json delete mode 100644 code/datums/ai/bane/bane_behaviors.dm delete mode 100644 code/datums/ai/bane/bane_subtrees.dm create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/call_reinforcements.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/clear_key.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/climb_tree.dm create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/find_flee_location.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/interact_with_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/pick_up_item.dm create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/play_dead.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/pull_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/use_mob_ability.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.json create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/find_food.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/find_parent.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/find_targets_prioritize_traits.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/fishing.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/generic_hunger.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/generic_play_instrument.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/play_with_owners.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.json create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.json create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/tipped_subtree.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm delete mode 100644 code/datums/ai/basic_mobs/pet_commands/fetch.dm create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_bt.dm create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.json delete mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_planning.dm create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.json delete mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_follow_friend.dm delete mode 100644 code/datums/ai/basic_mobs/pet_commands/play_dead.dm create mode 100644 code/datums/ai/basic_mobs/simple_ability.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_melee.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_melee_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_ranged.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_retaliate.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_capricious.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_capricious_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_fearful.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_fearful_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_goon.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_hostile.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_hostile_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_hostile_obstacles.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ranged.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ranged_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ranged_retaliate.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_retaliate.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_retaliate_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_skirmisher.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_skirmisher_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_skittish.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_skittish_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/talk.bt.json create mode 100644 code/datums/ai/basic_mobs/target_sources/_target_source.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/held_items_then_oview.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/held_items_typed.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/mobs_in_oview.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/near_village_humans.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/oview_single_type.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/oview_typed_from_bb_key.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/range_turfs_typecache_visible.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/slime_source.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/turfs_in_oview.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/accessible_cable.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/ally_mob.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/baby_raptor.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/beamable_hydro.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/befriendable_cultist.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/cat_food.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/chargeable_apc.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/conscious_human.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/conscious_mob.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/conscious_snail.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/damaged_eyes.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/damaged_machine.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/dead_mob.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/decorated_donut.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/drillable_ice.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/empty_paper.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/finished_stove.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/food_or_drink.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/goliath_diggable_turf.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/goose_edible.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/huntable.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/huntable_mouse.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/injured_mob.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/injured_raptor.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/legged_conscious_human.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/living_not_dead.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/non_stump_tree.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/pickup_item.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/playable_deer.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/playable_synthesizer.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/pollinatable_hydro.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/raptor_trough.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/slime_food.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/sniffable_hydro.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/stealable_item.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/stocked_beehive.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/treatable_hydro.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/trough_with_ore.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/unbroken_light.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/uncarried_egg.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/unlit_bonfire.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/valid_cat_home.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/valid_kitten.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/walkable_turf.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/water_dispenser.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/working_machine.dm create mode 100644 code/datums/ai/bots/bot_decorators.dm create mode 100644 code/datums/ai/bots/bot_patrol.bt.json create mode 100644 code/datums/ai/bots/bot_respond_to_summon.bt.json create mode 100644 code/datums/ai/bots/bot_salute_authority.bt.json create mode 100644 code/datums/ai/bots/bot_subtrees.dm create mode 100644 code/datums/ai/bt_viewer.dm create mode 100644 code/datums/ai/cursed/cursed.bt.json delete mode 100644 code/datums/ai/cursed/cursed_subtrees.dm create mode 100644 code/datums/ai/dog/dog.bt.json delete mode 100644 code/datums/ai/dog/dog_behaviors.dm create mode 100644 code/datums/ai/dog/dog_bt.dm create mode 100644 code/datums/ai/dog/dog_corgi.bt.json create mode 100644 code/datums/ai/dog/dog_harassment.bt.json delete mode 100644 code/datums/ai/dog/dog_subtrees.dm delete mode 100644 code/datums/ai/generic/find_and_set.dm delete mode 100644 code/datums/ai/generic/generic_behaviors.dm delete mode 100644 code/datums/ai/generic/generic_subtrees.dm create mode 100644 code/datums/ai/generic_behaviors/acquire_injured_target.dm create mode 100644 code/datums/ai/generic_behaviors/acquire_target.dm create mode 100644 code/datums/ai/generic_behaviors/ai_interact.dm create mode 100644 code/datums/ai/generic_behaviors/attack_obstacles.dm create mode 100644 code/datums/ai/generic_behaviors/battle_screech.dm create mode 100644 code/datums/ai/generic_behaviors/break_out_of_object.dm create mode 100644 code/datums/ai/generic_behaviors/break_spine.dm create mode 100644 code/datums/ai/generic_behaviors/cancel_current_plan.dm create mode 100644 code/datums/ai/generic_behaviors/clear_key.dm create mode 100644 code/datums/ai/generic_behaviors/consume.dm create mode 100644 code/datums/ai/generic_behaviors/copy_bb_key.dm create mode 100644 code/datums/ai/generic_behaviors/drag_target.dm create mode 100644 code/datums/ai/generic_behaviors/drop_all_held_items.dm create mode 100644 code/datums/ai/generic_behaviors/express_happiness.dm create mode 100644 code/datums/ai/generic_behaviors/face_target_or_face_initial.dm create mode 100644 code/datums/ai/generic_behaviors/fail.dm create mode 100644 code/datums/ai/generic_behaviors/find_furthest_turf_from_target.dm create mode 100644 code/datums/ai/generic_behaviors/find_nearby.dm create mode 100644 code/datums/ai/generic_behaviors/find_target_facing_turf.dm create mode 100644 code/datums/ai/generic_behaviors/find_unwebbed_turf.dm create mode 100644 code/datums/ai/generic_behaviors/find_valid_teleport_location.dm create mode 100644 code/datums/ai/generic_behaviors/give.dm create mode 100644 code/datums/ai/generic_behaviors/grab_target.dm create mode 100644 code/datums/ai/generic_behaviors/heal_eye_damage.dm create mode 100644 code/datums/ai/generic_behaviors/hunt_target.dm create mode 100644 code/datums/ai/generic_behaviors/issue_pet_command.dm create mode 100644 code/datums/ai/generic_behaviors/keep_playing_instrument.dm create mode 100644 code/datums/ai/generic_behaviors/maintain_distance.dm create mode 100644 code/datums/ai/generic_behaviors/mine_walls.dm create mode 100644 code/datums/ai/generic_behaviors/move_to_target.dm create mode 100644 code/datums/ai/generic_behaviors/perform_emote.dm create mode 100644 code/datums/ai/generic_behaviors/pick_random_ability.dm create mode 100644 code/datums/ai/generic_behaviors/pick_up.dm create mode 100644 code/datums/ai/generic_behaviors/play_instrument.dm create mode 100644 code/datums/ai/generic_behaviors/random_walk.dm create mode 100644 code/datums/ai/generic_behaviors/resist.dm create mode 100644 code/datums/ai/generic_behaviors/run_emote.dm create mode 100644 code/datums/ai/generic_behaviors/set_bb_cooldown.dm create mode 100644 code/datums/ai/generic_behaviors/set_bb_key.dm create mode 100644 code/datums/ai/generic_behaviors/setup_instrument.dm create mode 100644 code/datums/ai/generic_behaviors/speech.dm create mode 100644 code/datums/ai/generic_behaviors/spin_web.dm create mode 100644 code/datums/ai/generic_behaviors/stop_dragging.dm create mode 100644 code/datums/ai/generic_behaviors/stuff_in_disposal.dm create mode 100644 code/datums/ai/generic_behaviors/succeed.dm create mode 100644 code/datums/ai/generic_behaviors/target_retaliate.dm create mode 100644 code/datums/ai/generic_behaviors/use_in_hand.dm create mode 100644 code/datums/ai/generic_behaviors/use_on_object.dm create mode 100644 code/datums/ai/generic_behaviors/virtual_pick_up_item.dm create mode 100644 code/datums/ai/generic_behaviors/wait.dm create mode 100644 code/datums/ai/generic_decorators/ability_available.dm create mode 100644 code/datums/ai/generic_decorators/bb_key_at_least.dm create mode 100644 code/datums/ai/generic_decorators/bb_key_equals.dm create mode 100644 code/datums/ai/generic_decorators/bb_key_list_min_count.dm create mode 100644 code/datums/ai/generic_decorators/bb_key_set.dm create mode 100644 code/datums/ai/generic_decorators/bb_key_true.dm create mode 100644 code/datums/ai/generic_decorators/buckle_target_dangerous.dm create mode 100644 code/datums/ai/generic_decorators/can_see_target.dm create mode 100644 code/datums/ai/generic_decorators/check_cooldown.dm create mode 100644 code/datums/ai/generic_decorators/check_rider_stat.dm create mode 100644 code/datums/ai/generic_decorators/container_attackable.dm create mode 100644 code/datums/ai/generic_decorators/is_at_distance.dm create mode 100644 code/datums/ai/generic_decorators/is_dragging.dm create mode 100644 code/datums/ai/generic_decorators/is_grabbing_target.dm create mode 100644 code/datums/ai/generic_decorators/is_holding_target.dm create mode 100644 code/datums/ai/generic_decorators/is_in_vent.dm create mode 100644 code/datums/ai/generic_decorators/is_target_stunned.dm create mode 100644 code/datums/ai/generic_decorators/item_inside_pawn.dm create mode 100644 code/datums/ai/generic_decorators/key_in_typelist.dm create mode 100644 code/datums/ai/generic_decorators/keys_different_gender.dm create mode 100644 code/datums/ai/generic_decorators/mob_stat_at_least.dm create mode 100644 code/datums/ai/generic_decorators/no_humans_watching.dm create mode 100644 code/datums/ai/generic_decorators/pawn_buckled_to_obj.dm create mode 100644 code/datums/ai/generic_decorators/pawn_contained_in_obj.dm create mode 100644 code/datums/ai/generic_decorators/pawn_farther_than_from_key.dm create mode 100644 code/datums/ai/generic_decorators/pawn_grabbed_by_enemy.dm create mode 100644 code/datums/ai/generic_decorators/pawn_has_gravity.dm create mode 100644 code/datums/ai/generic_decorators/pawn_has_trait_from.dm create mode 100644 code/datums/ai/generic_decorators/pawn_health_below.dm create mode 100644 code/datums/ai/generic_decorators/pawn_inside_mob.dm create mode 100644 code/datums/ai/generic_decorators/pawn_is_restrained.dm create mode 100644 code/datums/ai/generic_decorators/pawn_loc_is_type.dm create mode 100644 code/datums/ai/generic_decorators/pawn_nutrition_below.dm create mode 100644 code/datums/ai/generic_decorators/pawn_same_z_as_key.dm create mode 100644 code/datums/ai/generic_decorators/pawn_turf_has_trait.dm create mode 100644 code/datums/ai/generic_decorators/random_chance.dm create mode 100644 code/datums/ai/generic_decorators/random_chance_from_key.dm create mode 100644 code/datums/ai/generic_decorators/target_has_reagent.dm create mode 100644 code/datums/ai/generic_decorators/target_has_trait.dm create mode 100644 code/datums/ai/generic_decorators/target_health_below_fraction.dm create mode 100644 code/datums/ai/generic_decorators/target_holding_lit_item.dm create mode 100644 code/datums/ai/generic_decorators/target_is_holding_item.dm create mode 100644 code/datums/ai/generic_decorators/target_is_type.dm create mode 100644 code/datums/ai/generic_decorators/target_legcuffed.dm create mode 100644 code/datums/ai/generic_decorators/target_on_ground.dm create mode 100644 code/datums/ai/generic_decorators/true_for_time.dm create mode 100644 code/datums/ai/generic_hunger.bt.json create mode 100644 code/datums/ai/generic_play_instrument.bt.json create mode 100644 code/datums/ai/generic_subtrees/basic_find_target.bt.json create mode 100644 code/datums/ai/generic_subtrees/basic_find_target.dm create mode 100644 code/datums/ai/generic_subtrees/capricious_pick_target.bt.json create mode 100644 code/datums/ai/generic_subtrees/capricious_pick_target.dm create mode 100644 code/datums/ai/generic_subtrees/climb_tree.bt.json create mode 100644 code/datums/ai/generic_subtrees/climb_tree.dm create mode 100644 code/datums/ai/generic_subtrees/find_food.bt.json create mode 100644 code/datums/ai/generic_subtrees/find_food.dm create mode 100644 code/datums/ai/generic_subtrees/find_partner.bt.json create mode 100644 code/datums/ai/generic_subtrees/find_partner.dm create mode 100644 code/datums/ai/generic_subtrees/find_stealable_object.bt.json create mode 100644 code/datums/ai/generic_subtrees/find_stealable_object.dm create mode 100644 code/datums/ai/generic_subtrees/forage_and_retaliate.bt.json create mode 100644 code/datums/ai/generic_subtrees/forage_and_retaliate.dm create mode 100644 code/datums/ai/generic_subtrees/make_babies.dm create mode 100644 code/datums/ai/generic_subtrees/move_to_and_eat.bt.json create mode 100644 code/datums/ai/generic_subtrees/move_to_and_eat.dm create mode 100644 code/datums/ai/generic_subtrees/move_to_and_hunt.bt.json create mode 100644 code/datums/ai/generic_subtrees/move_to_and_hunt.dm create mode 100644 code/datums/ai/generic_subtrees/move_to_reinforce.bt.json create mode 100644 code/datums/ai/generic_subtrees/move_to_reinforce.dm create mode 100644 code/datums/ai/generic_subtrees/pick_retaliate_target.bt.json create mode 100644 code/datums/ai/generic_subtrees/pick_retaliate_target.dm create mode 100644 code/datums/ai/generic_subtrees/random_walk.bt.json create mode 100644 code/datums/ai/generic_subtrees/random_walk.dm create mode 100644 code/datums/ai/generic_subtrees/skittish_and_speak.bt.json create mode 100644 code/datums/ai/generic_subtrees/skittish_and_speak.dm create mode 100644 code/datums/ai/generic_subtrees/steal_and_flee.bt.json create mode 100644 code/datums/ai/generic_subtrees/steal_and_flee.dm create mode 100644 code/datums/ai/hauntium/haunted.bt.json create mode 100644 code/datums/ai/hauntium/haunted_bt_nodes.dm delete mode 100644 code/datums/ai/hauntium/hauntium_subtrees.dm delete mode 100644 code/datums/ai/hunting_behavior/hunting_behaviors.dm delete mode 100644 code/datums/ai/hunting_behavior/hunting_cockroach.dm delete mode 100644 code/datums/ai/hunting_behavior/hunting_corpses.dm delete mode 100644 code/datums/ai/hunting_behavior/hunting_lights.dm delete mode 100644 code/datums/ai/hunting_behavior/hunting_mouse.dm delete mode 100644 code/datums/ai/idle_behaviors/_idle_behavior.dm delete mode 100644 code/datums/ai/idle_behaviors/idle_dog.dm delete mode 100644 code/datums/ai/idle_behaviors/idle_haunted.dm delete mode 100644 code/datums/ai/idle_behaviors/idle_monkey.dm delete mode 100644 code/datums/ai/idle_behaviors/idle_random_walk.dm create mode 100644 code/datums/ai/learn_ai_images/behavior_example.png create mode 100644 code/datums/ai/learn_ai_images/bt_editor_example1.png create mode 100644 code/datums/ai/learn_ai_images/decorator_example.png create mode 100644 code/datums/ai/learn_ai_images/parallel_example.png create mode 100644 code/datums/ai/learn_ai_images/selector_example.png create mode 100644 code/datums/ai/learn_ai_images/sequence_example.png create mode 100644 code/datums/ai/learn_ai_images/subplan_example.png create mode 100644 code/datums/ai/monkey/monkey.bt.json delete mode 100644 code/datums/ai/monkey/monkey_behaviors.dm create mode 100644 code/datums/ai/monkey/monkey_bt_nodes.dm create mode 100644 code/datums/ai/monkey/monkey_combat.bt.json create mode 100644 code/datums/ai/monkey/monkey_find_weapon.bt.json create mode 100644 code/datums/ai/monkey/monkey_serve_food.bt.json create mode 100644 code/datums/ai/monkey/monkey_shenanigans.bt.json create mode 100644 code/datums/ai/objects/vending_machines/vending_machine.bt.json create mode 100644 code/datums/ai/robot_customer/robot_customer.bt.json delete mode 100644 code/datums/ai/robot_customer/robot_customer_subtrees.dm create mode 100644 code/modules/bitrunning/antagonists/netguardian.bt.json create mode 100644 code/modules/bitrunning/virtual_domain/domains/crewman.bt.json create mode 100644 code/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.json create mode 100644 code/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.json create mode 100644 code/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.json create mode 100644 code/modules/mob/living/basic/alien/alien.bt.json create mode 100644 code/modules/mob/living/basic/alien/basic_alien.bt.json create mode 100644 code/modules/mob/living/basic/alien/drone.bt.json create mode 100644 code/modules/mob/living/basic/alien/lay_alien_egg.bt.json create mode 100644 code/modules/mob/living/basic/alien/melee_alien_combat.bt.json create mode 100644 code/modules/mob/living/basic/alien/plant_alien_weeds.bt.json create mode 100644 code/modules/mob/living/basic/alien/queen.bt.json create mode 100644 code/modules/mob/living/basic/alien/ranged_alien_combat.bt.json create mode 100644 code/modules/mob/living/basic/alien/sentinel.bt.json create mode 100644 code/modules/mob/living/basic/blob_minions/blob_spore.bt.json create mode 100644 code/modules/mob/living/basic/blob_minions/blob_zombie.bt.json create mode 100644 code/modules/mob/living/basic/blob_minions/blobbernaut.bt.json create mode 100644 code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.json create mode 100644 code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.json create mode 100644 code/modules/mob/living/basic/boss/thing/thing_aoe.bt.json create mode 100644 code/modules/mob/living/basic/boss/thing/thing_boss.bt.json create mode 100644 code/modules/mob/living/basic/boss/thing/thing_melee.bt.json create mode 100644 code/modules/mob/living/basic/bots/bot.bt.json create mode 100644 code/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.json create mode 100644 code/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/dedbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/ed209/ed209.bt.json create mode 100644 code/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.json create mode 100644 code/modules/mob/living/basic/bots/firebot/firebot.bt.json create mode 100644 code/modules/mob/living/basic/bots/honkbots/honkbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.json create mode 100644 code/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.json create mode 100644 code/modules/mob/living/basic/bots/medbot/medbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.json create mode 100644 code/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.json create mode 100644 code/modules/mob/living/basic/bots/mulebot/mulebot.bt.json create mode 100644 code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.json create mode 100644 code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json create mode 100644 code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json create mode 100644 code/modules/mob/living/basic/bots/secbot/secbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/vibebot/vibebot.bt.json create mode 100644 code/modules/mob/living/basic/clown/clown.bt.json create mode 100644 code/modules/mob/living/basic/cult/constructs/artificer.bt.json create mode 100644 code/modules/mob/living/basic/cult/constructs/juggernaut.bt.json create mode 100644 code/modules/mob/living/basic/cult/constructs/proteon.bt.json create mode 100644 code/modules/mob/living/basic/cult/constructs/wraith.bt.json create mode 100644 code/modules/mob/living/basic/cytology/vatbeast.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/bee/bee.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/bee/bee_bt.dm create mode 100644 code/modules/mob/living/basic/farm_animals/bee/find_hive.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/chicken/chick.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/chicken/chicken.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/cow/cow.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/deer/deer.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/goat/goat.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/goose/goose.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/pig.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/pony.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/rabbit.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/sheep.bt.json create mode 100644 code/modules/mob/living/basic/festivus_pole.bt.json create mode 100644 code/modules/mob/living/basic/heretic/raw_prophet.bt.json create mode 100644 code/modules/mob/living/basic/heretic/rust_walker.bt.json create mode 100644 code/modules/mob/living/basic/heretic/stalker.bt.json create mode 100644 code/modules/mob/living/basic/heretic/star_gazer.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/polar_bear/polar.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/wolf/wolf.bt.json create mode 100644 code/modules/mob/living/basic/illusion/escape.bt.json create mode 100644 code/modules/mob/living/basic/illusion/retaliate.bt.json create mode 100644 code/modules/mob/living/basic/jungle/human_trap.bt.json create mode 100644 code/modules/mob/living/basic/jungle/leaper/leaper.bt.json create mode 100644 code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.json create mode 100644 code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.json create mode 100644 code/modules/mob/living/basic/jungle/seedling/seedling.bt.json create mode 100644 code/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/goliath/goliath.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/legion/legion.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/legion/legion_brood.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/bard.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/go_mining.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/heal_injured.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/mook.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/mook_bt.dm create mode 100644 code/modules/mob/living/basic/lavaland/mook/support.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.json delete mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/tendril/tendril.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/watcher/watcher.bt.json create mode 100644 code/modules/mob/living/basic/minebots/minebot.bt.json create mode 100644 code/modules/mob/living/basic/minebots/minebot_combat.bt.json create mode 100644 code/modules/mob/living/basic/minebots/minebot_mining.bt.json delete mode 100644 code/modules/mob/living/basic/pets/cat/bread_cat_ai.dm create mode 100644 code/modules/mob/living/basic/pets/cat/cat.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_bread.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_bt.dm create mode 100644 code/modules/mob/living/basic/pets/cat/cat_cake.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_find_food.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_haul_food.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/kitten.bt.json delete mode 100644 code/modules/mob/living/basic/pets/cat/kitten_ai.dm create mode 100644 code/modules/mob/living/basic/pets/dog/guarddog.bt.json create mode 100644 code/modules/mob/living/basic/pets/fox.bt.json create mode 100644 code/modules/mob/living/basic/pets/fox_docile.bt.json create mode 100644 code/modules/mob/living/basic/pets/gondolas/gondola.bt.json create mode 100644 code/modules/mob/living/basic/pets/orbie/orbie.bt.json create mode 100644 code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.json create mode 100644 code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.json create mode 100644 code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.json create mode 100644 code/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.json create mode 100644 code/modules/mob/living/basic/pets/penguin/penguin.bt.json create mode 100644 code/modules/mob/living/basic/pets/penguin/penguin_baby.bt.json create mode 100644 code/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.json create mode 100644 code/modules/mob/living/basic/pets/pet_cult/pet_cult_bt.dm create mode 100644 code/modules/mob/living/basic/pets/sloth.bt.json create mode 100644 code/modules/mob/living/basic/revolutionary.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/dark_wizard.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/fleshblob.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/living_floor.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/mad_piano.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/skeleton.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/stickman.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/zombie.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.json create mode 100644 code/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.json create mode 100644 code/modules/mob/living/basic/slime/ai/slime.bt.json create mode 100644 code/modules/mob/living/basic/slime/ai/slime_bt.dm delete mode 100644 code/modules/mob/living/basic/slime/ai/subtrees.dm create mode 100644 code/modules/mob/living/basic/snails/snail.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/ant.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/bear/bear.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/cat_butcherer.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/changeling/headslug.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.json delete mode 100644 code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm create mode 100644 code/modules/mob/living/basic/space_fauna/faithless.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/garden_gnome.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/ghost.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/killer_tomato.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/lightgeist.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/morph.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/mushroom.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/roro.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/snake/banded.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/snake/snake.bt.json delete mode 100644 code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm create mode 100644 code/modules/mob/living/basic/space_fauna/spaceman.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.json delete mode 100644 code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm create mode 100644 code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/statue/statue.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/supermatter_spider.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.json create mode 100644 code/modules/mob/living/basic/stoats/stoat.bt.json create mode 100644 code/modules/mob/living/basic/trader/trader.bt.json create mode 100644 code/modules/mob/living/basic/tree.bt.json create mode 100644 code/modules/mob/living/basic/trooper/burst.bt.json create mode 100644 code/modules/mob/living/basic/trooper/peaceful.bt.json create mode 100644 code/modules/mob/living/basic/trooper/peaceful_burst.bt.json create mode 100644 code/modules/mob/living/basic/trooper/ranged.bt.json create mode 100644 code/modules/mob/living/basic/trooper/shotgunner.bt.json create mode 100644 code/modules/mob/living/basic/trooper/trooper.bt.json create mode 100644 code/modules/mob/living/basic/trooper/trooper_ranged.bt.json create mode 100644 code/modules/mob/living/basic/turtle/turtle.bt.json create mode 100644 code/modules/mob/living/basic/vermin/axolotl.bt.json create mode 100644 code/modules/mob/living/basic/vermin/butterfly.bt.json create mode 100644 code/modules/mob/living/basic/vermin/cockroach/cockroach.bt.json create mode 100644 code/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.json create mode 100644 code/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.json create mode 100644 code/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.json create mode 100644 code/modules/mob/living/basic/vermin/crab.bt.json create mode 100644 code/modules/mob/living/basic/vermin/eat_cable.bt.json create mode 100644 code/modules/mob/living/basic/vermin/eat_cheese.bt.json create mode 100644 code/modules/mob/living/basic/vermin/frog.bt.json create mode 100644 code/modules/mob/living/basic/vermin/frog_engage_target.bt.json create mode 100644 code/modules/mob/living/basic/vermin/lizard.bt.json create mode 100644 code/modules/mob/living/basic/vermin/mothroach/mothroach.bt.json create mode 100644 code/modules/mob/living/basic/vermin/mouse.bt.json create mode 100644 code/modules/mob/living/basic/vermin/mouse_rat.bt.json create mode 100644 code/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.json create mode 100644 code/modules/mob/living/basic/vermin/space_bat.bt.json create mode 100644 code/modules/mob/living/basic/vermin/suicide_frog.bt.json create mode 100644 code/modules/mob/living/basic/vermin/trash.bt.json delete mode 100644 code/modules/unit_tests/ensure_subtree_operational_datum.dm create mode 100644 tgui/packages/tgui/interfaces/BehaviorTreeViewer/index.tsx create mode 100644 tgui/packages/tgui/interfaces/BehaviorTreeViewer/types.ts create mode 100644 tools/build_bt.py diff --git a/.github/workflows/check_bt_compiled.yml b/.github/workflows/check_bt_compiled.yml new file mode 100644 index 000000000000..16cc1f167d55 --- /dev/null +++ b/.github/workflows/check_bt_compiled.yml @@ -0,0 +1,24 @@ +name: Check BT Compiled JSON + +on: + pull_request: + paths: + - "code/**/*.bt.json" + - "code/_generated/behavior_trees/**" + - "code/__DEFINES/**" + - "tools/build_bt.py" + +jobs: + check_bt_compiled: + name: Verify BT compiled JSON is up to date + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.x" + + - name: Check compiled BT files are up to date + run: python tools/build_bt.py --check diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4f751c350b95..a276a21e1a4a 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,7 @@ "Donkie.vscode-tgstation-test-adapter", "anturk.dmi-editor", "esbenp.prettier-vscode", - "biomejs.biome" + "biomejs.biome", + "BehaviorTreeG.behaviortreeg" ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index aa32b74939a5..eda5a2557f5e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,6 +1,13 @@ { "version": "2.0.0", "tasks": [ + { + "type": "shell", + "command": "python tools/build_bt.py", + "group": "build", + "presentation": { "reveal": "silent", "panel": "shared" }, + "label": "Compile BT Trees" + }, { "type": "process", "command": "tools/build/build.sh", @@ -17,7 +24,7 @@ "kind": "build", "isDefault": true }, - "dependsOn": "dm: reparse", + "dependsOn": ["dm: reparse", "Compile BT Trees"], "label": "Build All" }, { diff --git a/build/behavior_trees/datums/ai/babies/make_babies.bt.compiled.json b/build/behavior_trees/datums/ai/babies/make_babies.bt.compiled.json new file mode 100644 index 000000000000..d4788d520ca8 --- /dev/null +++ b/build/behavior_trees/datums/ai/babies/make_babies.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_babies_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/make_babies","target_key":"BB_babies_target","child_types_key":"BB_babies_child"}]}],"observer_abort":3,"key":"BB_babies_target"} diff --git a/build/behavior_trees/datums/ai/bane/bane.bt.compiled.json b/build/behavior_trees/datums/ai/bane/bane.bt.compiled.json new file mode 100644 index 000000000000..4984062567c1 --- /dev/null +++ b/build/behavior_trees/datums/ai/bane/bane.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_bane_batman","required_dist":1},{"type":"/datum/bt_node/ai_behavior/break_spine/bane","target_key":"BB_bane_batman"}]}],"key":"BB_bane_batman"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_bane_batman","target_source":"/datum/target_source/oview_single_type/living_mob","targeting_strategy":"/datum/targeting_strategy/conscious_mob","vision_range":7}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.compiled.json new file mode 100644 index 000000000000..13ab7c355003 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_in_vent","children":[{"type":"/datum/bt_node/ai_behavior/exit_vent"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_entry_vent_target"},{"type":"/datum/bt_node/ai_behavior/enter_vent"}]}],"cooldown_key":"Venting Cooldown","cooldown_duration":"BB_ventcrawl_cooldown"}],"observer_abort":3,"key":"BB_entry_vent_target"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.compiled.json new file mode 100644 index 000000000000..43697a9ca3a2 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/buckle_target_dangerous","children":[{"type":"/datum/bt_node/ai_behavior/break_out_of_object/from_bb","target_key":"BB_basic_mob_escape_target"}]},{"type":"/datum/bt_node/ai_behavior/resist"}]}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_contained_in_obj","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/container_attackable","children":[{"type":"/datum/bt_node/ai_behavior/break_out_of_object/from_bb","target_key":"BB_basic_mob_escape_target"}]},{"type":"/datum/bt_node/ai_behavior/resist"}]}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.compiled.json new file mode 100644 index 000000000000..c5f1fb7297a8 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_contained_in_obj","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.compiled.json new file mode 100644 index 000000000000..d0785f788d0e --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/write_on_paper","paper_key":"BB_SIMPLE_CARRY_ITEM","writing_list_key":"BB_writing_list"}],"observer_abort":1,"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","time_between_perform":4.0,"target_key":"BB_found_paper","can_attack_turfs":true,"can_attack_dense_objects":true},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_paper","required_dist":0}]},{"type":"/datum/bt_node/ai_behavior/pick_up_item_virtual","target_key":"BB_found_paper","storage_key":"BB_SIMPLE_CARRY_ITEM"}]}],"success_policy":1,"failure_policy":1}],"observer_abort":2,"key":"BB_found_paper"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.compiled.json new file mode 100644 index 000000000000..8f225ec19aae --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"swim_alternate_turf","required_dist":0,"finish_on_arrival":true}],"observer_abort":3,"key":"swim_alternate_turf"},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"key_swimmer_cooldown","cooldown_duration":300},{"type":"/datum/bt_node/ai_behavior/wait","duration":300},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"swim_alternate_turf","targeting_strategy":"/datum/targeting_strategy/walkable_turf","target_source":"/datum/target_source/oview_land_turfs"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"swim_alternate_turf","required_dist":0,"finish_on_arrival":true}]},{"type":"/datum/bt_node/ai_behavior/swim_splash"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.compiled.json new file mode 100644 index 000000000000..54f984a39302 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/subplan","children":[{"type":"$bqdqne64"}],"__bindings":{"bqdqne64":{"label":"speech_behavior","default":"/datum/bt_node/ai_behavior/random_speech_blackboard"}},"success_policy":1,"failure_policy":1,"loop_delay":10} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.compiled.json new file mode 100644 index 000000000000..50b8c06f73fb --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/find_flee_location","target_key":"$byk9gqj4","hiding_location_key":"Current Target Hiding Location","destination_key":"BB_flee_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_flee_location","required_dist":0,"finish_on_arrival":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_basic_stop_fleeing","invert":true,"observer_abort":3,"__bindings":{"byk9gqj4":{"label":"target_key","default":"Current Target"}}} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.compiled.json new file mode 100644 index 000000000000..84bba04be6c1 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/find_flee_location","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location","destination_key":"BB_flee_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_flee_location","required_dist":0,"finish_on_arrival":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_basic_stop_fleeing","invert":true,"observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.compiled.json new file mode 100644 index 000000000000..2b42b931e7b0 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"target_key":"Current Target","maximum_distance":9},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.compiled.json new file mode 100644 index 000000000000..421d1326ffb9 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/tipped_reaction"}],"key":"BB_basic_tip_reacting","observer_abort":2} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.compiled.json new file mode 100644 index 000000000000..4e3709d18c0d --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.compiled.json new file mode 100644 index 000000000000..6b9268de1e27 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack/dog","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.compiled.json new file mode 100644 index 000000000000..fcc3c7492416 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/minebot","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.compiled.json new file mode 100644 index 000000000000..3e8b1d36b36e --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.compiled.json new file mode 100644 index 000000000000..114dd3ec43f9 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_home","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/enter_exit_hive"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.compiled.json new file mode 100644 index 000000000000..fdd18ae54188 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_in_typelist","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/keys_different_gender","children":[{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"Seems confused"}],"invert":true,"key_a":"Literally me","key_b":"BB_current_pet_target"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_current_pet_target","combat_mode":false}]},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]}],"key":"BB_current_pet_target","typelist_key":"BB_babies_partner"}],"key":"BB_current_pet_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.compiled.json new file mode 100644 index 000000000000..7c7d8905d139 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/forget_failed_fetches"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/fetch_seek","target_key":"BB_current_pet_target"},{"type":"/datum/bt_node/ai_behavior/pick_up_item_virtual","target_key":"BB_current_pet_target","storage_key":"BB_SIMPLE_CARRY_ITEM"}]}],"key":"BB_current_pet_target","observer_abort":3}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_FETCH_DELIVER_TO","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/pass_item_virtual","delivery_key":"BB_FETCH_DELIVER_TO","storage_key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]}],"key":"BB_FETCH_DELIVER_TO","observer_abort":3}],"key":"BB_SIMPLE_CARRY_ITEM","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/wait"}],"observer_abort":3,"invert":true,"key":"BB_current_pet_target"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.compiled.json new file mode 100644 index 000000000000..7483308b6652 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_current_pet_target","combat_mode":false}]}],"success_policy":1,"failure_policy":0}],"key":"BB_current_pet_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.compiled.json new file mode 100644 index 000000000000..7c56af271fcc --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":true}],"success_policy":1,"failure_policy":0}],"key":"BB_current_pet_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.compiled.json new file mode 100644 index 000000000000..24ce4902b7b4 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/mine_wall","target_key":"BB_current_pet_target"}]}],"key":"BB_current_pet_target","observer_abort":3},{"type":"/datum/bt_node/ai_behavior/find_mineral_wall","target_key":"BB_current_pet_target"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.compiled.json new file mode 100644 index 000000000000..0099a4de932e --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]}],"key":"BB_current_pet_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.compiled.json new file mode 100644 index 000000000000..169d6c0c5df9 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/play_dead"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.compiled.json new file mode 100644 index 000000000000..97379ae486df --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/protect_owner_check"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.compiled.json new file mode 100644 index 000000000000..74c77d8f597c --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/protect_owner_check"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.compiled.json new file mode 100644 index 000000000000..bcea164844b4 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_current_pet_target"}},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.compiled.json new file mode 100644 index 000000000000..0f54e4df546f --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/ai_behavior/wait","duration":0} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.compiled.json new file mode 100644 index 000000000000..e961a273341d --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/swirl_around_target"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_swirl_turf","required_dist":0,"finish_on_arrival":true}]}]}],"success_policy":1,"failure_policy":1}],"key":"BB_swarm_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.compiled.json new file mode 100644 index 000000000000..832baf026542 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"BB_current_pet_target"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.compiled.json new file mode 100644 index 000000000000..53f2f98fbcef --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_pet_active_ability"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability.bt.compiled.json new file mode 100644 index 000000000000..cc9a591751e9 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ability_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_combat.bt.compiled.json new file mode 100644 index 000000000000..d1bc58148b43 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_targeted_spell","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_secondary_spell","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_blink_spell","target_key":"Current Target"}]}],"observer_abort":2,"cooldown_key":"BB_wizard_spell_cooldown","cooldown_duration":10}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":3,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"invert":false,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee.bt.compiled.json new file mode 100644 index 000000000000..a3512dbdaa41 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ability_melee_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee_combat.bt.compiled.json new file mode 100644 index 000000000000..c45a6bf99cc1 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged.bt.compiled.json new file mode 100644 index 000000000000..abae9529636f --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ability_ranged_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.compiled.json new file mode 100644 index 000000000000..3d639c95fad2 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate.bt.compiled.json new file mode 100644 index 000000000000..44dfc11a2042 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ability_retaliate_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.compiled.json new file mode 100644 index 000000000000..8084930f4828 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":3,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"BB_basic_mob_shitlist"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_capricious.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_capricious.bt.compiled.json new file mode 100644 index 000000000000..b1fed32e89ab --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_capricious.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_capricious_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_capricious_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_capricious_combat.bt.compiled.json new file mode 100644 index 000000000000..e6a1c4c95d0c --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_capricious_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"BB_basic_mob_shitlist"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/capricious_pick_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_fearful.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_fearful.bt.compiled.json new file mode 100644 index 000000000000..fe0b4f4783e2 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_fearful.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/simple_fearful_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_fearful_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_fearful_combat.bt.compiled.json new file mode 100644 index 000000000000..335e6be98891 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_fearful_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/subtree/random_walk"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_goon.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_goon.bt.compiled.json new file mode 100644 index 000000000000..339d5cc36873 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_goon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_hostile.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile.bt.compiled.json new file mode 100644 index 000000000000..c99cae59bca1 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_hostile_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat.bt.compiled.json new file mode 100644 index 000000000000..98eedae80588 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"$bbrbyj7y"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk","bindings":{"bf07i8ep":"$bp3p5vvb"}}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"__bindings":{"bbrbyj7y":{"label":"combat_speech_behavior","default":""},"bp3p5vvb":{"label":"walk_chance","default":25}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.compiled.json new file mode 100644 index 000000000000..74f3e952fc8c --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":0,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]},{"type":"$bhvqd9fh"}],"__bindings":{"bhvqd9fh":{"label":"speech loop subtree","default":"/datum/bt_node/subtree"}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles.bt.compiled.json new file mode 100644 index 000000000000..70dffa4e2a65 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/subtree/pick_retaliate_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.compiled.json new file mode 100644 index 000000000000..ace2dc0e2dff --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":0,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false},{"type":"$b95z0f0c"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"$by235tlw"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"$bqul7l8t"}],"__bindings":{"b95z0f0c":{"label":"while_attacking_subtree","default":""},"by235tlw":{"label":"cant_attack_subtree","default":""},"bqul7l8t":{"label":"speech_subtree","default":"/datum/bt_node/subtree"}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ranged.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged.bt.compiled.json new file mode 100644 index 000000000000..39ce20f83c62 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ranged_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_combat.bt.compiled.json new file mode 100644 index 000000000000..d5b107b7d303 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","time_between_perform":6.0,"target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target","approach_movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"$bsjjwub2"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"__bindings":{"bsjjwub2":{"label":"idle_behavior","default":"/datum/bt_node/subtree/random_walk"}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate.bt.compiled.json new file mode 100644 index 000000000000..ab4d970ead90 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ranged_retaliate_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.compiled.json new file mode 100644 index 000000000000..8b83bd02e122 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate.bt.compiled.json new file mode 100644 index 000000000000..15c3371800ee --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_retaliate_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate_combat.bt.compiled.json new file mode 100644 index 000000000000..69e93aa3570b --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":"$b2jvnm5d"}],"__bindings":{"b2jvnm5d":{"label":"check_faction","default":0}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher.bt.compiled.json new file mode 100644 index 000000000000..7d76a8bf6c31 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_skirmisher_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher_combat.bt.compiled.json new file mode 100644 index 000000000000..44403a4ba802 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_skittish.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_skittish.bt.compiled.json new file mode 100644 index 000000000000..1232523f9f76 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_skittish.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/simple_skittish_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_skittish_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_skittish_combat.bt.compiled.json new file mode 100644 index 000000000000..a57400770d32 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_skittish_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/subtree/random_walk"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/talk.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/talk.bt.compiled.json new file mode 100644 index 000000000000..7f8b669005c7 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/talk.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/random_speech_loop"} diff --git a/build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json b/build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json new file mode 100644 index 000000000000..a192bb7c18fd --- /dev/null +++ b/build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/bot_mode_flag","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_next_beacon_target","target_key":"beacon_target"}],"key":"previous_beacon_target"},{"type":"/datum/bt_node/ai_behavior/find_first_beacon_target","target_key":"beacon_target"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"beacon_target","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/arrive_at_beacon","target_key":"beacon_target"}]}],"key":"beacon_target"}]}],"observer_abort":2,"flag":2}],"observer_abort":2,"cooldown_key":"bot_beacon_cooldown"},{"type":"/datum/bt_node/ai_behavior/wait","duration":10}]} diff --git a/build/behavior_trees/datums/ai/bots/bot_respond_to_summon.bt.compiled.json b/build/behavior_trees/datums/ai/bots/bot_respond_to_summon.bt.compiled.json new file mode 100644 index 000000000000..7c68c683504c --- /dev/null +++ b/build/behavior_trees/datums/ai/bots/bot_respond_to_summon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"bot_summon_target","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/complete_summon_travel","target_key":"bot_summon_target"}]}],"key":"bot_summon_target"} diff --git a/build/behavior_trees/datums/ai/bots/bot_salute_authority.bt.compiled.json b/build/behavior_trees/datums/ai/bots/bot_salute_authority.bt.compiled.json new file mode 100644 index 000000000000..d2af26740eb9 --- /dev/null +++ b/build/behavior_trees/datums/ai/bots/bot_salute_authority.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_valid_authority","target_key":"salute_target"},{"type":"/datum/bt_node/ai_behavior/salute_authority","target_key":"salute_target","salute_keys":"salute_messages"}]}],"cooldown_key":"salute_cooldown","cooldown_duration":600} diff --git a/build/behavior_trees/datums/ai/cursed/cursed.bt.compiled.json b/build/behavior_trees/datums/ai/cursed/cursed.bt.compiled.json new file mode 100644 index 000000000000..c5851cf082c8 --- /dev/null +++ b/build/behavior_trees/datums/ai/cursed/cursed.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/throw_attack/haunted","target_key":"BB_haunt_target","throw_count_key":"BB_haunted_throw_attempt_count","haunt_list_key":"BB_to_haunt_list"}],"success_policy":1,"failure_policy":0,"loop_delay":20},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_haunt_target","required_dist":3,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"BB_haunt_target"},{"type":"/datum/bt_node/ai_behavior/idle_ghost_item"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_haunt_target","target_source":"/datum/target_source/oview_single_type/carbon_mob","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":7}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/dog/dog.bt.compiled.json b/build/behavior_trees/datums/ai/dog/dog.bt.compiled.json new file mode 100644 index 000000000000..fc346204445e --- /dev/null +++ b/build/behavior_trees/datums/ai/dog/dog.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/dog_harassment"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_dog"}],"success_policy":1,"failure_policy":0},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/datums/ai/dog/dog_corgi.bt.compiled.json b/build/behavior_trees/datums/ai/dog/dog_corgi.bt.compiled.json new file mode 100644 index 000000000000..475f8dcb8367 --- /dev/null +++ b/build/behavior_trees/datums/ai/dog/dog_corgi.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/dog_harassment"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_dog"}],"success_policy":1,"failure_policy":0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/datums/ai/dog/dog_harassment.bt.compiled.json b/build/behavior_trees/datums/ai/dog/dog_harassment.bt.compiled.json new file mode 100644 index 000000000000..00d36999427f --- /dev/null +++ b/build/behavior_trees/datums/ai/dog/dog_harassment.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_hated_dog_target","target_key":"BB_DOG_HARASS_TARGET","targeting_strategy":"BB_pet_targeting"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_DOG_HARASS_TARGET","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack/dog","target_key":"BB_DOG_HARASS_TARGET","targeting_strategy":"BB_pet_targeting","hiding_location_key":"Current Target Hiding Location"}]}],"chance":0.1} diff --git a/build/behavior_trees/datums/ai/generic_hunger.bt.compiled.json b/build/behavior_trees/datums/ai/generic_hunger.bt.compiled.json new file mode 100644 index 000000000000..06cf4dc331d6 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_hunger.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/consume","target_key":"bb_food_target","hunger_timer_key":"BB_NEXT_HUNGRY"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"bb_food_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/pick_up","target_key":"bb_food_target","drop_held":true}]}]}],"key":"bb_food_target"} diff --git a/build/behavior_trees/datums/ai/generic_play_instrument.bt.compiled.json b/build/behavior_trees/datums/ai/generic_play_instrument.bt.compiled.json new file mode 100644 index 000000000000..c3b4514f642d --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_play_instrument.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/is_holding_target","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_SONG_INSTRUMENT"},{"type":"/datum/bt_node/ai_behavior/pick_up","target_key":"BB_SONG_INSTRUMENT"}]}],"observer_abort":2,"invert":true,"key":"BB_SONG_INSTRUMENT"}],"invert":true,"key":"Has its instrument up its ass"},{"type":"/datum/bt_node/ai_behavior/keep_playing_instrument","song_instrument_key":"BB_SONG_INSTRUMENT"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/setup_instrument","song_instrument_key":"BB_SONG_INSTRUMENT","song_lines_key":"song_lines"},{"type":"/datum/bt_node/ai_behavior/play_instrument","song_instrument_key":"BB_SONG_INSTRUMENT","volume":"$bulu13xf"}]}]}],"key":"BB_SONG_INSTRUMENT","__bindings":{"bulu13xf":{"label":"volume","default":50}}} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/basic_find_target.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/basic_find_target.bt.compiled.json new file mode 100644 index 000000000000..c0c7d81a31e7 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/basic_find_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/capricious_pick_target.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/capricious_pick_target.bt.compiled.json new file mode 100644 index 000000000000..700ad03cb5d2 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/capricious_pick_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/capricious_retaliate","targeting_strategy":"targeting_strategy","ignore_faction":true},{"type":"/datum/bt_node/subtree/pick_retaliate_target"}]} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/climb_tree.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/climb_tree.bt.compiled.json new file mode 100644 index 000000000000..8fc223ff1b82 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/climb_tree.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_climbed_tree"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_climbed_tree"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_climbed_tree"}]}],"observer_abort":3,"key":"BB_climbed_tree"}],"observer_abort":2,"cooldown_key":"Tree Climbing Cooldown","cooldown_duration":"$bb0lcfol","__bindings":{"bb0lcfol":{"label":"tree_climbing_cooldown","default":300}}} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/find_food.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/find_food.bt.compiled.json new file mode 100644 index 000000000000..ab6b56f72f5f --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/find_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"BB_TARGET_FOOD","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_typed/from_bb_key/basic_foods","revalidation_mode":1}],"observer_abort":1,"cooldown_key":"BB_next_food_eat"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/find_partner.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/find_partner.bt.compiled.json new file mode 100644 index 000000000000..1edff728d4e1 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/find_partner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/find_partner","target_key":"BB_babies_target","partner_types_key":"BB_babies_partner","child_types_key":"BB_babies_child"}],"cooldown_key":"BB_partner_search_timeout"}],"key":"BB_breed_ready"}],"observer_abort":2,"key":"can we fuck?"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/find_stealable_object.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/find_stealable_object.bt.compiled.json new file mode 100644 index 000000000000..d19c824e1cb9 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/find_stealable_object.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_dragging","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/random_chance_from_key","children":[{"type":"/datum/bt_node/ai_behavior/stop_dragging"}],"chance_key":"guilty_concious_rate"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}]},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/random_chance_from_key","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_WANTS_TO_COMMIT_THEFT","value":true}],"chance_key":"steal_chance"}],"invert":true,"key":"BB_WANTS_TO_COMMIT_THEFT"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/succeed"}],"invert":true,"key":"BB_WANTS_TO_COMMIT_THEFT"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"item_to_steal","targeting_strategy":"/datum/targeting_strategy/pickup_item/stealable_item","target_source":"/datum/target_source/oview","must_be_reachable":true},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_WANTS_TO_COMMIT_THEFT","value":false}]}]} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/forage_and_retaliate.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/forage_and_retaliate.bt.compiled.json new file mode 100644 index 000000000000..2b9df392144c --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/forage_and_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_basic_mob_shitlist","observer_abort":1},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/random_walk"}],"key":"BB_disable_idle","invert":true}]},{"type":"$z4n9bk7p"},{"type":"$q8w3rtv1"}],"__bindings":{"q8w3rtv1":{"label":"Food Finder","default":"/datum/bt_node/subtree/find_food"},"z4n9bk7p":{"label":"Retaliate Manager","default":"/datum/bt_node/subtree/capricious_pick_target"}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_eat.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_eat.bt.compiled.json new file mode 100644 index 000000000000..1eb1dcf2626c --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_eat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_TARGET_FOOD","required_dist":1},{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"eats up happily!"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_TARGET_FOOD"}]}],"observer_abort":3,"key":"BB_TARGET_FOOD"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_hunt.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_hunt.bt.compiled.json new file mode 100644 index 000000000000..b1a430d4014d --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_hunt.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"$b3y599q4","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"$brrasnah","always_reset_target":"$b3cnse9r","hunt_cooldown":"$bqwjf4id","cooldown_key":"$bm3y5m55","behavior_combat_mode":"$bd1towgc"}]}],"observer_abort":3,"key":"$bvtz06kb","__bindings":{"bvtz06kb":{"label":"hunting_target","default":"BB_low_priority_hunting_target"},"b3y599q4":{"label":"hunting_target","default":"BB_low_priority_hunting_target"},"brrasnah":{"label":"hunting_target","default":"BB_low_priority_hunting_target"},"bqwjf4id":{"label":"hunt_cooldown","default":50},"bm3y5m55":{"label":"cooldown_key","default":""},"bd1towgc":{"label":"behavior_combat_mode","default":1},"b3cnse9r":{"label":"always_reset_target","default":1}}} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/move_to_reinforce.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/move_to_reinforce.bt.compiled.json new file mode 100644 index 000000000000..486a465bbc03 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/move_to_reinforce.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_basic_mob_reinforcement_target","required_dist":0,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_basic_mob_reinforcement_target"}]}],"observer_abort":2,"key":"BB_basic_mob_reinforcement_target"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/pick_retaliate_target.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/pick_retaliate_target.bt.compiled.json new file mode 100644 index 000000000000..ad3e6b31cb16 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/pick_retaliate_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json new file mode 100644 index 000000000000..82e2292b6d1c --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/true_for_time","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk","walk_chance":"$bf07i8ep"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}],"duration":50,"__bindings":{"bf07i8ep":{"label":"walk_chance","default":25}}} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/skittish_and_speak.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/skittish_and_speak.bt.compiled.json new file mode 100644 index 000000000000..908394cb4b42 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/skittish_and_speak.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/subtree/simple_skittish_combat"}],"success_policy":1,"failure_policy":1},{"type":"$f17iiafz"}],"__bindings":{"f17iiafz":{"label":"Speech Behavior","default":"/datum/bt_node/ai_behavior/random_speech_blackboard"}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/steal_and_flee.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/steal_and_flee.bt.compiled.json new file mode 100644 index 000000000000..65ded70c2dee --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/steal_and_flee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"item_to_steal"},{"type":"/datum/bt_node/ai_behavior/drag_target","target_key":"item_to_steal"},{"type":"/datum/bt_node/ai_behavior/copy_bb_key","dest_key":"BB_LAST_STOLEN_ITEM","source_key":"item_to_steal"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"item_to_steal"}]}],"observer_abort":3,"key":"item_to_steal"} diff --git a/build/behavior_trees/datums/ai/hauntium/haunted.bt.compiled.json b/build/behavior_trees/datums/ai/hauntium/haunted.bt.compiled.json new file mode 100644 index 000000000000..c661f45cbaa2 --- /dev/null +++ b/build/behavior_trees/datums/ai/hauntium/haunted.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/item_being_held","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/wait","duration":0}],"key":"BB_likes_equipper","observer_abort":1},{"type":"/datum/bt_node/ai_behavior/item_escape_grasp"}]}],"observer_abort":3},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/throw_attack/haunted","target_key":"BB_haunt_target","throw_count_key":"BB_haunted_throw_attempt_count","haunt_list_key":"BB_to_haunt_list"}],"success_policy":1,"failure_policy":0,"loop_delay":20},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_haunt_target","required_dist":3,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"BB_haunt_target"},{"type":"/datum/bt_node/ai_behavior/idle_ghost_item"}]},{"type":"/datum/bt_node/ai_behavior/pick_haunt_target","target_key":"BB_haunt_target","haunt_list_key":"BB_to_haunt_list"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/datums/ai/monkey/monkey.bt.compiled.json b/build/behavior_trees/datums/ai/monkey/monkey.bt.compiled.json new file mode 100644 index 000000000000..6a0c024cb9af --- /dev/null +++ b/build/behavior_trees/datums/ai/monkey/monkey.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/monkey_combat"},{"type":"/datum/bt_node/subtree/monkey_serve_food"},{"type":"/datum/bt_node/subtree/generic_hunger"},{"type":"/datum/bt_node/subtree/generic_play_instrument"},{"type":"/datum/bt_node/subtree/monkey_shenanigans"},{"type":"/datum/bt_node/ai_behavior/monkey_idle"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/monkey_set_combat_target","attack_target_key":"Current Target","enemies_key":"BB_monkey_enemies"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_monkey_current_press_target","targeting_strategy":"/datum/targeting_strategy/anything","target_source":"/datum/target_source/monkey_press_target","vision_range":2}],"key":"Monkeys wants to press something"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"Monkeys wants to press something","value":true}],"chance":0.2}],"invert":true,"key":"Monkeys wants to press something"},{"type":"/datum/bt_node/ai_behavior/succeed"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_is_holding_item","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_monkey_current_give_target","targeting_strategy":"/datum/targeting_strategy/anything","target_source":"/datum/target_source/oview_single_type/human_mob","vision_range":2}],"key":"Literally me"},{"type":"/datum/bt_node/decorator/target_is_holding_item","children":[{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_monkey_current_give_target"}],"invert":true,"key":"Literally me"},{"type":"/datum/bt_node/ai_behavior/succeed"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_holding_target","children":[{"type":"/datum/bt_node/ai_behavior/succeed"}],"key":"BB_SONG_INSTRUMENT"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_SONG_INSTRUMENT","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/held_items_typed/instrument"}],"key":"BB_monkey_tamed"},{"type":"/datum/bt_node/ai_behavior/succeed"}]},{"type":"/datum/bt_node/decorator/pawn_nutrition_below","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"bb_food_target","targeting_strategy":"/datum/targeting_strategy/pickup_item/food_or_drink","target_source":"/datum/target_source/held_items_then_oview","vision_range":2}],"cooldown_key":"BB_NEXT_HUNGRY"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon","target_key":"BB_monkey_pickuptarget","target_source":"/datum/target_source/monkey_weapon_upgrade","targeting_strategy":"/datum/targeting_strategy/monkey_weapon_upgrade","vision_range":5}],"invert":true,"key":"BB_monkey_tamed"}]}]}],"key":"Current Target","invert":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon","target_key":"BB_monkey_pickuptarget","target_source":"/datum/target_source/monkey_weapon_upgrade","targeting_strategy":"/datum/targeting_strategy/monkey_weapon_upgrade","vision_range":5}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/monkey/monkey_combat.bt.compiled.json b/build/behavior_trees/datums/ai/monkey/monkey_combat.bt.compiled.json new file mode 100644 index 000000000000..391d5f70aa56 --- /dev/null +++ b/build/behavior_trees/datums/ai/monkey/monkey_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"Current Target"}],"observer_abort":2,"invert":true,"target_key":"Current Target","maximum_distance":10,"require_reach":false},{"type":"/datum/bt_node/decorator/pawn_health_below","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"health_threshold":40},{"type":"/datum/bt_node/subtree/monkey_find_weapon"},{"type":"/datum/bt_node/decorator/mob_stat_at_least","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_monkey_target_disposal","target_source":"/datum/target_source/oview_single_type/disposal_unit","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":9},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/grab_target","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_target_disposal","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/stuff_in_disposal","attack_target_key":"Current Target","disposal_target_key":"BB_monkey_target_disposal"},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"Current Target"}]},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"Current Target"}]}],"observer_abort":2,"invert":false,"key":"Current Target","min_stat":1},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/monkey_attack_mob","target_key":"Current Target"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/recruit_monkeys","target_key":"Current Target"}],"cooldown_key":"BB_monkey_recruit_cooldown","cooldown_duration":10}],"success_policy":1,"failure_policy":1,"loop_delay":50},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/battle_screech/monkey"}],"chance":0.25}],"cooldown_key":"Battle Screech Cooldown","cooldown_duration":5}],"success_policy":1,"failure_policy":1,"loop_delay":10},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon","time_between_perform":0,"target_key":"BB_monkey_pickuptarget","targeting_strategy":"/datum/targeting_strategy/monkey_weapon_upgrade","target_source":"/datum/target_source/monkey_weapon_upgrade","vision_range":5}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":2.0,"finish_on_primary":true}],"key":"Current Target","observer_abort":2} diff --git a/build/behavior_trees/datums/ai/monkey/monkey_find_weapon.bt.compiled.json b/build/behavior_trees/datums/ai/monkey/monkey_find_weapon.bt.compiled.json new file mode 100644 index 000000000000..a9d52200befd --- /dev/null +++ b/build/behavior_trees/datums/ai/monkey/monkey_find_weapon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_pickuptarget","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/monkey_equip/pickpocket","target_key":"BB_monkey_pickuptarget"}],"key":"BB_monkey_pickup_is_pickpocket"},{"type":"/datum/bt_node/ai_behavior/monkey_equip/ground","target_key":"BB_monkey_pickuptarget"}]}]}],"observer_abort":3,"key":"BB_monkey_pickuptarget"} diff --git a/build/behavior_trees/datums/ai/monkey/monkey_serve_food.bt.compiled.json b/build/behavior_trees/datums/ai/monkey/monkey_serve_food.bt.compiled.json new file mode 100644 index 000000000000..9fe06ea5404a --- /dev/null +++ b/build/behavior_trees/datums/ai/monkey/monkey_serve_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/monkey_find_patrons","patrons_key":"BB_monkey_patrons_nearby","give_target_key":"BB_monkey_current_give_target"}],"cooldown_key":"Monkeys can look for patron","cooldown_duration":10,"lock_on_succeed":false},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_holding_target","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_current_give_target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/ai_behavior/give","target_key":"BB_monkey_current_give_target"}]}],"key":"BB_monkey_current_served_item"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_monkey_current_served_item","targeting_strategy":"/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks","target_source":"/datum/target_source/held_items_then_oview","vision_range":2,"must_be_reachable":true},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_current_served_item","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/ai_behavior/pick_up","target_key":"BB_monkey_current_served_item","drop_held":true}]}],"invert":false,"key":"BB_monkey_current_served_item"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_monkey_current_served_item","targeting_strategy":"/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks","target_source":"/datum/target_source/held_items_then_oview","vision_range":2,"must_be_reachable":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]}]}],"key":"BB_monkey_tamed"} diff --git a/build/behavior_trees/datums/ai/monkey/monkey_shenanigans.bt.compiled.json b/build/behavior_trees/datums/ai/monkey/monkey_shenanigans.bt.compiled.json new file mode 100644 index 000000000000..e8beeaa46257 --- /dev/null +++ b/build/behavior_trees/datums/ai/monkey/monkey_shenanigans.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_is_holding_item","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/use_in_hand"}],"chance":0.05}],"key":"Literally me"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_current_press_target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/ai_behavior/use_on_object","target_key":"BB_monkey_current_press_target"},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_monkey_current_press_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"Monkeys wants to press something"}]}],"key":"BB_monkey_current_press_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/random_chance_from_key","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_current_give_target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/ai_behavior/give","target_key":"BB_monkey_current_give_target"}]}],"chance_key":"BB_monkey_give_chance"}],"key":"BB_monkey_current_give_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/monkey_find_weapon"}],"key":"BB_monkey_tamed"}]} diff --git a/build/behavior_trees/datums/ai/objects/vending_machines/vending_machine.bt.compiled.json b/build/behavior_trees/datums/ai/objects/vending_machines/vending_machine.bt.compiled.json new file mode 100644 index 000000000000..dde504f3c336 --- /dev/null +++ b/build/behavior_trees/datums/ai/objects/vending_machines/vending_machine.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/vending_is_tilted","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/vendor_rise_up"}],"cooldown_key":"BB_vending_untilt_cooldown"}]},{"type":"/datum/bt_node/decorator/vending_is_tilted","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_vendor_target","target_key":"BB_vending_current_target","vision_range":7},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_vending_current_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/vendor_crush","target_key":"BB_vending_current_target"}]}],"cooldown_key":"BB_vending_tilt_cooldown"}],"invert":true}]} diff --git a/build/behavior_trees/datums/ai/robot_customer/robot_customer.bt.compiled.json b/build/behavior_trees/datums/ai/robot_customer/robot_customer.bt.compiled.json new file mode 100644 index 000000000000..55b0e771a316 --- /dev/null +++ b/build/behavior_trees/datums/ai/robot_customer/robot_customer.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/robot_customer/find_exit_portal"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_customer_exit_portal","required_dist":0},{"type":"/datum/bt_node/ai_behavior/robot_customer/leave_venue"}]}],"key":"BB_customer_leaving","observer_abort":2},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_customer_current_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/break_spine","target_key":"BB_customer_current_target"}]}],"key":"BB_customer_current_target","observer_abort":2},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/robot_customer/find_seat"}],"cooldown_key":"BB_customer_find_seat_cooldown","cooldown_duration":80,"lock_on_succeed":false}],"key":"BB_customer_my_seat","invert":true},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_customer_my_seat","required_dist":0},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/robot_customer/order_food"}],"key":"BB_customer_current_order","invert":true},{"type":"/datum/bt_node/ai_behavior/robot_customer/wait_for_food"}]}]}],"key":"BB_customer_my_seat"}]} diff --git a/build/behavior_trees/modules/bitrunning/antagonists/netguardian.bt.compiled.json b/build/behavior_trees/modules/bitrunning/antagonists/netguardian.bt.compiled.json new file mode 100644 index 000000000000..c43cd3695dc4 --- /dev/null +++ b/build/behavior_trees/modules/bitrunning/antagonists/netguardian.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"netguardian_rocket","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/avoid_friendly_fire","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","time_between_perform":10},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman.bt.compiled.json b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman.bt.compiled.json new file mode 100644 index 000000000000..694c63452df9 --- /dev/null +++ b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.compiled.json b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.compiled.json new file mode 100644 index 000000000000..644c7eefb550 --- /dev/null +++ b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.compiled.json b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.compiled.json new file mode 100644 index 000000000000..c05b838e02b1 --- /dev/null +++ b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.compiled.json b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.compiled.json new file mode 100644 index 000000000000..48e54e5e9c8d --- /dev/null +++ b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/alien.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/alien.bt.compiled.json new file mode 100644 index 000000000000..55d0d44b70e5 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/alien.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_alien","bindings":{"buewuqr6":"/datum/bt_node/subtree/melee_alien_combat","bkzbx73d":"/datum/bt_node/subtree/lay_alien_egg"}} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/basic_alien.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/basic_alien.bt.compiled.json new file mode 100644 index 000000000000..1b0824cb9919 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/basic_alien.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"$buewuqr6"}],"observer_abort":3,"key":"Current Target"},{"type":"$bkzbx73d"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"__bindings":{"buewuqr6":{"label":"Combat Subtree","default":"/datum/bt_node/subtree"},"bkzbx73d":{"label":"Idle Behavior","default":"/datum/bt_node/subtree/random_walk"}}} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/drone.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/drone.bt.compiled.json new file mode 100644 index 000000000000..d3adf7228ef9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/drone.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_alien","bindings":{"buewuqr6":"/datum/bt_node/subtree/melee_alien_combat","bkzbx73d":"/datum/bt_node/ai_behavior/plant_alien_weeds"}} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/lay_alien_egg.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/lay_alien_egg.bt.compiled.json new file mode 100644 index 000000000000..18caaa98520b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/lay_alien_egg.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/lay_alien_egg"}],"observer_abort":2,"cooldown_key":"BB_ALIEN_PLANT_COOLDOWN","cooldown_duration":300} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/melee_alien_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/melee_alien_combat.bt.compiled.json new file mode 100644 index 000000000000..7fe5ec46d8ae --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/melee_alien_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/plant_alien_weeds.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/plant_alien_weeds.bt.compiled.json new file mode 100644 index 000000000000..17a0c23cebbe --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/plant_alien_weeds.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/plant_alien_weeds"}],"observer_abort":2,"cooldown_key":"BB_ALIEN_PLANT_COOLDOWN","cooldown_duration":300},{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/queen.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/queen.bt.compiled.json new file mode 100644 index 000000000000..eaf72e8cb15b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/queen.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_alien","bindings":{"buewuqr6":"/datum/bt_node/subtree/ranged_alien_combat","bkzbx73d":"/datum/bt_node/ai_behavior/lay_alien_egg"}} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/ranged_alien_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/ranged_alien_combat.bt.compiled.json new file mode 100644 index 000000000000..2924f00ec158 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/ranged_alien_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","time_between_perform":30,"target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","avoid_friendly_fire":true}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target","approach_movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/sentinel.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/sentinel.bt.compiled.json new file mode 100644 index 000000000000..65bf067bf1ce --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/sentinel.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_alien","bindings":{"buewuqr6":"/datum/bt_node/subtree/ranged_alien_combat"}} diff --git a/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_spore.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_spore.bt.compiled.json new file mode 100644 index 000000000000..9e00321a5439 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_spore.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/move_to_and_hunt","bindings":{"bvtz06kb":"BB_current_hunting_target","b3y599q4":"BB_current_hunting_target","brrasnah":"BB_current_hunting_target"}},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_travel_destination","required_dist":0},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_travel_destination"}]}],"observer_abort":3,"key":"BB_travel_destination"},{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","targeting_strategy":"/datum/targeting_strategy/dead_mob","target_source":"/datum/target_source/oview_single_type/human_mob"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_zombie.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_zombie.bt.compiled.json new file mode 100644 index 000000000000..04e9fa2cdc21 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_zombie.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_travel_destination","required_dist":0},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_travel_destination"}]}],"observer_abort":3,"key":"BB_travel_destination"},{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/blob_minions/blobbernaut.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/blob_minions/blobbernaut.bt.compiled.json new file mode 100644 index 000000000000..2d83f85aebc4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/blob_minions/blobbernaut.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.compiled.json new file mode 100644 index 000000000000..89cd89c9ea74 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/blood_drunk_miner_combat"},{"type":"/datum/bt_node/ai_behavior/wait","duration":0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.compiled.json new file mode 100644 index 000000000000..d1701ec34c49 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/mob_stat_at_least","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_bdm_kinetic_accelerator_ability","target_key":"Current Target","maximum_distance":3},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_bdm_transform_weapon_ability","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}]}],"key":"Current Target"},{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_bdm_dash_attack_ability","target_key":"Current Target"}],"invert":false,"target_key":"Current Target","min_distance":3},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_bdm_transform_weapon_ability","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}]}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"} diff --git a/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_aoe.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_aoe.bt.compiled.json new file mode 100644 index 000000000000..e7dd1dadd498 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_aoe.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/pawn_has_trait_from","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_THETHING_MELEEMODE","value":true},{"type":"/datum/bt_node/ai_behavior/pick_random_ability","ability_keys":["BB_THETHING_DECIMATE","BB_THETHING_BIGTENDRILS","BB_THETHING_CARDTENDRILS","BB_THETHING_ACIDSPIT"],"last_used_key":"BB_THETHING_LASTAOE","result_key":"BB_generic_action"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_generic_action","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_THETHING_CHARGE","target_key":"Current Target"}],"chance":0.6}]}],"observer_abort":1,"invert":true,"trait":"immobilized","source":"megafauna"}],"invert":true,"key":"BB_THETHING_MELEEMODE"}],"invert":true,"key":"BB_THETHING_NOAOE"} diff --git a/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_boss.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_boss.bt.compiled.json new file mode 100644 index 000000000000..a85c8dc2f810 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_boss.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/thing_aoe"},{"type":"/datum/bt_node/subtree/thing_melee"}]}],"success_policy":1,"failure_policy":1}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait","duration":0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","vision_range":6}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_melee.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_melee.bt.compiled.json new file mode 100644 index 000000000000..dc2607af78cf --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_melee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/true_for_time","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_THETHING_MELEEMODE","value":false},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_THETHING_SHRIEK","target_key":"Current Target","maximum_distance":2},{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_THETHING_CHARGE","target_key":"Current Target"}],"target_key":"Current Target","min_distance":3},{"type":"/datum/bt_node/decorator/pawn_has_trait_from","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":1,"invert":true,"trait":"immobilized","source":"megafauna"}]}]}],"observer_abort":1,"duration":50} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/bot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/bot.bt.compiled.json new file mode 100644 index 000000000000..b1a4e2b000f9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/bot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/subtree/bot_salute_authority"},{"type":"/datum/bt_node/subtree/bot_patrol"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.compiled.json new file mode 100644 index 000000000000..67a0637e1734 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/execute_clean","target_key":"BB_current_pet_target"}]}],"observer_abort":3,"key":"BB_current_pet_target"} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.compiled.json new file mode 100644 index 000000000000..afade702a030 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/decorator/override_id_set","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"}],"observer_abort":2,"override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/execute_clean","target_key":"Current Target"}]}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"friendly_janitor","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"friendly_janitor","befriend_message":"friendly_message"}]}],"observer_abort":3,"key":"friendly_janitor"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"friendly_janitor","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/conscious_human/cleanbot_whisperer","vision_range":5,"time_between_perform":300}],"key":"friendly_janitor","invert":true}]},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/subtree/bot_patrol"}],"observer_abort":0,"cooldown_key":"post_clean_cooldown"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","time_between_perform":30,"target_key":"Current Target","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/cleanbot_cleanables","vision_range":5,"ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"reach_distance":15},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"invert":true},{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"acid_spray_target","required_dist":0,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/execute_clean","target_key":"acid_spray_target"}]}],"observer_abort":3,"key":"acid_spray_target"}],"observer_abort":2,"cooldown_key":"acid_spray_cooldown","cooldown_duration":30},{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"cleanbot_foam"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/subtree/bot_patrol"}],"cooldown_key":"post_clean_cooldown"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"acid_spray_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/conscious_human","ignore_list_key":"temporary_ignore_list","vision_range":5,"time_between_perform":300}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/dedbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/dedbot.bt.compiled.json new file mode 100644 index 000000000000..6329cd4a1085 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/dedbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/melee","ability_key":"BB_dedbot_exenterate","target_key":"Current Target"}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209.bt.compiled.json new file mode 100644 index 000000000000..3134c05c97b6 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_target_stunned","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","max_range":9}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target","approach_movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":false,"repeat_secondary_delay":10,"finish_on_primary":false}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.compiled.json new file mode 100644 index 000000000000..b19188387243 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/subtree/simple_ranged_combat","bindings":{"bsjjwub2":"/datum/bt_node/subtree/bot_patrol"}}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/firebot/firebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/firebot/firebot.bt.compiled.json new file mode 100644 index 000000000000..cd1dc47b6f43 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/firebot/firebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/announce_fire_detected"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/bot_interact/extinguish","target_key":"Current Target"}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"Current Target","target_source":"/datum/target_source/firebot_targets","targeting_strategy":"/datum/targeting_strategy/extinguishable_person","ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"vision_range":5},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"Current Target","target_source":"/datum/target_source/range_turfs/firebot_hotspots","targeting_strategy":"/datum/targeting_strategy/burning_hotspot","ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"vision_range":5}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/handle_firebot_speech"},{"type":"/datum/bt_node/subtree/bot_salute_authority"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot.bt.compiled.json new file mode 100644 index 000000000000..bc66e93cd889 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/honkbot_slip"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/play_with_clown","target_key":"clown_friend"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"clown_friend","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"clown_friend"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"slippery_target","target_source":"/datum/target_source/honkbot_slippery","targeting_strategy":"/datum/targeting_strategy/can_see","ignore_list_key":"temporary_ignore_list","vision_range":5,"time_between_perform":50},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"slip_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/can_see/slip_victim","ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"vision_range":5}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"clown_friend","target_source":"/datum/target_source/oview_single_type/living_mob","targeting_strategy":"/datum/targeting_strategy/clown_friend","ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"vision_range":5,"time_between_perform":50}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.compiled.json new file mode 100644 index 000000000000..dd807a874d2f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/can_see_target","children":[{"type":"/datum/bt_node/decorator/can_see_target","children":[{"type":"/datum/bt_node/decorator/pawn_has_gravity","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"slip_target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/grab_target","target_key":"slip_target"},{"type":"/datum/bt_node/decorator/is_grabbing_target","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"slippery_target","required_dist":0,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"}],"observer_abort":1,"key":"slip_target"},{"type":"/datum/bt_node/ai_behavior/release_and_slip","victim_key":"slip_target"},{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"flip"}]}]}],"observer_abort":3,"key":"slip_target","range":5}],"observer_abort":3,"key":"slippery_target","range":5} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.compiled.json new file mode 100644 index 000000000000..c88e409ba1e3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/ai_behavior/wash_target","target_key":"wash_target"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"wash_target","required_dist":0,"finish_on_arrival":false,"movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/commence_trashtalk","target_key":"wash_target"}],"observer_abort":0,"cooldown_key":"trash_talk_cooldown","cooldown_duration":40}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"wash_target"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/hygiene_wash","target_key":"wash_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/conscious_human/washable_human","ignore_list_key":"temporary_ignore_list","vision_range":5,"time_between_perform":50},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot.bt.compiled.json new file mode 100644 index 000000000000..4542085b09d3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_medical_flag","children":[{"type":"/datum/bt_node/subtree/medbot_find_and_announce_crit"}],"observer_abort":3,"flag":1},{"type":"/datum/bt_node/decorator/bot_medical_flag","children":[{"type":"/datum/bt_node/subtree/medbot_treat_patient"}],"observer_abort":3,"invert":true,"flag":8},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"patient_in_crit","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/crit_patient","vision_range":7},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/medbot_patient","target_key":"Current Target","target_source":"/datum/target_source/oview_single_type/human_mob/medbot_patient","targeting_strategy":"/datum/targeting_strategy/treatable_patient","ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"reach_distance":20,"vision_range":7}],"failure_policy":0,"success_policy":0,"repeat_secondary":false,"repeat_secondary_delay":10,"finish_on_primary":false},{"type":"/datum/bt_node/subtree/bot_salute_authority"},{"type":"/datum/bt_node/decorator/bot_medical_flag","children":[{"type":"/datum/bt_node/ai_behavior/handle_medbot_speech","announce_key":"announce_ability"}],"flag":4}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":false}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.compiled.json new file mode 100644 index 000000000000..80720b96b5a3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/announce_patient","target_key":"patient_in_crit"}],"observer_abort":3,"key":"patient_in_crit"} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.compiled.json new file mode 100644 index 000000000000..47d46973cc8a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/tend_to_patient","target_key":"Current Target"}]}],"observer_abort":3,"key":"Current Target"} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/mulebot/mulebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/mulebot/mulebot.bt.compiled.json new file mode 100644 index 000000000000..73f497e6cb3a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/mulebot/mulebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"mulebot_travel_target","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/handle_delivery","target_key":"mulebot_travel_target"}]}],"key":"mulebot_travel_target"},{"type":"/datum/bt_node/decorator/bot_wire_cut","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_mode","children":[{"type":"/datum/bt_node/ai_behavior/find_delivery_beacon","target_key":"mulebot_travel_target","tag_key":"mulebot_destination"}],"mode":"Delivering"},{"type":"/datum/bt_node/decorator/bot_mode","children":[{"type":"/datum/bt_node/ai_behavior/find_delivery_beacon","target_key":"mulebot_travel_target","tag_key":"mulebot_home_beacon"}],"mode":"Returning"}]}],"key":"mulebot_travel_target","invert":true}],"wire":"Beacon","invert":true}]},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json new file mode 100644 index 000000000000..622583c84e82 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/repairbot_repair_target"},{"type":"/datum/bt_node/subtree/repairbot_find_target"},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.compiled.json new file mode 100644 index 000000000000..4128c88eac77 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"robot target","required_dist":0,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/bot_interact/tip_robot","target_key":"robot target"},{"type":"/datum/bt_node/ai_behavior/grab_target","target_key":"robot target"}]}],"key":"robot target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deconstruct_target","required_dist":0,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/bot_interact","target_key":"deconstruct_target"}]}],"key":"deconstruct_target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json new file mode 100644 index 000000000000..f6922d5734eb --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_robot","target_key":"robot target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/deconstructable","target_key":"deconstruct_target","minimum_distance":1}]}],"invert":false},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/refillable_target","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_grille_target","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_window_fix","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_girder","target_key":"Current Target","minimum_distance":1}]},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"interaction_type","value":1},{"type":"/datum/bt_node/ai_behavior/cancel_current_plan"}]},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_wall_target","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"interaction_type","value":2},{"type":"/datum/bt_node/ai_behavior/cancel_current_plan"}]}]}],"observer_abort":0,"invert":true,"key":"Current Target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json new file mode 100644 index 000000000000..5ff44f5bafda --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/subtree/repairbot_emagged"}],"observer_abort":3},{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/bot_interact","target_key":"Current Target"}],"key":"interaction_type","value":1},{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder","ability_key":"girder_build_ability","target_key":"Current Target"}],"key":"interaction_type","value":2}]}]}],"observer_abort":3,"key":"Current Target"}],"observer_abort":1,"invert":true},{"type":"/datum/bt_node/subtree/bot_patrol"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/secbot/secbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/secbot/secbot.bt.compiled.json new file mode 100644 index 000000000000..1437099e8764 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/secbot/secbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/vibebot/vibebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/vibebot/vibebot.bt.compiled.json new file mode 100644 index 000000000000..bc0a69a489dd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/vibebot/vibebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"party_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/vibebot_party","ability_key":"party_ability","target_key":"party_target"}]}],"key":"party_target"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"party_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/conscious_human/party_friend","ignore_list_key":"temporary_ignore_list","vision_range":5,"time_between_perform":50}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/clown/clown.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/clown/clown.bt.compiled.json new file mode 100644 index 000000000000..1722ad983fc5 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/clown/clown.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/simple_retaliate_combat"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/cult/constructs/artificer.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/cult/constructs/artificer.bt.compiled.json new file mode 100644 index 000000000000..eceda0c7f563 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/cult/constructs/artificer.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/cult/constructs/juggernaut.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/cult/constructs/juggernaut.bt.compiled.json new file mode 100644 index 000000000000..c4e166a3dbd3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/cult/constructs/juggernaut.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/cult/constructs/proteon.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/cult/constructs/proteon.bt.compiled.json new file mode 100644 index 000000000000..5895d2498f92 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/cult/constructs/proteon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/skittish_brawler_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/cult/constructs/wraith.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/cult/constructs/wraith.bt.compiled.json new file mode 100644 index 000000000000..1f1bc0d98f43 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/cult/constructs/wraith.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/cytology/vatbeast.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/cytology/vatbeast.bt.compiled.json new file mode 100644 index 000000000000..98d46260ed31 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/cytology/vatbeast.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_at_least","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/melee","ability_key":"BB_generic_action","target_key":"Current Target"}],"observer_abort":2,"key":"BB_basic_mob_has_target_time","minimum":100},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_TARGET_FOOD","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_typed/from_bb_key/basic_foods"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/bee.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/bee.bt.compiled.json new file mode 100644 index 000000000000..ce0187ca3c23 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/bee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/find_hive"},{"type":"/datum/bt_node/subtree/transition_hive_status"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/pollinate_target"},{"type":"/datum/bt_node/subtree/simple_hostile_combat"}]},{"type":"/datum/bt_node/ai_behavior/enter_exit_hive"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_target_hydro","target_source":"/datum/target_source/oview_single_type/hydroponics","targeting_strategy":"/datum/targeting_strategy/pollinatable_hydro","vision_range":10,"time_between_perform":100}],"chance":0.85}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_hive"}],"invert":true,"key":"BB_target_home"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/find_hive.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/find_hive.bt.compiled.json new file mode 100644 index 000000000000..383021ff26e8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/find_hive.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_home","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/inhabit_hive"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_target_home"}]}],"key":"BB_current_home","invert":true}],"observer_abort":2,"invert":false,"key":"BB_target_home"} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.compiled.json new file mode 100644 index 000000000000..80f66c96bedc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_hydro","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/pollinate_hydro"}]}],"observer_abort":2,"key":"BB_target_hydro"} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.compiled.json new file mode 100644 index 000000000000..5e4ce721119e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/find_hive"},{"type":"/datum/bt_node/subtree/pollinate_target"},{"type":"/datum/bt_node/subtree/transition_hive_status"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/simple_hostile_combat"},{"type":"/datum/bt_node/ai_behavior/enter_exit_hive/queen"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_hive"}],"invert":true,"key":"BB_target_home"}],"failure_policy":0,"success_policy":0,"repeat_secondary":false,"repeat_secondary_delay":10,"finish_on_primary":false} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.compiled.json new file mode 100644 index 000000000000..4edb3e6f3760 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_home","required_dist":1},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_current_home"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_wants_to_transition_hive","value":false}]}],"observer_abort":2,"key":"BB_wants_to_transition_hive","value":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chick.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chick.bt.compiled.json new file mode 100644 index 000000000000..2fec6c77ba97 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chick.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_mom","required_dist":1,"finish_on_arrival":true}],"target_key":"BB_found_mom","min_distance":2},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/look_to_parent","parent_key":"BB_found_mom"}],"chance":0.15}]}],"key":"BB_found_mom","observer_abort":1},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_mom"}],"key":"BB_found_mom","invert":true},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chicken.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chicken.bt.compiled.json new file mode 100644 index 000000000000..5f77adc0a39c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chicken.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/skittish_and_speak"} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow.bt.compiled.json new file mode 100644 index 000000000000..c7f3628371bc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/tip_reaction"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.compiled.json new file mode 100644 index 000000000000..772646294be2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/tip_reaction"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.compiled.json new file mode 100644 index 000000000000..26ae961a8a1c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/tip_reaction"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/random_walk"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/deer/deer.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/deer/deer.bt.compiled.json new file mode 100644 index 000000000000..6a062dfbb42d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/deer/deer.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/stop_and_stare"}],"key":"BB_thing_that_made_us_stationary","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deer_home","required_dist":1},{"type":"/datum/bt_node/ai_behavior/deer_rest"}]}],"observer_abort":1,"key":"deer_home"}],"cooldown_key":"deer_next_rest_timer"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deer_playfriend","required_dist":1},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"deer_play_cooldown","cooldown_duration":100},{"type":"/datum/bt_node/ai_behavior/deer_play"}]}],"observer_abort":3,"key":"deer_playfriend"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deer_tree_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/deer_mark","target_key":"deer_tree_target","cooldown_key":"deer_mark_cooldown"}]}],"observer_abort":2,"key":"deer_tree_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deer_grass_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/hunt_target/deer_graze","target_key":"deer_grass_target","cooldown_key":"deer_graze_cooldown"}]}],"observer_abort":3,"key":"deer_grass_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deer_water_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/deer_drink","target_key":"deer_water_target","cooldown_key":"deer_drink_cooldown"}]}],"observer_abort":3,"key":"deer_water_target"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/random_speech_blackboard"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"deer_wants_to_play","value":true}],"chance":0.03}],"cooldown_key":"deer_play_cooldown"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"deer_playfriend","target_source":"/datum/target_source/oview_single_type/deer_animals","targeting_strategy":"/datum/targeting_strategy/playable_deer"},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"deer_wants_to_play","value":false}]}],"observer_abort":3,"key":"deer_wants_to_play"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"deer_tree_target","target_source":"/datum/target_source/oview_single_type/flora_tree","targeting_strategy":"/datum/targeting_strategy/anything"}],"cooldown_key":"deer_mark_cooldown"}],"invert":true,"key":"deer_tree_target"}],"invert":true,"key":"deer_home"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"deer_grass_target","target_source":"/datum/target_source/range_turfs/typecache_visible/deer_grass","targeting_strategy":"/datum/targeting_strategy/anything"}],"cooldown_key":"deer_graze_cooldown"}],"invert":true,"key":"deer_grass_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"deer_water_target","target_source":"/datum/target_source/range_turfs/typecache_visible/deer_water","targeting_strategy":"/datum/targeting_strategy/anything"}],"cooldown_key":"deer_drink_cooldown"}],"invert":true,"key":"deer_water_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_thing_that_made_us_stationary","target_source":"/datum/target_source/oview_typed/from_bb_key/stationary_targets","targeting_strategy":"/datum/targeting_strategy/anything"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/goat/goat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/goat/goat.bt.compiled.json new file mode 100644 index 000000000000..03e7b196a701 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/goat/goat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/subtree/forage_and_retaliate"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.compiled.json new file mode 100644 index 000000000000..cf4d3d55a626 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_TARGET_FOOD","targeting_strategy":"/datum/targeting_strategy/goose_edible","target_source":"/datum/target_source/oview_items","vision_range":1} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose.bt.compiled.json new file mode 100644 index 000000000000..4aa63aee1e5a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/subtree/forage_and_retaliate","bindings":{"q8w3rtv1":"/datum/bt_node/subtree/forage_for_goose_food"}}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.compiled.json new file mode 100644 index 000000000000..3fa72d010554 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/subtree/forage_and_retaliate","bindings":{"q8w3rtv1":"/datum/bt_node/subtree/forage_for_goose_food","z4n9bk7p":"/datum/bt_node/subtree/pick_retaliate_target"}}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.compiled.json new file mode 100644 index 000000000000..d43acdd1a6dc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target","can_attack_turfs":true},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/random_chance_from_key","children":[{"type":"/datum/bt_node/ai_behavior/run_emote","emote_key":"BB_emotes"}],"chance_key":"BB_EMOTE_CHANCE"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/pig.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/pig.bt.compiled.json new file mode 100644 index 000000000000..2c15b53fa014 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/pig.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/subtree/skittish_brawler_combat"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/pony.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/pony.bt.compiled.json new file mode 100644 index 000000000000..cff1f73085db --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/pony.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/simple_skittish_combat"}],"key":"BB_tamed","observer_abort":2},{"type":"/datum/bt_node/subtree/skittish_brawler_combat"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/rabbit.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/rabbit.bt.compiled.json new file mode 100644 index 000000000000..5f77adc0a39c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/rabbit.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/skittish_and_speak"} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/sheep.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/sheep.bt.compiled.json new file mode 100644 index 000000000000..5f77adc0a39c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/sheep.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/skittish_and_speak"} diff --git a/build/behavior_trees/modules/mob/living/basic/festivus_pole.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/festivus_pole.bt.compiled.json new file mode 100644 index 000000000000..befc9be9d224 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/festivus_pole.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target","target_key":"BB_low_priority_hunting_target","ability_key":"BB_festive_apc"}]}],"observer_abort":3,"key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/subtree/random_walk","bindings":{"bf07i8ep":10}}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/apc","targeting_strategy":"/datum/targeting_strategy/chargeable_apc","vision_range":6}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/heretic/raw_prophet.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/heretic/raw_prophet.bt.compiled.json new file mode 100644 index 000000000000..abae9529636f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/heretic/raw_prophet.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ability_ranged_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/heretic/rust_walker.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/heretic/rust_walker.bt.compiled.json new file mode 100644 index 000000000000..8635de408191 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/heretic/rust_walker.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/decorator/pawn_turf_has_trait","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability"}],"observer_abort":0,"trait":"rust_trait"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk/rust"}],"success_policy":1,"failure_policy":0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/heretic/stalker.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/heretic/stalker.bt.compiled.json new file mode 100644 index 000000000000..a1bf2a7fd06f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/heretic/stalker.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_inside_mob","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability/shapeshift"}],"observer_abort":2,"invert":true,"key":"Current Target"}],"invert":true},{"type":"/datum/bt_node/decorator/pawn_inside_mob","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_at_least","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability/shapeshift"}],"observer_abort":2,"key":"BB_basic_mob_has_target_time","minimum":80},{"type":"/datum/bt_node/ai_behavior/wait"}]}],"observer_abort":3,"invert":false,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]}],"observer_abort":1},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/heretic/star_gazer.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/heretic/star_gazer.bt.compiled.json new file mode 100644 index 000000000000..e6c3d8359448 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/heretic/star_gazer.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.compiled.json new file mode 100644 index 000000000000..224616f0eb5e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/ice_demon_flee_from_fire"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/ice_demon_combat"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.compiled.json new file mode 100644 index 000000000000..d622cdcd2c8c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/ice_demon_flee_from_fire"},{"type":"/datum/bt_node/subtree/simple_hostile_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.compiled.json new file mode 100644 index 000000000000..83b18fc48efe --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_furthest_turf_from_target","target_key":"Current Target","set_key":"escape_destination","range":7},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"demon_teleport_ability","target_key":"escape_destination"}]}],"ability_key":"demon_teleport_ability"}],"target_key":"Current Target","maximum_distance":1},{"type":"/datum/bt_node/decorator/pawn_health_below","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"demon_clone_ability","target_key":"Current Target"}],"ability_key":"demon_clone_ability"}],"health_threshold":75},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_valid_teleport_location","target_key":"Current Target","set_key":"teleport_destination","range":3},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"demon_teleport_ability","target_key":"teleport_destination"}]}],"ability_key":"demon_teleport_ability"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","max_range":9}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance","target_key":"Current Target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.compiled.json new file mode 100644 index 000000000000..d19a08d6d05d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/target_holding_lit_item","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"demon_slip_ability"}],"ability_key":"demon_slip_ability"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target","item_types_key":"list_scary_items"} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.compiled.json new file mode 100644 index 000000000000..384b14d072c4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_whelp_widespread_fire","target_key":"Current Target","maximum_distance":3},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_whelp_straightline_fire","target_key":"Current Target","maximum_distance":7},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_cannibal","required_dist":1},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_target_cannibal"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_target_cannibal"}]}],"observer_abort":2,"key":"BB_target_cannibal"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_rock","required_dist":0},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_target_rock","combat_mode":false},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"BB_whelp_sculpt_cooldown","cooldown_duration":3000},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_target_rock"}]}],"observer_abort":2,"key":"BB_target_rock"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_tree","required_dist":2},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"BB_whelp_straightline_fire","target_key":"BB_target_tree","maximum_distance":2},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"BB_whelp_burn_cooldown","cooldown_duration":1200}]}],"observer_abort":2,"key":"BB_target_tree"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_target_cannibal","target_source":"/datum/target_source/oview_single_type/ice_whelp","targeting_strategy":"/datum/targeting_strategy/dead_mob/not_pulled","vision_range":10},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_target_rock","target_source":"/datum/target_source/oview_single_type/icy_rock","targeting_strategy":"/datum/targeting_strategy/anything"}],"cooldown_key":"BB_whelp_sculpt_cooldown"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_target_tree","target_source":"/datum/target_source/oview_single_type/flora_tree","targeting_strategy":"/datum/targeting_strategy/non_stump_tree","vision_range":9}],"ability_key":"BB_whelp_straightline_fire"}],"cooldown_key":"d"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/polar_bear/polar.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/polar_bear/polar.bt.compiled.json new file mode 100644 index 000000000000..d2df8a3d4e1c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/polar_bear/polar.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/simple_hostile_combat","bindings":{"bbrbyj7y":"/datum/bt_node/ai_behavior/random_speech/bear"}},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/enrage"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/wolf/wolf.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/wolf/wolf.bt.compiled.json new file mode 100644 index 000000000000..41477912f9c8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/wolf/wolf.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/illusion/escape.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/illusion/escape.bt.compiled.json new file mode 100644 index 000000000000..86f5a41b8cd6 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/illusion/escape.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/subtree/pick_retaliate_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/illusion/retaliate.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/illusion/retaliate.bt.compiled.json new file mode 100644 index 000000000000..86f5a41b8cd6 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/illusion/retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/subtree/pick_retaliate_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/human_trap.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/human_trap.bt.compiled.json new file mode 100644 index 000000000000..56a99511c05d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/human_trap.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_ability_ranged_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/leaper/leaper.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/leaper/leaper.bt.compiled.json new file mode 100644 index 000000000000..8c71682d87ca --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/leaper/leaper.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"leaper_bubble","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"leaper_flop","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"leaper_volley","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"leaper_summon","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.compiled.json new file mode 100644 index 000000000000..da6c3f0f363e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/mega_arachnid_combat"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_surveillance_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_surveillance_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_surveillance_target"}]}],"key":"BB_surveillance_target","observer_abort":1},{"type":"/datum/bt_node/subtree/climb_tree"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_surveillance_target","target_source":"/datum/target_source/oview_typed/surveillance_equipment","targeting_strategy":"/datum/targeting_strategy/working_machine","vision_range":7},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_climbed_tree","target_source":"/datum/target_source/oview_single_type/flora_tree","targeting_strategy":"/datum/targeting_strategy/anything"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.compiled.json new file mode 100644 index 000000000000..3e7b12c6752f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_arachnid_slip"}],"ability_key":"BB_arachnid_slip"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_is_type","children":[{"type":"/datum/bt_node/decorator/target_legcuffed","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_arachnid_restrain","target_key":"Current Target"}],"ability_key":"BB_arachnid_restrain"}],"invert":true}],"target_type":"/mob/living/carbon/human"},{"type":"/datum/bt_node/decorator/target_is_type","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"target_type":"/mob/living/carbon/human","invert":true},{"type":"/datum/bt_node/decorator/target_legcuffed","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]},{"type":"/datum/bt_node/decorator/target_health_below_fraction","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"key":"Current Target","fraction":0.5}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"key":"Current Target","observer_abort":3} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling.bt.compiled.json new file mode 100644 index 000000000000..cb86ef52d36d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/decorator/override_id_set","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"}],"observer_abort":2,"override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_has_reagent","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"hydroplant_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"hydroplant_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"hydroplant_target"}]}],"key":"hydroplant_target","observer_abort":3}],"invert":false,"key":"watercan_target","reagent_type":"/datum/reagent/water"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"beamable_hydroplant_target","required_dist":2},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"solarbeam_ability","target_key":"beamable_hydroplant_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"beamable_hydroplant_target"}]}],"key":"beamable_hydroplant_target","observer_abort":3}],"ability_key":"solarbeam_ability","observer_abort":2},{"type":"/datum/bt_node/decorator/item_inside_pawn","children":[{"type":"/datum/bt_node/decorator/target_has_reagent","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_low_priority_hunting_target"}]}],"observer_abort":3,"key":"BB_low_priority_hunting_target"}],"invert":true,"key":"watercan_target","reagent_type":"/datum/reagent/water"}],"key":"watercan_target"},{"type":"/datum/bt_node/decorator/item_inside_pawn","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"watercan_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"watercan_target"}]}],"key":"watercan_target","observer_abort":3}],"invert":true,"key":"watercan_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/decorator/item_inside_pawn","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"watercan_target","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_single_type/watering_can"}],"invert":true,"key":"watercan_target"},{"type":"/datum/bt_node/decorator/item_inside_pawn","children":[{"type":"/datum/bt_node/decorator/target_has_reagent","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview","targeting_strategy":"/datum/targeting_strategy/water_dispenser"}],"invert":true,"key":"watercan_target","reagent_type":"/datum/reagent/water"}],"key":"watercan_target"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"hydroplant_target","target_source":"/datum/target_source/oview","targeting_strategy":"/datum/targeting_strategy/treatable_hydro","time_between_perform":50},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"beamable_hydroplant_target","target_source":"/datum/target_source/oview","targeting_strategy":"/datum/targeting_strategy/beamable_hydro","time_between_perform":40}],"ability_key":"solarbeam_ability"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.compiled.json new file mode 100644 index 000000000000..b69f6a865d4f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"rapidseeds_ability","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"solarbeam_ability","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.compiled.json new file mode 100644 index 000000000000..77df6cb13d44 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/decorator/target_has_trait","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","max_range":9}],"min_distance":2},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1}],"observer_abort":3,"invert":true,"key":"watcher_overwatched"},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.compiled.json new file mode 100644 index 000000000000..80d0afc48c25 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/decorator/mob_stat_at_least","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"BB_bileworm_devour","target_key":"BB_basic_execution_target","maximum_distance":16}],"observer_abort":2,"invert":false,"key":"BB_basic_execution_target","min_stat":2}],"ability_key":"BB_bileworm_devour"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/decorator/bileworm_should_resurface","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute","ability_key":"BB_bileworm_resurface","target_key":"Current Target"}],"target_key":"Current Target"}],"ability_key":"BB_bileworm_resurface"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute","ability_key":"BB_bileworm_spew_bile","target_key":"Current Target"}],"ability_key":"BB_bileworm_spew_bile"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":false}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.compiled.json new file mode 100644 index 000000000000..19d1f2b0032d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/brimbeam","ability_key":"BB_TARGETED_action","target_key":"Current Target","maximum_distance":9}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_cardinal","target_key":"Current Target","minimum_distance":2,"maximum_distance":9},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.compiled.json new file mode 100644 index 000000000000..8fdab6836a28 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/ai_behavior/dig_away_from_danger"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/grub_eat_target","bindings":{"bjwb8dxm":"BB_ore_target","bj13tsp3":"BB_ore_target","b6t594ql":"BB_ore_target"}},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_mom","required_dist":1,"finish_on_arrival":true}],"observer_abort":3,"key":"BB_found_mom"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_ore"},{"type":"/datum/bt_node/ai_behavior/find_mom"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.compiled.json new file mode 100644 index 000000000000..3c4f0195dc2c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]},{"type":"","override_id":"pet_command"},{"type":"/datum/bt_node/ai_behavior/dig_away_from_danger"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/burrow_through_ground"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/grub_eat_target","bindings":{"b6t594ql":"BB_boulder_target","bj13tsp3":"BB_boulder_target","bjwb8dxm":"BB_BOULDER_TARGETBB_BOULDER_TARGET"}},{"type":"/datum/bt_node/subtree/grub_eat_target","bindings":{"b6t594ql":"BB_ore_target","bj13tsp3":"BB_ore_target","bjwb8dxm":"BB_ore_target"}},{"type":"/datum/bt_node/subtree/grub_eat_target","bindings":{"b6t594ql":"BB_vent_target","bj13tsp3":"BB_vent_target","bjwb8dxm":"BB_vent_target"}},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_mineral_wall"},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"Mining Cooldown","cooldown_duration":40},{"type":"/datum/bt_node/ai_behavior/mine_wall","target_key":"BB_target_mineral_wall"}]}],"observer_abort":3,"key":"BB_target_mineral_wall"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/grab_target","target_key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_low_priority_hunting_target"}]}],"key":"BB_low_priority_hunting_target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_ore"},{"type":"/datum/bt_node/ai_behavior/find_boulder"},{"type":"/datum/bt_node/ai_behavior/find_ore_vent"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/find_mineral_wall","target_key":"BB_target_mineral_wall"}],"cooldown_key":"Mining Cooldown"},{"type":"/datum/bt_node/ai_behavior/find_grub_egg"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.compiled.json new file mode 100644 index 000000000000..dc90fd61f095 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"$bj13tsp3","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/grub_eat","target_key":"$bjwb8dxm"}]}],"observer_abort":3,"key":"$b6t594ql","__bindings":{"b6t594ql":{"label":"target_key","default":null},"bj13tsp3":{"label":"target_key","default":""},"bjwb8dxm":{"label":"target_key","default":""}}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/goliath/goliath.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/goliath/goliath.bt.compiled.json new file mode 100644 index 000000000000..a28028fef51f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/goliath/goliath.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/goliath_tentacles","ability_key":"BB_goliath_tentacles","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining","target_key":"Current Target"}],"key":"Current Target"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/goliath_find_diggable_turf","target_key":"BB_goliath_hole"}],"key":"BB_goliath_hole","invert":true},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_goliath_hole","required_dist":0},{"type":"/datum/bt_node/ai_behavior/goliath_dig","target_key":"BB_goliath_hole"}]},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location","check_faction":true}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.compiled.json new file mode 100644 index 000000000000..e96fbf26a777 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_mom","required_dist":1,"finish_on_arrival":true}],"observer_abort":3,"key":"BB_found_mom"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":false},{"type":"/datum/bt_node/ai_behavior/find_parent","mom_types_key":"BB_find_mom_types","found_mom_key":"BB_found_mom"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.compiled.json new file mode 100644 index 000000000000..f659cd1d9afe --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"trough_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target/food_trough","target_key":"trough_target"}]}],"observer_abort":3,"key":"trough_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":false},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"trough_target","target_source":"/datum/target_source/oview_single_type/gutlunch_trough","targeting_strategy":"/datum/targeting_strategy/trough_with_ore","vision_range":9}],"key":"BB_check_hungry"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.compiled.json new file mode 100644 index 000000000000..f501a0c0e197 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/subtree/simple_retaliate_combat","bindings":{"b2jvnm5d":1}}]},{"type":"/datum/bt_node/ai_behavior/befriend_ashwalkers"},{"type":"/datum/bt_node/subtree/find_partner"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":50,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.compiled.json new file mode 100644 index 000000000000..56a99511c05d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_ability_ranged_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion.bt.compiled.json new file mode 100644 index 000000000000..dc586bdcd46c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/random_speech/legion"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_brood.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_brood.bt.compiled.json new file mode 100644 index 000000000000..5ea2779a29c2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_brood.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.compiled.json new file mode 100644 index 000000000000..a6f3af26ff19 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/consider_venting"},{"type":"/datum/bt_node/subtree/random_walk","bindings":{"bf07i8ep":10}}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_entry_vent_target","target_source":"/datum/target_source/oview_single_type/vent_pump","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":7},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/random_speech/legion"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.compiled.json new file mode 100644 index 000000000000..2a1862e533d4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.compiled.json new file mode 100644 index 000000000000..49b0c7917e7d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/simple_retaliate_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.compiled.json new file mode 100644 index 000000000000..cfdc3e730a6c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/simple_capricious_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard.bt.compiled.json new file mode 100644 index 000000000000..d2c4f973b830 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/generic_mook_behavior","bindings":{"b1kiiayf":"/datum/bt_node/subtree/bard_play_music","b0ymbjo1":"/datum/bt_node/subtree/bard_find_targets"}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.compiled.json new file mode 100644 index 000000000000..0c56f6db6c96 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mook_tribal_chief","target_source":"/datum/target_source/oview_single_type/tribal_chief","targeting_strategy":"/datum/targeting_strategy/anything"},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"mook_tribal_chief","long_range_friendship":true,"forget_target":false}]}],"invert":true,"key":"mook_tribal_chief"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"music_audience","target_source":"/datum/target_source/near_village_humans","targeting_strategy":"/datum/targeting_strategy/conscious_human","time_between_perform":10}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.compiled.json new file mode 100644 index 000000000000..74fa5b9cfed8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/generic_play_instrument","bindings":{"bulu13xf":75}},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"music_audience","required_dist":2,"finish_on_arrival":false}],"observer_abort":2,"key":"music_audience"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target","target_key":"home_village","walk_chance":75}],"success_policy":1,"failure_policy":0,"loop_delay":10}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.compiled.json new file mode 100644 index 000000000000..312234cbe87d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_deposit_position"}],"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_ore","time_between_perform":10,"range":1}],"invert":true,"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"material_stand_target","target_source":"/datum/target_source/oview_single_type/ore_stand","targeting_strategy":"/datum/targeting_strategy/anything"}],"invert":true,"key":"material_stand_target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.compiled.json new file mode 100644 index 000000000000..9874acfc3716 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/issue_pet_command","command_list_key":"BB_mook_commands","command_type":"/datum/pet_command/attack","target_key":"Current Target","commandable_mob_type":"/mob/living/basic/mining/mook","command_distance":7}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/issue_pet_command","command_list_key":"BB_mook_commands","command_type":"/datum/pet_command/fetch","target_key":"BB_ore_target","commandable_mob_type":"/mob/living/basic/mining/mook","command_distance":7}],"observer_abort":3,"key":"BB_ore_target"}]}],"cooldown_key":"command cooldown","cooldown_duration":100,"lock_on_succeed":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.compiled.json new file mode 100644 index 000000000000..c3fb056077b2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_deposit_position"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_deposit_position"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"material_stand_target"}]}],"observer_abort":2,"key":"BB_deposit_position"}],"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"bonfire_target"},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"bonfire_target","always_reset_target":true,"behavior_combat_mode":false}]}],"observer_abort":2,"invert":false,"key":"bonfire_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_ore_target"},{"type":"/datum/bt_node/ai_behavior/pick_up_item_virtual","target_key":"BB_ore_target","storage_key":"BB_SIMPLE_CARRY_ITEM"}]}],"observer_abort":2,"invert":false,"key":"BB_ore_target"}],"observer_abort":2,"invert":true,"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target","target_key":"home_village","walk_chance":75}],"success_policy":1,"failure_policy":0,"loop_delay":10}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"bonfire_target","target_source":"/datum/target_source/oview_typed/from_bb_key/bonfire_targets","targeting_strategy":"/datum/targeting_strategy/unlit_bonfire"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.compiled.json new file mode 100644 index 000000000000..374bad58e688 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_village"}],"invert":true,"key":"home_village"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_same_z_as_key","children":[{"type":"/datum/bt_node/decorator/pawn_farther_than_from_key","children":[{"type":"/datum/bt_node/decorator/mook_has_flee_reason","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/clear_key","key":"injured_mook"},{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"mook_jump_ability"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_target_mineral_wall"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"music_audience"}]}],"observer_abort":2}],"anchor_key":"home_village","distance_key":"maximum_distance_to_village"}],"key":"home_village"},{"type":"$bk7ixti3"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"mook_leap_ability","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"$b1kiiayf"}]},{"type":"$b0ymbjo1"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"__bindings":{"b1kiiayf":{"label":"Idle Behavior","default":"/datum/bt_node/subtree"},"b0ymbjo1":{"label":"Targetting Behavior","default":"/datum/bt_node/subtree"},"bk7ixti3":{"label":"High Priority Behavior","default":"/datum/bt_node/subtree"}}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/go_mining.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/go_mining.bt.compiled.json new file mode 100644 index 000000000000..b7010694ca12 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/go_mining.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_deposit_position","required_dist":0},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_deposit_position"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"material_stand_target"}]}],"observer_abort":2,"key":"BB_deposit_position"}],"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_ore_target"},{"type":"/datum/bt_node/ai_behavior/pick_up_item_virtual","target_key":"BB_ore_target","storage_key":"BB_SIMPLE_CARRY_ITEM"}]}],"observer_abort":2,"invert":false,"key":"BB_ore_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_mineral_wall"},{"type":"/datum/bt_node/ai_behavior/mine_wall","target_key":"BB_target_mineral_wall","time_between_perform":10},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"mining cooldown","cooldown_duration":100},{"type":"/datum/bt_node/ai_behavior/find_ore","time_between_perform":5.0}]}],"observer_abort":2,"invert":false,"key":"BB_target_mineral_wall"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/calculate_wander_destination"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_wander_destination","required_dist":0}]},{"type":"/datum/bt_node/subtree/random_walk"}]}],"observer_abort":2,"invert":true,"key":"BB_SIMPLE_CARRY_ITEM"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_deposit_position"}],"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/find_ore"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/find_mineral_wall/mook","target_key":"BB_target_mineral_wall"}],"cooldown_key":"mining cooldown"}]}],"invert":true,"key":"BB_SIMPLE_CARRY_ITEM"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/heal_injured.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/heal_injured.bt.compiled.json new file mode 100644 index 000000000000..c70b3d625f1a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/heal_injured.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"injured_mook"},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","always_reset_target":true,"target_key":"injured_mook"}]}],"observer_abort":3,"key":"injured_mook"},{"type":"/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target","target_key":"home_village","walk_chance":75}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/mook.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/mook.bt.compiled.json new file mode 100644 index 000000000000..eb69e776b453 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/mook.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/generic_mook_behavior","bindings":{"b0ymbjo1":"/datum/bt_node/subtree/worker_find_targets","b1kiiayf":"/datum/bt_node/subtree/go_mining"}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support.bt.compiled.json new file mode 100644 index 000000000000..e82ee7b19c71 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/generic_mook_behavior","bindings":{"b1kiiayf":"/datum/bt_node/subtree/heal_injured","b0ymbjo1":"/datum/bt_node/subtree/support_find_targets"}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.compiled.json new file mode 100644 index 000000000000..995d92aea784 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mook_tribal_chief","target_source":"/datum/target_source/oview_single_type/tribal_chief","targeting_strategy":"/datum/targeting_strategy/anything"},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"mook_tribal_chief","long_range_friendship":true,"forget_target":false}]}],"invert":true,"key":"mook_tribal_chief"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"injured_mook","target_source":"/datum/target_source/oview_typed/from_bb_key/mook_heal_targets","targeting_strategy":"/datum/targeting_strategy/injured_mob"}],"invert":true,"key":"BB_storm_approaching"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.compiled.json new file mode 100644 index 000000000000..47b9fe22a20b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/generic_mook_behavior","bindings":{"b0ymbjo1":"/datum/bt_node/subtree/chief_find_targets","b1kiiayf":"/datum/bt_node/subtree/chief_manage_village","bk7ixti3":"/datum/bt_node/subtree/chief_issue_commands"}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.compiled.json new file mode 100644 index 000000000000..22d81cd33cca --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"material_stand_target","target_source":"/datum/target_source/oview_single_type/ore_stand","targeting_strategy":"/datum/targeting_strategy/anything"}],"invert":true,"key":"material_stand_target"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mook_tribal_chief","target_source":"/datum/target_source/oview_single_type/tribal_chief","targeting_strategy":"/datum/targeting_strategy/anything"},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"mook_tribal_chief","long_range_friendship":true,"forget_target":false}]}],"invert":true,"key":"mook_tribal_chief"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.compiled.json new file mode 100644 index 000000000000..5d4dd01a8694 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_hunting_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/hunt_target/latch_onto","target_key":"BB_current_hunting_target","hunt_cooldown":50}]}],"observer_abort":1,"invert":true,"target_key":"BB_current_hunting_target"}],"key":"BB_current_hunting_target","observer_abort":3}],"success_policy":1,"failure_policy":1,"loop_delay":15.0},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","target_source":"/datum/target_source/oview_single_type/ore_vent","targeting_strategy":"/datum/targeting_strategy/ore_vent_unclaimed","vision_range":7}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.compiled.json new file mode 100644 index 000000000000..64de9c0a6b5f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/express_happiness"}],"observer_abort":3,"chance":0.3},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_mom"},{"type":"/datum/bt_node/ai_behavior/look_to_parent"}]}],"observer_abort":3,"key":"BB_found_mom"}],"observer_abort":3,"chance":0.15},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/raptor_find_food"},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_mom"}],"invert":true,"key":"BB_found_mom"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.compiled.json new file mode 100644 index 000000000000..4b4a289ebbc1 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"raptor_baby"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"raptor_baby","combat_mode":false},{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"grooms its baby!"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"raptor_baby"}]}],"chance":0.6}],"observer_abort":3,"key":"raptor_baby"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.compiled.json new file mode 100644 index 000000000000..67a67cd3559f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/raptor_flee"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/raptor_heal_injured"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/raptor_food_trough"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/subtree/play_with_owner"}],"observer_abort":0,"chance":0.4},{"type":"/datum/bt_node/subtree/care_for_young"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/raptor_find_food"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"BB_owner_target","targeting_strategy":"/datum/targeting_strategy/ally_mob","target_source":"/datum/target_source/oview_single_type/living_mob"}],"observer_abort":0,"key":"raptor_playful"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"injured_raptor","targeting_strategy":"/datum/targeting_strategy/injured_mob/not_self/injured_raptor"},{"type":"/datum/bt_node/ai_behavior/retrieve_injured_rider","target_key":"injured_raptor"}]}],"observer_abort":0,"key":"BB_basic_mob_healer"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"raptor_baby","targeting_strategy":"/datum/targeting_strategy/healthy_raptor_baby","target_source":"/datum/target_source/oview_raptor_babies"}],"observer_abort":0,"key":"raptor_motherly"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]},{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/express_happiness"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.compiled.json new file mode 100644 index 000000000000..01309e1c72ca --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"raptor_trough_target","targeting_strategy":"/datum/targeting_strategy/raptor_trough","target_source":"/datum/target_source/oview_single_type/raptor_trough"}],"failure_policy":0,"success_policy":0,"repeat_secondary":false,"finish_on_primary":false}],"observer_abort":1,"cooldown_key":"BB_NEXT_EAT_FOOD"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.compiled.json new file mode 100644 index 000000000000..087fea9e73d2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"BB_RAPTOR_COWARDLY"},{"type":"/datum/bt_node/decorator/pawn_health_below","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"health_blackboard_key":"raptor_flee_threshold"},{"type":"/datum/bt_node/decorator/check_rider_stat","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.compiled.json new file mode 100644 index 000000000000..a9af0238bf0f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"injured_raptor"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"injured_raptor","combat_mode":false},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"injured_raptor"}]}],"observer_abort":3,"key":"injured_raptor"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.compiled.json new file mode 100644 index 000000000000..1d2d0523de20 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_owner_target"},{"type":"/datum/bt_node/ai_behavior/hunt_target/play_with_owner","cooldown_key":"BB_owner_target"}]}],"observer_abort":3,"key":"BB_owner_target"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.compiled.json new file mode 100644 index 000000000000..bd3184c469a7 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"raptor_trough_target"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"raptor_trough_target","combat_mode":false},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"raptor_trough_target"}]}],"observer_abort":3,"key":"raptor_trough_target"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/tendril/tendril.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/tendril/tendril.bt.compiled.json new file mode 100644 index 000000000000..364faacec07d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/tendril/tendril.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"tendril_spikes"},{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"tendril_lash"}]}],"target_key":"Current Target","maximum_distance":7},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"tendril_chaser","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":false}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":true,"vision_range":5},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","target_loss_distance":9,"vision_range":5}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/watcher/watcher.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/watcher/watcher.bt.compiled.json new file mode 100644 index 000000000000..db275f6a561d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/watcher/watcher.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/decorator/target_has_trait","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_at_least","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability"}],"key":"BB_basic_mob_has_target_time","minimum":50},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","max_range":9}]}],"success_policy":1,"failure_policy":1}],"observer_abort":3,"invert":true,"key":"watcher_overwatched"},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/minebots/minebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/minebots/minebot.bt.compiled.json new file mode 100644 index 000000000000..c0787dc703fc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/minebots/minebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/minebot_combat"},{"type":"/datum/bt_node/subtree/minebot_mining"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"miner_friend","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/rock_stoner"},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"miner_friend","long_range_friendship":true}]},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"nearby_dead_miner","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/unconscious_human"}],"cooldown_key":"minebot callcrit cooldown","cooldown_duration":100},{"type":"/datum/bt_node/ai_behavior/send_sos_message","target_key":"nearby_dead_miner"}]}]}],"success_policy":0,"failure_policy":0,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/minebots/minebot_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/minebots/minebot_combat.bt.compiled.json new file mode 100644 index 000000000000..efd6d22309b1 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/minebots/minebot_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"minebot_missile_ability","target_key":"minebot_missile_target"}],"observer_abort":2,"key":"minebot_missile_ability"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/minebot","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target","min_dist_key":"minimum_shooting_distance","max_dist_key":"minimum_shooting_distance"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"minebot_missile_target","target_source":"/datum/target_source/oview_single_type/minebot_target","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":7}],"key":"minebot_missile_target","invert":true}],"success_policy":0,"failure_policy":0,"loop_delay":10},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"minebot_landmine_ability"}],"invert":false,"key":"minebot_landmine_ability"}],"success_policy":0,"failure_policy":0,"loop_delay":10}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3} diff --git a/build/behavior_trees/modules/mob/living/basic/minebots/minebot_mining.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/minebots/minebot_mining.bt.compiled.json new file mode 100644 index 000000000000..33fa38f1c9e8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/minebots/minebot_mining.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/target_health_below_fraction","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"defend_drone","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/repair_drone","target_key":"defend_drone"}]}],"observer_abort":3,"key":"defend_drone","fraction":0.75}],"observer_abort":3,"key":"defend_drone"}],"observer_abort":3,"key":"minebot_repair_drone"},{"type":"/datum/bt_node/ai_behavior/befriend_target/check_ally","target_key":"defend_drone","long_range_friendship":true,"forget_target":false}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_ore_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/collect_ore/minebot","target_key":"BB_ore_target"}]}],"observer_abort":3,"key":"BB_ore_target"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"target_mineral_turf","required_dist":2,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/minebot_mine_turf","target_key":"target_mineral_turf"}]}],"observer_abort":2,"key":"target_mineral_turf"}],"observer_abort":2,"key":"automated_mining"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","time_between_perform":50,"target_key":"defend_drone","targeting_strategy":"/datum/targeting_strategy/anything","target_source":"/datum/target_source/oview_single_type/node_drone","vision_range":9,"revalidation_mode":1},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_ore_target","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_single_type/ore","vision_range":2},{"type":"/datum/bt_node/ai_behavior/find_mineral_wall/minebot","target_key":"target_mineral_turf"}]}],"observer_abort":2,"key":"automated_mining"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json new file mode 100644 index 000000000000..b13bced69f26 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_basic_flee_target"}]}],"key":"BB_basic_flee_target","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/cat_reside_in_home"},{"type":"/datum/bt_node/subtree/cat_haul_food"},{"type":"/datum/bt_node/subtree/cat_hunt_mice"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"tresspasser_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/territorial_struggle","target_key":"tresspasser_target","cries_key":"hostile_meows"}]}],"key":"tresspasser_target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3}],"observer_abort":0,"invert":true,"key":"mouse_target"},{"type":"/datum/bt_node/subtree/cat_find_food"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/cats"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_home","target_source":"/datum/target_source/oview_single_type/cat_house","targeting_strategy":"/datum/targeting_strategy/valid_cat_home"}],"chance":0.05},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/find_cat_tresspasser","target_key":"tresspasser_target"}],"chance":0.05},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mouse_target","target_source":"/datum/target_source/oview_single_type/mouse","targeting_strategy":"/datum/targeting_strategy/huntable_mouse","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_food_target","target_source":"/datum/target_source/oview_typed/from_bb_key/huntable_prey","targeting_strategy":"/datum/targeting_strategy/cat_food","vision_range":9}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_bread.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_bread.bt.compiled.json new file mode 100644 index 000000000000..4f03d5a4ddef --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_bread.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/cat_turn_off_stove"},{"type":"/datum/bt_node/subtree/cat_haul_food"},{"type":"/datum/bt_node/subtree/cat_hunt_mice"},{"type":"/datum/bt_node/subtree/cat_find_food"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/cats"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mouse_target","target_source":"/datum/target_source/oview_single_type/mouse","targeting_strategy":"/datum/targeting_strategy/huntable_mouse","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_food_target","target_source":"/datum/target_source/oview_typed/from_bb_key/huntable_prey","targeting_strategy":"/datum/targeting_strategy/cat_food","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"stove_target","target_source":"/datum/target_source/oview_single_type/oven","targeting_strategy":"/datum/targeting_strategy/finished_stove","vision_range":9},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"food_to_deliver","target_source":"/datum/target_source/carried_huntable_prey","targeting_strategy":"/datum/targeting_strategy/anything"}],"key":"food_to_deliver","invert":true},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"food_to_deliver","target_source":"/datum/target_source/carried_huntable_prey","targeting_strategy":"/datum/targeting_strategy/anything"}],"key":"food_to_deliver","invert":true}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_cake.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_cake.bt.compiled.json new file mode 100644 index 000000000000..cfc0bf46ee61 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_cake.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/cat_turn_off_stove"},{"type":"/datum/bt_node/subtree/cat_decorate_donuts"},{"type":"/datum/bt_node/subtree/cat_haul_food"},{"type":"/datum/bt_node/subtree/cat_hunt_mice"},{"type":"/datum/bt_node/subtree/cat_find_food"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/cats"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mouse_target","target_source":"/datum/target_source/oview_single_type/mouse","targeting_strategy":"/datum/targeting_strategy/huntable_mouse","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_food_target","target_source":"/datum/target_source/oview_typed/from_bb_key/huntable_prey","targeting_strategy":"/datum/targeting_strategy/cat_food","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"stove_target","target_source":"/datum/target_source/oview_single_type/oven","targeting_strategy":"/datum/targeting_strategy/finished_stove","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"donut_target","target_source":"/datum/target_source/oview_single_type/donut","targeting_strategy":"/datum/targeting_strategy/decorated_donut","vision_range":9},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"food_to_deliver","target_source":"/datum/target_source/carried_huntable_prey","targeting_strategy":"/datum/targeting_strategy/anything"}],"key":"food_to_deliver","invert":true},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"food_to_deliver","target_source":"/datum/target_source/carried_huntable_prey","targeting_strategy":"/datum/targeting_strategy/anything"}],"key":"food_to_deliver","invert":true}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.compiled.json new file mode 100644 index 000000000000..875e7b4fde44 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"donut_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/decorate_donuts","target_key":"donut_target"}]}],"key":"donut_target","observer_abort":3} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_find_food.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_find_food.bt.compiled.json new file mode 100644 index 000000000000..51899c21580d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_find_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"cat_food_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"cat_food_target","always_reset_target":true}]}],"key":"cat_food_target","observer_abort":3} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_haul_food.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_haul_food.bt.compiled.json new file mode 100644 index 000000000000..a717f60d6e46 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_haul_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cat_holding_food","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"kitten_to_feed","required_dist":1},{"type":"/datum/bt_node/ai_behavior/deliver_food_to_kitten","target_key":"kitten_to_feed","food_key":"food_to_deliver"}]}],"key":"food_to_deliver"}],"key":"kitten_to_feed","observer_abort":3}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.compiled.json new file mode 100644 index 000000000000..770c97852cc3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cat_holding_food","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"mouse_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/play_with_mouse","target_key":"mouse_target"}]}],"key":"mouse_target","observer_abort":3}],"invert":true} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.compiled.json new file mode 100644 index 000000000000..0cf26f4dd1a9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_loc_is_type","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/leave_cat_home"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}],"loc_type":"/obj/structure/cat_house","observer_abort":2},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"cat_home","required_dist":1},{"type":"/datum/bt_node/ai_behavior/enter_cat_home","target_key":"cat_home"}]}],"key":"cat_home","observer_abort":3}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.compiled.json new file mode 100644 index 000000000000..d7496ff2ade9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"stove_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"stove_target","always_reset_target":true}]}],"key":"stove_target","observer_abort":3} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/kitten.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/kitten.bt.compiled.json new file mode 100644 index 000000000000..3054191cd5aa --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/kitten.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/beacon_for_food","target_key":"human_beg_target","meows_key":"hungry_meows"}],"key":"human_beg_target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_farther_than_from_key","children":[{"type":"/datum/bt_node/ai_behavior/beacon_for_food","target_key":"cat_food_target","meows_key":"hungry_meows"}],"anchor_key":"cat_food_target","distance_key":"max_distance_to_food"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"cat_food_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"cat_food_target","always_reset_target":true}]}]}],"key":"cat_food_target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/cats"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_food_target","target_source":"/datum/target_source/oview_typed/from_bb_key/huntable_prey","targeting_strategy":"/datum/targeting_strategy/cat_food","vision_range":9},{"type":"/datum/bt_node/ai_behavior/find_human_to_beg","target_key":"human_beg_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/dog/guarddog.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/dog/guarddog.bt.compiled.json new file mode 100644 index 000000000000..d94250485b84 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/dog/guarddog.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_retaliate_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/fox.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/fox.bt.compiled.json new file mode 100644 index 000000000000..cc55e5de0823 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/fox.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}}],"key":"BB_basic_flee_target","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/fox"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/decorator/no_humans_watching","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"polling_rate":20},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/fox_docile.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/fox_docile.bt.compiled.json new file mode 100644 index 000000000000..d977d389b7cc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/fox_docile.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}}],"key":"BB_basic_flee_target","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/random_walk"},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/fox"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/gondolas/gondola.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/gondolas/gondola.bt.compiled.json new file mode 100644 index 000000000000..e1ea2e2d727e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/gondolas/gondola.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/random_walk","bindings":{"bf07i8ep":10}} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/orbie/orbie.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/orbie/orbie.bt.compiled.json new file mode 100644 index 000000000000..69f59fc81132 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/orbie/orbie.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"nearby_playmate","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/interact_with_playmate","target_key":"nearby_playmate"}]}],"key":"nearby_playmate","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/relay_pda_message","target_key":"last_received_message"}],"observer_abort":2,"key":"last_received_message"}]},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/find_playmate"}],"cooldown_key":"next_playdate"},{"type":"/datum/bt_node/subtree/find_food"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.compiled.json new file mode 100644 index 000000000000..0b2b3a8ebefd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_contained_in_obj","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/container_attackable","children":[{"type":"/datum/bt_node/ai_behavior/break_out_of_object/from_bb","target_key":"BB_basic_mob_escape_target"}]},{"type":"/datum/bt_node/ai_behavior/resist"}]}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/subtree/perching"}],"chance":0.05},{"type":"/datum/bt_node/subtree/parrot_hoard"},{"type":"/datum/bt_node/ai_behavior/idle_random_walk/parrot"}]}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/parrot_repeat_speech"}],"cooldown_key":"BB_parrot_speech_cooldown","cooldown_duration":75.0},{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"chance":0.05}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.compiled.json new file mode 100644 index 000000000000..d0e880787173 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"perch_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/perch_on_target/haunt","target_key":"perch_target"}]},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"perch_target"}]}],"key":"perch_target"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"perch_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/conscious_human"}],"chance":0.02},{"type":"/datum/bt_node/subtree/parrot_hoard"},{"type":"/datum/bt_node/ai_behavior/idle_random_walk/parrot"}]}],"success_policy":1,"failure_policy":0,"loop_delay":15.0},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/parrot_repeat_speech"}],"cooldown_key":"BB_parrot_speech_cooldown","cooldown_duration":75.0},{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"chance":0.05}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.compiled.json new file mode 100644 index 000000000000..547cbde046f3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/succeed"}],"key":"hoard_location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"hoard_location","targeting_strategy":"/datum/targeting_strategy/parrot_hoard_location","target_source":"/datum/target_source/oview","vision_range":"hoard_location_range"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_is_holding_item","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"hoard_location","required_dist":1},{"type":"/datum/bt_node/ai_behavior/drop_all_held_items"}]}],"observer_abort":1,"key":"Literally me"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"hoard_item_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/parrot","target_key":"hoard_item_target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"hoard_item_target"}]}],"observer_abort":1,"key":"hoard_item_target"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/parrot_hoard_item","target_key":"hoard_item_target"}],"chance":0.05}]}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.compiled.json new file mode 100644 index 000000000000..5cfd1694daf2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"perch_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/ally_mob"}],"chance":0.5},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"perch_target","target_source":"/datum/target_source/oview_typed/from_bb_key/parrot_perch_types","targeting_strategy":"/datum/targeting_strategy/anything"}]}],"key":"perch_target","invert":true},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"perch_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/perch_on_target","target_key":"perch_target"}]}],"observer_abort":1,"invert":false,"key":"perch_target"}]},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"perch_target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin.bt.compiled.json new file mode 100644 index 000000000000..8e5adef45d2e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/move_to_and_hunt","bindings":{"bvtz06kb":"BB_fishing_target","b3y599q4":"BB_fishing_target","brrasnah":"BB_fishing_target","b3cnse9r":1,"bd1towgc":0,"bqwjf4id":450,"bm3y5m55":"BB_fishing_timer"}},{"type":"/datum/bt_node/subtree/move_to_and_hunt","bindings":{"bvtz06kb":"BB_drillable_ice","b3y599q4":"BB_drillable_ice","brrasnah":"BB_drillable_ice","b3cnse9r":1,"bd1towgc":1,"bqwjf4id":150,"bm3y5m55":"BB_ice_drilling_timer"}},{"type":"/datum/bt_node/subtree/move_to_and_hunt"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/penguin"}},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_fishing_target","target_source":"/datum/target_source/range_turfs/typecache_visible/ice","targeting_strategy":"/datum/targeting_strategy/fishing"}],"cooldown_key":"BB_fishing_timer"}],"cooldown_key":"BB_next_food_eat"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_drillable_ice","target_source":"/datum/target_source/range_turfs/typecache_visible/ice","targeting_strategy":"/datum/targeting_strategy/drillable_ice"}],"cooldown_key":"BB_ice_drilling_timer"}],"invert":true,"key":"BB_fishing_target"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/penguin_egg","targeting_strategy":"/datum/targeting_strategy/uncarried_egg"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin_baby.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin_baby.bt.compiled.json new file mode 100644 index 000000000000..74c13f8600fd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin_baby.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_mom","required_dist":1},{"type":"/datum/bt_node/ai_behavior/look_to_parent","parent_key":"BB_found_mom"}]}],"cooldown_key":"BB_parent_emote_cooldown","cooldown_duration":60}],"observer_abort":2,"key":"BB_found_mom"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/ai_behavior/find_mom","mom_types_key":"BB_find_mom_types","ignore_types_key":"BB_ignore_mom_types","found_mom_key":"BB_found_mom"},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/penguin"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.compiled.json new file mode 100644 index 000000000000..3290bdc5f1fa --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"friendly_cultist","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"friendly_cultist","befriend_message":"friendly_message"}]}],"key":"friendly_cultist","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"occupied_rune","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/activate_rune"}]}],"key":"occupied_rune","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"dead_cultist","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/drag_target","target_key":"dead_cultist"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"nearby_rune","required_dist":0,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"dead_cultist"}]}],"key":"dead_cultist","observer_abort":3},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"friendly_cultist","target_source":"/datum/target_source/oview_single_type/carbon_mob","targeting_strategy":"/datum/targeting_strategy/befriendable_cultist","vision_range":9,"time_between_perform":50},{"type":"/datum/bt_node/ai_behavior/find_occupied_rune"},{"type":"/datum/bt_node/ai_behavior/find_dead_cultist"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/sloth.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/sloth.bt.compiled.json new file mode 100644 index 000000000000..814ac30820ad --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/sloth.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/subtree/climb_tree"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_climbed_tree","target_source":"/datum/target_source/oview_single_type/flora_tree","targeting_strategy":"/datum/targeting_strategy/anything"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/revolutionary.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/revolutionary.bt.compiled.json new file mode 100644 index 000000000000..e717c859fade --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/revolutionary.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_hostile_combat","bindings":{"bbrbyj7y":"/datum/bt_node/ai_behavior/random_speech_blackboard"}}]} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.compiled.json new file mode 100644 index 000000000000..311240eadb5d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/ai_behavior/wait"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_lightning_strike","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_cybersun_barrage","target_key":"Current Target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/dark_wizard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/dark_wizard.bt.compiled.json new file mode 100644 index 000000000000..a67dcbcb128d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/dark_wizard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","time_between_perform":6.0,"target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target","approach_movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/fleshblob.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/fleshblob.bt.compiled.json new file mode 100644 index 000000000000..8a767cda2251 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/fleshblob.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/living_floor.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/living_floor.bt.compiled.json new file mode 100644 index 000000000000..42ff9194db7f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/living_floor.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"target_key":"Current Target","maximum_distance":0}],"success_policy":1,"failure_policy":1}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","vision_range":2}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/mad_piano.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mad_piano.bt.compiled.json new file mode 100644 index 000000000000..0643dd9bec54 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mad_piano.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat","bindings":{"bp3p5vvb":80}} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.compiled.json new file mode 100644 index 000000000000..c3c0eb4d8492 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","key":"BB_current_hunting_target","targeting_strategy":"/datum/targeting_strategy/anything","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/move_to_target","key":"BB_current_hunting_target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_current_hunting_target"}]}],"key":"BB_GUNMIMIC_GUN_EMPTY","invert":true}],"observer_abort":3,"key":"BB_current_hunting_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest","key":"BB_current_hunting_target","targeting_strategy":"/datum/targeting_strategy/anything","target_source":"/datum/target_source/animatable_objects","vision_range":7,"revalidation_mode":3}],"key":"BB_GUNMIMIC_GUN_EMPTY","invert":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.compiled.json new file mode 100644 index 000000000000..68d1b491e28f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":0,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.compiled.json new file mode 100644 index 000000000000..b9fb9ac2f16e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/simple_hostile_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.compiled.json new file mode 100644 index 000000000000..da1e8d7c186c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/avoid_friendly_fire","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_GUNMIMIC_GUN_EMPTY","invert":true},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/skeleton.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/skeleton.bt.compiled.json new file mode 100644 index 000000000000..dc27d1b52653 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/skeleton.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"rattles"}],"chance":0.2}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman.bt.compiled.json new file mode 100644 index 000000000000..8a767cda2251 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.compiled.json new file mode 100644 index 000000000000..6d376bbd6511 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_ranged_combat","bindings":{"bpnckxev":50}} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.compiled.json new file mode 100644 index 000000000000..3b31b3414591 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_targeted_spell","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_secondary_spell","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_blink_spell","target_key":"Current Target"}]}],"cooldown_key":"BB_wizard_spell_cooldown","cooldown_duration":10}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"invert":false,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie.bt.compiled.json new file mode 100644 index 000000000000..83e682656a60 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.compiled.json new file mode 100644 index 000000000000..9c3ea45a3502 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/simple_hostile_combat"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.compiled.json new file mode 100644 index 000000000000..28e715a7375e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/feed_on_slime_target","target_key":"BB_current_pet_target"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/modules/mob/living/basic/slime/ai/slime.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/slime/ai/slime.bt.compiled.json new file mode 100644 index 000000000000..67621bb800c5 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/slime/ai/slime.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_slime_evolve"}],"ability_key":"BB_slime_evolve"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_slime_reproduce"}],"observer_abort":2,"ability_key":"BB_slime_reproduce"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/feed_on_slime_target","target_key":"BB_slime_eat_target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"BB_slime_eat_target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_slime_eat_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"BB_slime_eat_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/feed_on_slime_target","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":false},{"type":"/datum/bt_node/decorator/slime_wants_to_eat","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_slime_eat_target","targeting_strategy":"/datum/targeting_strategy/slime_food","target_source":"/datum/target_source/oview_living_no_slimes","vision_range":7,"must_be_reachable":true}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/change_slime_face"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/snails/snail.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/snails/snail.bt.compiled.json new file mode 100644 index 000000000000..997827fa395f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/snails/snail.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"snail_retreat_ability"}],"success_policy":1,"failure_policy":1}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/ant.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/ant.bt.compiled.json new file mode 100644 index 000000000000..69269d3f36fd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/ant.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/bear/bear.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/bear/bear.bt.compiled.json new file mode 100644 index 000000000000..8c4e6dfba042 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/bear/bear.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/subtree/climb_tree"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_honey","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/find_hive","target_key":"BB_found_honey","cooldown_key":"BB_bear_hive_cooldown","hunt_cooldown":50}]}],"key":"BB_found_honey","observer_abort":3},{"type":"/datum/bt_node/decorator/is_dragging","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/pull_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_bear_honeycomb_cooldown","hunt_cooldown":50}]}],"key":"BB_low_priority_hunting_target","observer_abort":3}],"observer_abort":1,"invert":true},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_climbed_tree","target_source":"/datum/target_source/oview_single_type/flora_tree","targeting_strategy":"/datum/targeting_strategy/anything"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_found_honey","target_source":"/datum/target_source/oview_single_type/beehive","targeting_strategy":"/datum/targeting_strategy/stocked_beehive","vision_range":10}],"cooldown_key":"BB_bear_hive_cooldown"},{"type":"/datum/bt_node/decorator/is_dragging","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/honeycomb","targeting_strategy":"/datum/targeting_strategy/huntable","vision_range":10}],"cooldown_key":"BB_bear_honeycomb_cooldown"}],"invert":true},{"type":"/datum/bt_node/ai_behavior/random_speech/bear"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.compiled.json new file mode 100644 index 000000000000..50293ab32886 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"$be5c4p10"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/carp_flee","bindings":{"bf3k9q2p":"$b7cm2s3z"}},{"type":"$bc0mb4t9"}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"$bid1e6k3"},{"type":"/datum/bt_node/subtree/random_walk"}]}]},{"type":"$bf1nd7r2"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_magicarp_spell_target","ability_key":"BB_magicarp_spell","target_key":"BB_magicarp_spell_target","targeting_strategy_key":"targeting_strategy"}],"key":"BB_magicarp_spell"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"__bindings":{"be5c4p10":{"label":"escape_tree","default":"/datum/bt_node/subtree"},"bf1nd7r2":{"label":"find_targets_tree","default":"/datum/bt_node/subtree/carp_target_selection"},"bc0mb4t9":{"label":"combat_tree","default":"/datum/bt_node/subtree/carp_combat"},"bid1e6k3":{"label":"idle_tree","default":"/datum/bt_node/subtree/carp_migration"},"b7cm2s3z":{"label":"flee_from_key","default":"Current Target"}}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp.bt.compiled.json new file mode 100644 index 000000000000..f1ea869828c9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_carp_tree","bindings":{"be5c4p10":"/datum/bt_node/subtree/escape_captivity"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.compiled.json new file mode 100644 index 000000000000..b44fb494abd2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"BB_magicarp_spell","target_key":"BB_magicarp_spell_target"}],"ability_key":"BB_magicarp_spell"}],"key":"BB_magicarp_spell_target"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target","time_between_perform":15.0},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/make_carp_rift/towards/aggressive","ability_key":"BB_carp_rift","target_key":"Current Target"}],"ability_key":"BB_carp_rift"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.compiled.json new file mode 100644 index 000000000000..b50eacda3733 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/carp_should_flee","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/make_carp_rift/away","ability_key":"BB_carp_rift","target_key":"Current Target"}],"ability_key":"BB_carp_rift"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/find_flee_location","target_key":"$bf3k9q2p","hiding_location_key":"Current Target Hiding Location","destination_key":"BB_flee_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_flee_location","required_dist":0,"finish_on_arrival":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"target_key":"Current Target"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.compiled.json new file mode 100644 index 000000000000..3d9462e4bbaa --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_carp_rift_shortcut/nearby","destination_key":"BB_carp_rift_destination"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_carp_rift_destination","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_carp_rift_destination"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/carp_path_blocked","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/make_carp_rift/towards/unvalidated","ability_key":"BB_carp_rift","target_key":"BB_carp_migration_target"}],"ability_key":"BB_carp_rift"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"BB_carp_migration_target","time_between_perform":15.0}]}],"target_key":"BB_carp_migration_target"},{"type":"/datum/bt_node/ai_behavior/succeed"}]},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_carp_migration_target","required_dist":3,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/clear_arrived_migration_target","target_key":"BB_carp_migration_target"}]}],"key":"BB_carp_migration_target"},{"type":"/datum/bt_node/ai_behavior/find_next_carp_migration_step","path_key":"BB_carp_migration_path","target_key":"BB_carp_migration_target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.compiled.json new file mode 100644 index 000000000000..56f25cf96ecb --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_carp_tree","bindings":{"bf1nd7r2":"/datum/bt_node/subtree/carp_passive_selection","b7cm2s3z":"BB_basic_flee_target"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.compiled.json new file mode 100644 index 000000000000..71ffcc900036 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location","check_faction":false}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.compiled.json new file mode 100644 index 000000000000..736b43c21acb --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_carp_tree","bindings":{"bf1nd7r2":"/datum/bt_node/subtree/carp_retaliate_selection","bc0mb4t9":"/datum/bt_node/subtree/carp_retaliate_selection","bid1e6k3":"/datum/bt_node/subtree"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.compiled.json new file mode 100644 index 000000000000..f1ea869828c9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_carp_tree","bindings":{"be5c4p10":"/datum/bt_node/subtree/escape_captivity"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.compiled.json new file mode 100644 index 000000000000..c9ac61faf9b8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":false} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.compiled.json new file mode 100644 index 000000000000..4727d5ac7e33 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"key":"BB_basic_stop_fleeing","invert":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/cat_butcherer.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/cat_butcherer.bt.compiled.json new file mode 100644 index 000000000000..3594068e0ce9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/cat_butcherer.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat_with_retaliate"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/changeling/headslug.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/changeling/headslug.bt.compiled.json new file mode 100644 index 000000000000..9dbb6fb090f0 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/changeling/headslug.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/random_walk"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.compiled.json new file mode 100644 index 000000000000..0889c9e9f359 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_target_facing_turf","target_key":"Current Target","set_key":"BB_glare_position"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_glare_position","required_dist":0},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_glare_ability","target_key":"Current Target"}]}],"observer_abort":3,"ability_key":"BB_glare_ability"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_blind_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/heal_eye_damage","target_key":"BB_blind_target"}]}],"observer_abort":3,"key":"BB_blind_target"},{"type":"/datum/bt_node/subtree/move_to_and_hunt","bindings":{"bqwjf4id":20,"b3cnse9r":1}},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_blind_target","target_source":"/datum/target_source/oview_single_type/carbon_mob","targeting_strategy":"/datum/targeting_strategy/damaged_eyes","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/carrot","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":6}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/faithless.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/faithless.bt.compiled.json new file mode 100644 index 000000000000..4bfe6e5b1d87 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/faithless.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"BB_low_priority_hunting_target","always_reset_target":true}]}],"observer_abort":3,"key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_typed/from_bb_key/hunt_target_list","targeting_strategy":"/datum/targeting_strategy/unbroken_light","vision_range":7},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/faithless"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/garden_gnome.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/garden_gnome.bt.compiled.json new file mode 100644 index 000000000000..73f73c702cb7 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/garden_gnome.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":"$b2jvnm5d"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/random_speech/garden_gnome"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/ghost.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/ghost.bt.compiled.json new file mode 100644 index 000000000000..15c3371800ee --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/ghost.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_retaliate_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.compiled.json new file mode 100644 index 000000000000..5e3ae60adf83 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/relay_to_hive_partner"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_hive_partner","target_source":"/datum/target_source/oview_single_type/hivebot","targeting_strategy":"/datum/targeting_strategy/living_not_dead","vision_range":10}],"chance":0.1}],"success_policy":0,"failure_policy":0,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.compiled.json new file mode 100644 index 000000000000..80226ce0624a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_machine_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/repair_machines","target_key":"BB_machine_target"}]}],"observer_abort":2,"key":"BB_machine_target"},{"type":"/datum/bt_node/subtree/relay_to_hive_partner"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_machine_target","target_source":"/datum/target_source/oview_single_type/machine","targeting_strategy":"/datum/targeting_strategy/damaged_machine","vision_range":10},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_hive_partner","target_source":"/datum/target_source/oview_single_type/hivebot","targeting_strategy":"/datum/targeting_strategy/living_not_dead","vision_range":10}],"key":"BB_hive_partner","invert":true}],"chance":0.1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.compiled.json new file mode 100644 index 000000000000..768d4f7c1f9c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","time_between_perform":30,"avoid_friendly_fire":true}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/relay_to_hive_partner"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_hive_partner","target_source":"/datum/target_source/oview_single_type/hivebot","targeting_strategy":"/datum/targeting_strategy/living_not_dead","vision_range":10}],"key":"BB_hive_partner","invert":true}],"chance":0.1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.compiled.json new file mode 100644 index 000000000000..54c129760abd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","time_between_perform":15.0,"avoid_friendly_fire":true}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/relay_to_hive_partner"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_hive_partner","target_source":"/datum/target_source/oview_single_type/hivebot","targeting_strategy":"/datum/targeting_strategy/living_not_dead","vision_range":10}],"key":"BB_hive_partner","invert":true}],"chance":0.1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.compiled.json new file mode 100644 index 000000000000..7f515ce5ab4c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_hive_partner","required_dist":1},{"type":"/datum/bt_node/ai_behavior/relay_message","target_key":"BB_hive_partner"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_hive_partner"}]}],"observer_abort":2,"key":"BB_hive_partner"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/killer_tomato.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/killer_tomato.bt.compiled.json new file mode 100644 index 000000000000..80638d2c1c57 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/killer_tomato.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat","bindings":{"bqul7l8t":"/datum/bt_node/subtree/random_speech_loop"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/lightgeist.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/lightgeist.bt.compiled.json new file mode 100644 index 000000000000..8a767cda2251 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/lightgeist.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.compiled.json new file mode 100644 index 000000000000..d7f083f2c7d3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_meteor_ground_spikes","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_meteor_spine_traps"}]}],"success_policy":1,"failure_policy":1}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/ai_behavior/meteor_heart_deaggro"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/morph.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/morph.bt.compiled.json new file mode 100644 index 000000000000..3594068e0ce9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/morph.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat_with_retaliate"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/mushroom.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/mushroom.bt.compiled.json new file mode 100644 index 000000000000..a3ff530bcf92 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/mushroom.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_hunt","bindings":{"bvtz06kb":"BB_low_priority_hunting_target","b3y599q4":"BB_low_priority_hunting_target","brrasnah":"BB_low_priority_hunting_target","bd1towgc":1,"b3cnse9r":1}},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_single_type/mushroom_food","vision_range":6}],"key":"BB_low_priority_hunting_target","invert":true}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.compiled.json new file mode 100644 index 000000000000..6e28b2abdab3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_summon_mimics","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_summon_minions"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/find_paper_and_write"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_found_paper","target_source":"/datum/target_source/oview_single_type/paper","targeting_strategy":"/datum/targeting_strategy/empty_paper","time_between_perform":100}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.compiled.json new file mode 100644 index 000000000000..2c2d16007fc6 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_raise_horde_ability","target_key":"BB_basic_flee_target"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"BB_basic_flee_target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"BB_basic_flee_target","targeting_strategy":"targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_basic_flee_target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"},{"type":"/datum/bt_node/ai_behavior/use_mob_ability/domain","ability_key":"BB_domain_ability"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/roro.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/roro.bt.compiled.json new file mode 100644 index 000000000000..d94250485b84 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/roro.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_retaliate_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/banded.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/banded.bt.compiled.json new file mode 100644 index 000000000000..81aa18815bbd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/banded.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/snake.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/snake.bt.compiled.json new file mode 100644 index 000000000000..060d0fd30425 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/snake.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spaceman.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spaceman.bt.compiled.json new file mode 100644 index 000000000000..fa7d52228081 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spaceman.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/simple_retaliate_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.compiled.json new file mode 100644 index 000000000000..cfd354a3a885 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_spider_web_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/spin_web","action_key":"BB_spider_web_action","target_key":"BB_spider_web_target"}]}],"observer_abort":3,"key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_unwebbed_turf","target_key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.compiled.json new file mode 100644 index 000000000000..69b4956b49ca --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}}],"observer_abort":2,"key":"BB_basic_flee_target"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_spider_web_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/spin_web","action_key":"BB_spider_web_action","target_key":"BB_spider_web_target"}]}],"observer_abort":3,"key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_unwebbed_turf","target_key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.compiled.json new file mode 100644 index 000000000000..7fbac82cdd81 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.compiled.json new file mode 100644 index 000000000000..16995a0eb626 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_spider_web_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/spin_web","action_key":"BB_spider_web_action","target_key":"BB_spider_web_target"}]}],"observer_abort":3,"key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_unwebbed_turf","target_key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.compiled.json new file mode 100644 index 000000000000..6989bddc89c9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/consider_venting"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"invert":false,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_entry_vent_target","target_source":"/datum/target_source/oview_single_type/vent_pump","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":7}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.compiled.json new file mode 100644 index 000000000000..7ce03eefeee0 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"Current Target"}},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_spider_web_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/spin_web","action_key":"BB_spider_web_action","target_key":"BB_spider_web_target"}]}],"observer_abort":3,"key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_unwebbed_turf","target_key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.compiled.json new file mode 100644 index 000000000000..3cc7fd46b43b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/face_target_or_face_initial","target_key":"Current Target"}],"observer_abort":2,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/statue.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/statue.bt.compiled.json new file mode 100644 index 000000000000..daa9321f79c2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/statue.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","movement_failed":false},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"BB_low_priority_hunting_target","hunt_cooldown":100,"always_reset_target":true}]}],"observer_abort":3,"key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"}],"observer_abort":3,"invert":true,"key":"BB_low_priority_hunting_target"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_typed/from_bb_key/hunt_target_list","targeting_strategy":"/datum/targeting_strategy/unbroken_light","vision_range":7}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.compiled.json new file mode 100644 index 000000000000..39d4a08e4edc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/ai_behavior/wait"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","vision_range":14},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"scream"}],"chance":0.05}],"observer_abort":0,"key":"Current Target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/supermatter_spider.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/supermatter_spider.bt.compiled.json new file mode 100644 index 000000000000..1b78b4ebaf44 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/supermatter_spider.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat","bindings":{"b95z0f0c":"/datum/bt_node/subtree/random_speech_loop"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.compiled.json new file mode 100644 index 000000000000..6c6daa9752af --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_fugu_inflate","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions/attack_turfs","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/run_away_from_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/stoats/stoat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/stoats/stoat.bt.compiled.json new file mode 100644 index 000000000000..8e3ac45c42c9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/stoats/stoat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":0,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/decorator/is_dragging","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_LAST_STOLEN_ITEM"}}],"observer_abort":1},{"type":"/datum/bt_node/subtree/steal_and_flee"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/subtree/find_stealable_object"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/subtree/find_partner"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/trader/trader.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trader/trader.bt.compiled.json new file mode 100644 index 000000000000..84e0bf02a83d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trader/trader.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","avoid_friendly_fire":true,"time_between_perform":30}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_first_customer","required_dist":1}],"key":"BB_rush_to_sell","value":true},{"type":"/datum/bt_node/ai_behavior/succeed"}]},{"type":"/datum/bt_node/ai_behavior/setup_shop"}]}],"observer_abort":2,"key":"BB_first_customer"}],"observer_abort":0,"invert":true,"key":"BB_shop_spot"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk/not_while_on_target","target_key":"BB_shop_spot"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"BB_first_customer","target_source":"/datum/target_source/oview","targeting_strategy":"/datum/targeting_strategy/conscious_human","revalidation_mode":1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/tree.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/tree.bt.compiled.json new file mode 100644 index 000000000000..1b78b4ebaf44 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/tree.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat","bindings":{"b95z0f0c":"/datum/bt_node/subtree/random_speech_loop"}} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/burst.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/burst.bt.compiled.json new file mode 100644 index 000000000000..a2f6923b6f4d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/burst.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/trooper_ranged","bindings":{"b4kar3zl":30,"bnr1aazo":1}} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/peaceful.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/peaceful.bt.compiled.json new file mode 100644 index 000000000000..1f531427f00b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/peaceful.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements","target_key":"Call reinforcements target"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true}],"observer_abort":2,"key":"Call reinforcements target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Call reinforcements target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/peaceful_burst.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/peaceful_burst.bt.compiled.json new file mode 100644 index 000000000000..52da7f12e760 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/peaceful_burst.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements","target_key":"Call reinforcements target"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true}],"observer_abort":2,"key":"Call reinforcements target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","time_between_perform":30,"avoid_friendly_fire":true}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Call reinforcements target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/ranged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/ranged.bt.compiled.json new file mode 100644 index 000000000000..eb5c181ecd68 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/trooper_ranged","bindings":{"b4kar3zl":10,"beslksyc":5,"bnr1aazo":1}} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/shotgunner.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/shotgunner.bt.compiled.json new file mode 100644 index 000000000000..dbcb92512680 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/shotgunner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/trooper_ranged","bindings":{"b4kar3zl":30,"beslksyc":3,"bnr1aazo":1}} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/trooper.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/trooper.bt.compiled.json new file mode 100644 index 000000000000..644c7eefb550 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/trooper.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/trooper_ranged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/trooper_ranged.bt.compiled.json new file mode 100644 index 000000000000..e60f01c3b793 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/trooper_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","time_between_perform":"$b4kar3zl","max_range":"$beslksyc","avoid_friendly_fire":"$bnr1aazo"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"__bindings":{"b4kar3zl":{"label":"time_between_perform","default":0},"beslksyc":{"label":"max_range","default":3},"bnr1aazo":{"label":"avoid_friendly_fire","default":0}}} diff --git a/build/behavior_trees/modules/mob/living/basic/turtle/turtle.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/turtle/turtle.bt.compiled.json new file mode 100644 index 000000000000..2a5a1630c166 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/turtle/turtle.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_generic_action"}],"observer_abort":2,"ability_key":"BB_generic_action"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"turtle_headbutt_victim","required_dist":1},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"turtle_headbutt_cooldown","cooldown_duration":600},{"type":"/datum/bt_node/ai_behavior/hunt_target/headbutt_leg","target_key":"turtle_headbutt_victim","always_reset_target":true}]}],"observer_abort":2,"key":"turtle_headbutt_victim"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"turtle_flora_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"turtle_flora_cooldown","cooldown_duration":600},{"type":"/datum/bt_node/ai_behavior/hunt_target/sniff_flora","target_key":"turtle_flora_target","always_reset_target":true}]}],"observer_abort":2,"key":"turtle_headbutt_victim"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk","bindings":{"bf07i8ep":10}}]},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"turtle_headbutt_victim","target_source":"/datum/target_source/oview_typed/from_bb_key/turtle_headbutt_types","targeting_strategy":"/datum/targeting_strategy/legged_conscious_human"}],"cooldown_key":"turtle_headbutt_cooldown"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"turtle_flora_target","target_source":"/datum/target_source/oview_typed/from_bb_key/turtle_flora_types","targeting_strategy":"/datum/targeting_strategy/sniffable_hydro"}],"cooldown_key":"turtle_flora_cooldown"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/express_happiness"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/axolotl.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/axolotl.bt.compiled.json new file mode 100644 index 000000000000..9dbb6fb090f0 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/axolotl.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/random_walk"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/butterfly.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/butterfly.bt.compiled.json new file mode 100644 index 000000000000..9dbb6fb090f0 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/butterfly.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/random_walk"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach.bt.compiled.json new file mode 100644 index 000000000000..49087f9c3453 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_roach_hunt_cooldown","hunt_cooldown":50,"always_reset_target":true}]}],"key":"BB_low_priority_hunting_target","observer_abort":3},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/ants","targeting_strategy":"/datum/targeting_strategy/huntable","vision_range":2}],"cooldown_key":"BB_roach_hunt_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.compiled.json new file mode 100644 index 000000000000..509de755c2e4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_roach_hunt_cooldown","hunt_cooldown":50,"always_reset_target":true}]}],"key":"BB_low_priority_hunting_target","observer_abort":3},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/ants","targeting_strategy":"/datum/targeting_strategy/huntable","vision_range":2}],"cooldown_key":"BB_roach_hunt_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.compiled.json new file mode 100644 index 000000000000..458fb780ce9f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_roach_hunt_cooldown","hunt_cooldown":50,"always_reset_target":true}]}],"key":"BB_low_priority_hunting_target","observer_abort":3},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/ants","targeting_strategy":"/datum/targeting_strategy/huntable","vision_range":2}],"cooldown_key":"BB_roach_hunt_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.compiled.json new file mode 100644 index 000000000000..d3b96084f49a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/mobroach","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_roach_hunt_cooldown","hunt_cooldown":50,"always_reset_target":true}]}],"key":"BB_low_priority_hunting_target","observer_abort":3},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/ants","targeting_strategy":"/datum/targeting_strategy/huntable","vision_range":2}],"cooldown_key":"BB_roach_hunt_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/crab.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/crab.bt.compiled.json new file mode 100644 index 000000000000..025b68b9301f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/crab.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/go_for_swim"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"key":"BB_basic_stop_fleeing","invert":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"swim_alternate_turf","targeting_strategy":"/datum/targeting_strategy/walkable_turf","target_source":"/datum/target_source/oview_water_turfs"}],"cooldown_key":"key_swimmer_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/crab"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/eat_cable.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/eat_cable.bt.compiled.json new file mode 100644 index 000000000000..74aa222412f0 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/eat_cable.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_mouse_wants_to_eat_cable"},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_mouse_cable_hunt_cooldown","hunt_cooldown":200}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/cable","targeting_strategy":"/datum/targeting_strategy/accessible_cable","vision_range":0}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"BB_low_priority_hunting_target"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/eat_cheese.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/eat_cheese.bt.compiled.json new file mode 100644 index 000000000000..235e587e3b97 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/eat_cheese.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"BB_current_hunting_target","cooldown_key":"BB_mouse_cheese_hunt_cooldown","hunt_cooldown":200}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_single_type/cheese","vision_range":1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"BB_current_hunting_target"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/frog.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/frog.bt.compiled.json new file mode 100644 index 000000000000..4f860c1f6df4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/frog.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/frog_engage_target"},{"type":"/datum/bt_node/subtree/go_for_swim"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"swim_alternate_turf","targeting_strategy":"/datum/targeting_strategy/walkable_turf","target_source":"/datum/target_source/oview_water_turfs"}],"cooldown_key":"key_swimmer_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/frog"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/frog_engage_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/frog_engage_target.bt.compiled.json new file mode 100644 index 000000000000..3df4a0ae1111 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/frog_engage_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_has_trait","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/find_flee_location","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location","destination_key":"BB_flee_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_flee_location","required_dist":0,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","trait":"scary_fisherman"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/lizard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/lizard.bt.compiled.json new file mode 100644 index 000000000000..3231de256acf --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/lizard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/lizard"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/mothroach/mothroach.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/mothroach/mothroach.bt.compiled.json new file mode 100644 index 000000000000..426dcea21279 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/mothroach/mothroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/override_id_set","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"}],"observer_abort":2,"override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/random_speech/mothroach"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/mouse.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/mouse.bt.compiled.json new file mode 100644 index 000000000000..ea00144350e9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/mouse.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/override_id_set","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"}],"observer_abort":2,"override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/eat_cheese"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","target_source":"/datum/target_source/oview_single_type/cheese","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":1}],"cooldown_key":"BB_mouse_cheese_hunt_cooldown"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/play_instrument_on_floor"},{"type":"/datum/bt_node/subtree/eat_cable"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/random_walk"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","target_source":"/datum/target_source/oview_single_type/cheese","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":1}],"cooldown_key":"BB_mouse_cheese_hunt_cooldown"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_SONG_INSTRUMENT","target_source":"/datum/target_source/oview_single_type/piano_synth","targeting_strategy":"/datum/targeting_strategy/playable_synthesizer"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/cable","targeting_strategy":"/datum/targeting_strategy/accessible_cable","vision_range":0}],"key":"BB_mouse_wants_to_eat_cable"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/mouse"}},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_mouse_wants_to_eat_cable","value":true}],"chance":0.01}],"cooldown_key":"BB_mouse_cable_hunt_cooldown"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/mouse_rat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/mouse_rat.bt.compiled.json new file mode 100644 index 000000000000..b74136eee82b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/mouse_rat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/decorator/override_id_set","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"}],"observer_abort":2,"override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":0,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/eat_cheese"},{"type":"/datum/bt_node/subtree/eat_cable"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","target_source":"/datum/target_source/oview_single_type/cheese","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":1}],"cooldown_key":"BB_mouse_cheese_hunt_cooldown"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/cable","targeting_strategy":"/datum/targeting_strategy/accessible_cable","vision_range":0}],"key":"BB_mouse_wants_to_eat_cable"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/mouse"}},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_mouse_wants_to_eat_cable","value":true}],"chance":0.01}],"cooldown_key":"BB_mouse_cable_hunt_cooldown"}]}],"success_policy":0,"failure_policy":0,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.compiled.json new file mode 100644 index 000000000000..04e34b375de1 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/keep_playing_instrument","song_instrument_key":"BB_SONG_INSTRUMENT"}],"observer_abort":1,"maximum_distance":1,"require_reach":true},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_SONG_INSTRUMENT","required_dist":1},{"type":"/datum/bt_node/ai_behavior/setup_instrument","song_instrument_key":"BB_SONG_INSTRUMENT","song_lines_key":"song_lines"},{"type":"/datum/bt_node/ai_behavior/play_instrument","song_instrument_key":"BB_SONG_INSTRUMENT","volume":50}]}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_SONG_INSTRUMENT","target_source":"/datum/target_source/oview_single_type/piano_synth","targeting_strategy":"/datum/targeting_strategy/playable_synthesizer"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"BB_SONG_INSTRUMENT"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/space_bat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/space_bat.bt.compiled.json new file mode 100644 index 000000000000..d94250485b84 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/space_bat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_retaliate_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/suicide_frog.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/suicide_frog.bt.compiled.json new file mode 100644 index 000000000000..232470affa5b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/suicide_frog.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/trash.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/trash.bt.compiled.json new file mode 100644 index 000000000000..f663b3d4ce78 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/trash.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/frog_engage_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/random_speech/frog"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm index ec1a3ebbb134..d7337ed66951 100644 --- a/code/__DEFINES/MC.dm +++ b/code/__DEFINES/MC.dm @@ -144,10 +144,3 @@ /datum/controller/subsystem/ai_controllers/##X/fire() {..() /*just so it shows up on the profiler*/} \ /datum/controller/subsystem/ai_controllers/##X -#define UNPLANNED_CONTROLLER_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/unplanned_controllers/##X);\ -/datum/controller/subsystem/unplanned_controllers/##X/New(){\ - NEW_SS_GLOBAL(SS##X);\ - PreInit();\ -}\ -/datum/controller/subsystem/unplanned_controllers/##X/fire() {..() /*just so it shows up on the profiler*/} \ -/datum/controller/subsystem/unplanned_controllers/##X diff --git a/code/__DEFINES/ai/ai.dm b/code/__DEFINES/ai/ai.dm index a6724aeb4aae..7711efb42d03 100644 --- a/code/__DEFINES/ai/ai.dm +++ b/code/__DEFINES/ai/ai.dm @@ -1,16 +1,36 @@ -#define GET_AI_BEHAVIOR(behavior_type) SSai_behaviors.ai_behaviors[behavior_type] -#define GET_TARGETING_STRATEGY(targeting_type) SSai_behaviors.targeting_strategies[targeting_type] -#define GET_TARGET_PRIORITY_STRATEGY(targeting_type) SSai_behaviors.target_priority_strategies[targeting_type] +#define GET_TARGETING_STRATEGY(targeting_type) SSai_controllers.targeting_strategies[targeting_type] +#define GET_TARGET_PRIORITY_STRATEGY(targeting_type) SSai_controllers.target_priority_strategies[targeting_type] +#define GET_TARGET_SOURCE(source_type) SSai_controllers.target_sources[source_type] + +/** + * Returns TRUE if the target should be rejected based on factions. + * Inlined faction check for targeting strategies but with less proc overhead. + * Strategies with fully custom faction logic set custom_faction_check and override faction_check() instead. + */ +#define TARGETING_FACTION_CHECK(strategy, controller, living_mob, the_target) \ + (((strategy).ignore_faction || (controller).blackboard[BB_ALWAYS_IGNORE_FACTION] || (controller).blackboard[BB_TEMPORARILY_IGNORE_FACTION]) \ + ? (strategy).invert_faction_check \ + : ((living_mob).faction_check_atom((the_target), exact_match = (strategy).check_factions_exactly) \ + ? !(strategy).invert_faction_check \ + : (strategy).invert_faction_check)) + +// Revalidation modes for /datum/bt_node/ai_behavior/acquire_target +/// If a target is already set, validate it via is_valid_target before searching. Replace if invalid. +#define TARGET_REVALIDATE 1 +/// If a target is already set, return SUCCESS immediately without re-checking. +#define TARGET_KEEP_IF_SET 2 +/// Always run the full candidate search, ignoring any existing target. +#define TARGET_ALWAYS_SEARCH 3 #define HAS_AI_CONTROLLER_TYPE(thing, type) istype(thing?.ai_controller, type) //AI controller flags //If you add a new status, be sure to add it to the ai_controllers subsystem's ai_controllers_by_status list. -///The AI is currently active. +///The AI is currently active, and is planned by the high priority subsystem. #define AI_STATUS_ON "ai_on" -///The AI is currently offline for any reason. +///The AI is currently active, but is planned by the low priority (background) subsystem. +#define AI_STATUS_ON_LOW "ai_on_low" +///The AI is not running. Cancels any active plans if set. #define AI_STATUS_OFF "ai_off" -///The AI is currently in idle mode. -#define AI_STATUS_IDLE "ai_idle" //Flags returned by get_able_to_run() ///pauses AI processing @@ -43,16 +63,6 @@ #define AI_BEHAVIOR_INSTANT (NONE) -///Does this task require movement from the AI before it can be performed? -#define AI_BEHAVIOR_REQUIRE_MOVEMENT (1<<0) -///Does this require the current_movement_target to be adjacent and in reach? -#define AI_BEHAVIOR_REQUIRE_REACH (1<<1) -///Does this task let you perform the action while you move closer? (Things like moving and shooting) -#define AI_BEHAVIOR_MOVE_AND_PERFORM (1<<2) -///Does finishing this task not null the current movement target? -#define AI_BEHAVIOR_KEEP_MOVE_TARGET_ON_FINISH (1<<3) -///Does this behavior NOT block planning? -#define AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION (1<<4) ///AI flags /// Don't move if being pulled @@ -65,19 +75,16 @@ #define CAN_ACT_IN_STASIS (1<<3) /// Continue processing while aggressively grabbed #define CAN_ACT_WHILE_GRABBED (1<<4) +/// Keeps planning on low prio when unwatched +#define RUN_WHILE_UNWATCHED (1<<5) +/// Always plans at high priority. Only works with RUN_WHILE_UNWATCHED +#define ALWAYS_HIGH_PRIORITY (1<<6) /// Flags we expect for most AI controllers #define DEFAULT_AI_FLAGS (PAUSE_DURING_DO_AFTER | CAN_ACT_WHILE_GRABBED) /// Flags for passive mobs that are easy to push around #define PASSIVE_AI_FLAGS (PAUSE_DURING_DO_AFTER | STOP_MOVING_WHEN_PULLED) -//Base Subtree defines - -///This subtree should cancel any further planning, (Including from other subtrees) -#define SUBTREE_RETURN_FINISH_PLANNING 1 - -//Generic subtree defines - /// default search range (tiles, passed to oview) when using find_and_set #define SEARCH_TACTIC_DEFAULT_RANGE 7 /// probability that the pawn should try resisting out of restraints @@ -97,3 +104,7 @@ GLOBAL_LIST_INIT(all_radial_directions, list( "WEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST), "NORTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTHWEST) )) + + +///Use this if you dont want a controller to show up in the sidebar (e.g. when its a class that just sets BB keys) +#define ABSTRACT_AI_CLASS "Abstract" diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index 21ed0c47aa18..ec074de5c2ca 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -1,5 +1,22 @@ //Generic BB keys +///Use this if you need a generic variable for a target; Use this if you don't have multiple different things to target in your ai (PROTIP: YOU BASICALLY NEVER DO!) +#define BB_CURRENT_TARGET "Current Target" +///Use this if you need a generic variable for a hiding location; +#define BB_CURRENT_TARGET_HIDING_LOCATION "Current Target Hiding Location" +///For any battle screech cooldowns +#define BB_BATTLE_SCREECH_COOLDOWN "Battle Screech Cooldown" +///Target for snitching (calling reinforcements on) but dont want to atatck. +#define BB_CALL_REINFORCEMENTS_TARGET "Call reinforcements target" +///Generic target for hunting +#define BB_HUNT_TARGET_LIST "Hunt Target List" +///Target of current movement.alist +#define BB_CURRENT_MOVEMENT_TARGET "Current target movement" +///The pawn controlled by this controller +#define BB_MY_PAWN "Literally me" + +///Cooldown on venting (sus) +#define BB_VENTING_COOLDOWN "Venting Cooldown" #define BB_CURRENT_MIN_MOVE_DISTANCE "min_move_distance" ///time until we should next eat, set by the generic hunger subtree @@ -30,13 +47,17 @@ #define BB_GUILTY_CONSCIOUS_CHANCE "guilty_concious_rate" ///the item we will steal #define BB_ITEM_TO_STEAL "item_to_steal" +///be stoat do crime +#define BB_WANTS_TO_COMMIT_THEFT "BB_WANTS_TO_COMMIT_THEFT" +///last stolen item +#define BB_LAST_STOLEN_ITEM "BB_LAST_STOLEN_ITEM" ///the owner we will try to play with #define BB_OWNER_TARGET "BB_owner_target" ///the list of interactions we can have with the owner #define BB_INTERACTIONS_WITH_OWNER "BB_interactions_with_owner" -///The trait checked by ai_behavior/find_potential_targets/prioritize_trait to return a target with a trait over the rest. +///The trait checked by ai_behavior/update_targets/prioritize_trait to return a target with a trait over the rest. #define BB_TARGET_PRIORITY_TRAIT "target_priority_trait" /// Store a single or list of emotes at this key @@ -48,6 +69,8 @@ #define BB_REINFORCEMENTS_SAY "BB_reinforcements_say" /// Something the mob will remote when calling reinforcements #define BB_REINFORCEMENTS_EMOTE "BB_reinforcements_emote" +/// Does this mob call reinforcements? +#define BB_CALLS_REINFORCEMENTS "BB_calls_reinforcements" ///Turf we want a mob to move to #define BB_TRAVEL_DESTINATION "BB_travel_destination" @@ -56,6 +79,8 @@ #define BB_SONG_INSTRUMENT "BB_SONG_INSTRUMENT" ///song lines blackboard, set by default on controllers #define BB_SONG_LINES "song_lines" +///mob doesn't use hand code so we're assuming its instrument is up its ass +#define BB_INSTRUMENT_UP_ASS "Has its instrument up its ass" ///bane ai used by example script #define BB_BANE_BATMAN "BB_bane_batman" @@ -66,6 +91,12 @@ /// Are we a panicking goose? #define BB_GOOSE_VOMIT_CHANCE "BB_goose_vomit_chance" +/// Set TRUE to suppress a mob's idle wandering (e.g. while under external/deadchat control). +#define BB_DISABLE_IDLE "BB_disable_idle" + +/// Set TRUE on a mob's controller by /datum/component/tameable once it has been tamed. +#define BB_TAMED "BB_tamed" + //Hunting BB keys ///key that holds our current hunting target #define BB_CURRENT_HUNTING_TARGET "BB_current_hunting_target" @@ -73,6 +104,14 @@ #define BB_LOW_PRIORITY_HUNTING_TARGET "BB_low_priority_hunting_target" ///key that holds the cooldown for our hunting subtree #define BB_HUNTING_COOLDOWN(type) "BB_HUNTING_COOLDOWN_[type]" +///cooldown key for the cockroach's ant hunt +#define BB_ROACH_HUNT_COOLDOWN "BB_roach_hunt_cooldown" +///cooldown key for the mouse's cheese hunt +#define BB_MOUSE_CHEESE_HUNT_COOLDOWN "BB_mouse_cheese_hunt_cooldown" +///cooldown key for the mouse's cable hunt +#define BB_MOUSE_CABLE_HUNT_COOLDOWN "BB_mouse_cable_hunt_cooldown" +///Whether we're hungry for a cable +#define BB_MOUSE_WANTS_TO_EAT_CABLE "BB_mouse_wants_to_eat_cable" ///Basic Mob Keys @@ -81,14 +120,13 @@ /// Key used to store the time we can actually attack #define BB_BASIC_MOB_MELEE_COOLDOWN_TIMER "BB_basic_melee_cooldown_timer" -///Targeting subtrees -#define BB_BASIC_MOB_CURRENT_TARGET "BB_basic_current_target" -#define BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION "BB_basic_current_target_hiding_location" #define BB_TARGETING_STRATEGY "targeting_strategy" #define BB_TARGET_PRIORITY_STRATEGY "target_priority_strategy" #define BB_HUNT_TARGETING_STRATEGY "hunt_targeting_strategy" ///some behaviors that check current_target also set this on deep crit mobs #define BB_BASIC_MOB_EXECUTION_TARGET "BB_basic_execution_target" +/// Target atom written by escape-captivity BT decorators (pawn_buckled_to_obj, pawn_contained_in_obj) +#define BB_BASIC_MOB_ESCAPE_TARGET "BB_basic_mob_escape_target" ///Blackboard key for a whitelist typecache of "things we can target while trying to move" #define BB_OBSTACLE_TARGETING_WHITELIST "BB_targeting_whitelist" /// Key for the minimum status at which we want to target mobs (does not need to be specified if CONSCIOUS) @@ -101,6 +139,9 @@ #define BB_BASIC_MOB_IDLE_WALK_CHANCE "BB_basic_idle_walk_chance" #define BB_BASIC_MOB_TARGET_REFRESH_COOLDOWN "BB_basic_mob_target_refresh_cooldown" +/// whether we can have fuck +#define BB_FUCKS "can we fuck?" + /// Minimum range to keep target within #define BB_RANGED_SKIRMISH_MIN_DISTANCE "BB_ranged_skirmish_min_distance" /// Maximum range to keep target within @@ -115,6 +156,8 @@ #define BB_FLEE_TARGETING_STRATEGY "flee_targeting_strategy" #define BB_BASIC_MOB_FLEE_DISTANCE "BB_basic_flee_distance" #define DEFAULT_BASIC_FLEE_DISTANCE 9 +/// Computed flee destination turf set by find_flee_location and consumed by move_to_target +#define BB_FLEE_LOCATION "BB_flee_location" /// Generic key for a non-specific targeted action #define BB_TARGETED_ACTION "BB_TARGETED_action" @@ -189,7 +232,7 @@ ///are we in some kind of temporary state of ignoring factions when targeting? can result in volatile results if multiple behaviours touch this #define BB_TEMPORARILY_IGNORE_FACTION "BB_temporarily_ignore_factions" -///currently only used by clowns, a list of what can the mob speak randomly +///A list of what can the mob speak randomly #define BB_BASIC_MOB_SPEAK_LINES "BB_speech_lines" #define BB_EMOTE_SAY "emote_say" #define BB_EMOTE_HEAR "emote_hear" @@ -224,6 +267,9 @@ ///key that holds the next time we will start fishing #define BB_FISHING_TIMER "BB_fishing_timer" +///key that holds the next time we will start drilling ice +#define BB_ICE_DRILLING_TIMER "BB_ice_drilling_timer" + ///are we ONLY allowed to fish when we're hungry? #define BB_ONLY_FISH_WHILE_HUNGRY "BB_only_fish_while_hungry" @@ -241,7 +287,7 @@ // Keys used by one and only one behavior // Used to hold state without making bigass lists -/// For /datum/ai_behavior/find_potential_targets, what if any field are we using currently +/// For /datum/ai_behavior/update_targets, what if any field are we using currently #define BB_FIND_TARGETS_FIELD(type) "bb_find_targets_field_[type]" ///Currently enraged diff --git a/code/__DEFINES/ai/behavior_trees.dm b/code/__DEFINES/ai/behavior_trees.dm new file mode 100644 index 000000000000..309c4112ebf8 --- /dev/null +++ b/code/__DEFINES/ai/behavior_trees.dm @@ -0,0 +1,72 @@ +/// Maximum number of execution indices the bt_viewer draining log can hold between polls. +#define BT_EXECUTION_LOG_MAX 250 + +// BT node return values +/// Node completed its goal and succeeded +#define BT_SUCCESS 1 +/// Node failed for one reason or the other +#define BT_FAILURE 2 +/// Node has an action running; +#define BT_RUNNING 3 + +// Parallel node completion policies (mutually exclusive per axis) +/// Parallel succeeds when child 1 succeeds (default) +#define BT_PARALLEL_SUCCESS_CHILD_ONE 0 +/// Parallel succeeds only when all children succeed +#define BT_PARALLEL_SUCCESS_ALL 1 +/// Parallel fails when child 1 fails (default) +#define BT_PARALLEL_FAILURE_CHILD_ONE 0 +/// Parallel fails when any child fails +#define BT_PARALLEL_FAILURE_ANY 1 + +/// Subplan propagates BT_SUCCESS when all children succeed (default) +#define BT_SUBPLAN_SUCCEED_ON_SUCCESS 0 +/// Subplan resets and loops (returns BT_RUNNING) when all children succeed +#define BT_SUBPLAN_LOOP_ON_SUCCESS 1 +/// Subplan propagates BT_FAILURE when a child fails (default) +#define BT_SUBPLAN_FAIL_ON_FAILURE 0 +/// Subplan resets and loops (returns BT_RUNNING) when a child fails +#define BT_SUBPLAN_LOOP_ON_FAILURE 1 + +/// No observer abort registered +#define BT_ABORT_NONE 0 +/// Abort this branch when the watched condition becomes FALSE +#define BT_ABORT_SELF (1<<0) +/// Abort lower-priority running behaviors when the watched condition becomes TRUE +#define BT_ABORT_LOWER_PRIORITY (1<<1) +/// Both BT_ABORT_SELF and BT_ABORT_LOWER_PRIORITY +#define BT_ABORT_BOTH (BT_ABORT_SELF | BT_ABORT_LOWER_PRIORITY) + +// BT viewer node type identifiers (stored on bt_node.node_type) +/// Selector composite node +#define BT_NODE_SELECTOR 0 +/// Sequence composite node +#define BT_NODE_SEQUENCE 1 +/// Parallel composite node +#define BT_NODE_PARALLEL 2 +/// Decorator (gate/condition) node +#define BT_NODE_DECORATOR 3 +/// Leaf behavior node +#define BT_NODE_LEAF 4 +/// Subtree container node +#define BT_NODE_SUBTREE 5 +/// Subplan composite node +#define BT_NODE_SUBPLAN 6 + +// Strings used to actually fill the JSONs for behavior trees +/// Key storing the node typepath in a descriptor list +#define BT_DESC_TYPE "type" +/// Key storing the children list in a descriptor list +#define BT_DESC_CHILDREN "children" +/// Key storing the override slot ID in a subtree descriptor +#define BT_DESC_OVERRIDE_ID "override_id" +/// Key storing bindable parameter declarations in a compiled subtree descriptor +#define BT_DESC_BINDINGS "__bindings" + +/// Resolves the compiled JSON path from a behavior tree's source JSON path (e.g. "code/datums/ai/dog/dog.bt.json" -> "build/behavior_trees/datums/ai/dog/dog.bt.compiled.json"). +#define BT_COMPILED_PATH(json_path) (replacetext(replacetext(json_path, "code/", "build/behavior_trees/"), ".json", ".compiled.json")) + +// Runtime subtree IDs. Can be used to override trees at runtime + +/// pet_command ID to override based on given pet command +#define SUBPLAN_ID_PET_COMMAND "pet_command" diff --git a/code/__DEFINES/ai/bot_keys.dm b/code/__DEFINES/ai/bot_keys.dm index 653bace09d8a..f134f088d29f 100644 --- a/code/__DEFINES/ai/bot_keys.dm +++ b/code/__DEFINES/ai/bot_keys.dm @@ -9,6 +9,8 @@ #define BB_SALUTE_MESSAGES "salute_messages" ///the beepsky we will salute #define BB_SALUTE_TARGET "salute_target" +///cooldown key for authority saluting +#define BB_SALUTE_COOLDOWN "salute_cooldown" ///our announcement ability #define BB_ANNOUNCE_ABILITY "announce_ability" ///list of our radio channels @@ -19,8 +21,6 @@ #define BB_BOT_BEACON_COOLDOWN "bot_beacon_cooldown" // medbot keys -///the patient we must heal -#define BB_PATIENT_TARGET "patient_target" ///list holding our wait dialogue #define BB_WAIT_SPEECH "wait_speech" ///what we will say to our patient after we heal them @@ -53,12 +53,13 @@ #define BB_CLEANBOT_EMAGGED_PHRASES "emagged_phrases" ///key that holds drawings we hunt #define BB_CLEANABLE_DRAWINGS "cleanable_drawings" -///Key that holds our clean target -#define BB_CLEAN_TARGET "clean_target" + ///key that holds the janitor we will befriend #define BB_FRIENDLY_JANITOR "friendly_janitor" ///key that holds the victim we will spray #define BB_ACID_SPRAY_TARGET "acid_spray_target" +///key that holds our acid spray attack cooldown +#define BB_ACID_SPRAY_COOLDOWN "acid_spray_cooldown" ///key that holds trash we will burn #define BB_HUNTABLE_TRASH "huntable_trash" @@ -75,6 +76,8 @@ #define BB_WASH_FRUSTRATION "wash_frustration" ///key that holds cooldown after we finish cleaning something, so we dont immediately run off to patrol #define BB_POST_CLEAN_COOLDOWN "post_clean_cooldown" +///key that holds cooldown after trash talk +#define BB_TRASH_TALK_COOLDOWN "trash_talk_cooldown" //secbots ///threat of our current target @@ -99,8 +102,6 @@ //firebot keys ///things we can extinguish #define BB_FIREBOT_CAN_EXTINGUISH "can_extinguish" -///the target we will extinguish -#define BB_FIREBOT_EXTINGUISH_TARGET "extinguish_target" ///lines we say when we detect a fire #define BB_FIREBOT_FIRE_DETECTED_LINES "fire_detected_lines" ///lines we say when we are idle @@ -123,33 +124,23 @@ #define BB_VIBEBOT_INSTRUMENT "instrument" //repairbots -///key that holds the floor we should tile over -#define BB_TILELESS_FLOOR "tileless_floor" -///key that holds the turf we should place a girder over -#define BB_GIRDER_TARGET "girder_target" -///key that holds the girder we should place a wall over -#define BB_GIRDER_TO_WALL_TARGET "girder_to_wall" -///key that holds the grille we must fix -#define BB_WINDOW_FRAMETARGET "grille_target" -///key that holds the machinery we repair with a welder -#define BB_WELDER_TARGET "welder_target" ///our wall girder ability #define BB_GIRDER_BUILD_ABILITY "girder_build_ability" -///key that holds breached floors we should repair -#define BB_BREACHED_FLOOR "breached_floor" ///key that holds our emagged speech #define BB_REPAIRBOT_EMAGGED_SPEECH "emagged_speech" ///key that holds our normal speech #define BB_REPAIRBOT_NORMAL_SPEECH "normal_speech" -///key that holds the thing we should deconstruct -#define BB_DECONSTRUCT_TARGET "deconstruct_target" ///key that holds our speech timer #define BB_REPAIRBOT_SPEECH_COOLDOWN "speech_cooldown" -///key that holds our target borg -#define BB_ROBOT_TARGET "robot_target" -///key that holds materials we can refill -#define BB_REFILLABLE_TARGET "refillable_target" +///Key that holds our current interaction type, to determine how we will interact with this object +#define BB_REPAIRBOT_INTERACTION_TYPE "interaction_type" +///Key that hold robot target +#define BB_ROBOT_TARGET "robot target" +///Key thast holds thingt to deconstruct +#define BB_DECONSTRUCT_TARGET "deconstruct_target" +#define REPAIRBOT_INTERACTION_INTERACT 1 +#define REPAIRBOT_INTERACTION_BUILD_GIRDERS 2 //mulebots ///key that holds our delivery destination's name diff --git a/code/__DEFINES/ai/carp.dm b/code/__DEFINES/ai/carp.dm index 459b98ffb020..1ff4acb3fe2e 100644 --- a/code/__DEFINES/ai/carp.dm +++ b/code/__DEFINES/ai/carp.dm @@ -6,6 +6,8 @@ #define BB_CARP_MIGRATION_PATH "BB_carp_migration_path" /// Current target turf in your migration #define BB_CARP_MIGRATION_TARGET "BB_carp_migration_target" +/// Turf of a carp rift we've decided to ride towards +#define BB_CARP_RIFT_DESTINATION "BB_carp_rift_destination" /// Targeting keys for magicarp spells #define BB_MAGICARP_SPELL_TARGET "BB_magicarp_spell_target" diff --git a/code/__DEFINES/ai/haunted.dm b/code/__DEFINES/ai/haunted.dm index d836d6be60da..1956d60ac3d0 100644 --- a/code/__DEFINES/ai/haunted.dm +++ b/code/__DEFINES/ai/haunted.dm @@ -8,6 +8,8 @@ #define HAUNTED_ITEM_AGGRO_ADDITION 2 ///how far a cursed item will still try to chase a target #define CURSED_VIEW_RANGE 7 +///Max number of throws per attack sequence before giving up on the current target +#define HAUNTED_MAX_THROW_ATTEMPTS 4 #define BB_TO_HAUNT_LIST "BB_to_haunt_list" ///Actual mob the item is haunting at the moment diff --git a/code/__DEFINES/ai/monkey.dm b/code/__DEFINES/ai/monkey.dm index f11ac96fefe2..6329e546cad6 100644 --- a/code/__DEFINES/ai/monkey.dm +++ b/code/__DEFINES/ai/monkey.dm @@ -7,14 +7,12 @@ #define BB_MONKEY_BLACKLISTITEMS "BB_monkey_blacklistitems" #define BB_MONKEY_PICKUPTARGET "BB_monkey_pickuptarget" #define BB_MONKEY_PICKPOCKETING "BB_monkey_pickpocketing" -#define BB_MONKEY_CURRENT_ATTACK_TARGET "BB_monkey_current_attack_target" #define BB_MONKEY_CURRENT_PRESS_TARGET "BB_monkey_current_press_target" #define BB_MONKEY_CURRENT_GIVE_TARGET "BB_monkey_current_give_target" #define BB_MONKEY_TARGET_DISPOSAL "BB_monkey_target_disposal" #define BB_MONKEY_TARGET_MONKEYS "BB_monkey_target_monkeys" #define BB_MONKEY_DISPOSING "BB_monkey_disposing" #define BB_MONKEY_RECRUIT_COOLDOWN "BB_monkey_recruit_cooldown" -#define BB_RESISTING "BB_resisting" /// Monkey is not necessarily a wild animal so it won't resort to fighting over food and such #define BB_MONKEY_TAMED "BB_monkey_tamed" /// Chance to give our held item to a nearby mob @@ -23,3 +21,17 @@ #define BB_MONKEY_PRESS_TYPEPATH "BB_monkey_press_typepath" /// The item the monkey is currently serving to someone #define BB_MONKEY_CURRENT_SERVED_ITEM "BB_monkey_current_served_item" +/// Set to TRUE when the monkey's current pickup target is on a mob (pickpocket), null when it's on the floor +#define BB_MONKEY_PICKUP_IS_PICKPOCKET "BB_monkey_pickup_is_pickpocket" +/// List of nearby patrons the monkey can serve food to +#define BB_MONKEY_PATRONS_NEARBY "BB_monkey_patrons_nearby" +/// Common emotes played during idle behavior +#define BB_MONKEY_IDLE_COMMON_EMOTES "BB_monkey_idle_common_emotes" +/// Rare emotes played during idle behavior +#define BB_MONKEY_IDLE_RARE_EMOTES "BB_monkey_idle_rare_emotes" +/// Whether the monkey believes its gun still works (20% chance it doesn't notice a dry-fire) +#define BB_MONKEY_GUN_WORKED "BB_monkey_gun_worked" +///Whether the monkey wants to press some shit +#define BB_MONKEY_WANNA_PRESS_SOME_SHIT "Monkeys wants to press something" +///Whether the monkey can look for patrons again +#define BB_MONKEY_PATRON_FIND_COOLDOWN "Monkeys can look for patron" diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 8a4e9d56aa53..b13ef26e1b00 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -60,16 +60,28 @@ // bee keys ///the bee hive we live inside #define BB_CURRENT_HOME "BB_current_home" +///the bee hive we want to move into +#define BB_TARGET_HOME "BB_target_home" ///the hydro we will pollinate #define BB_TARGET_HYDRO "BB_target_hydro" ///key to swarm around #define BB_SWARM_TARGET "BB_swarm_target" +///turf picked by swirl_around_target for move_to_target to path toward +#define BB_SWIRL_TURF "BB_swirl_turf" +///key to indicate if the bee wants to go in or out of its hive. +#define BB_WANTS_TO_TRANSITION_HIVE "BB_wants_to_transition_hive" // bear keys ///the hive with honey that we will steal from #define BB_FOUND_HONEY "BB_found_honey" +///cooldown between hive raids +#define BB_BEAR_HIVE_COOLDOWN "BB_bear_hive_cooldown" +///cooldown between honeycomb hunts +#define BB_BEAR_HONEYCOMB_COOLDOWN "BB_bear_honeycomb_cooldown" ///the tree that we will climb #define BB_CLIMBED_TREE "BB_climbed_tree" +///tree climbing cooldown +#define BB_TREE_CLIMBING_COOLDOWN "Tree Climbing Cooldown" /// Lobstrosities will only attack people with one of these traits #define BB_LOBSTROSITY_EXPLOIT_TRAITS "BB_lobstrosity_exploit_traits" @@ -90,6 +102,8 @@ #define BB_BLIND_TARGET "BB_blind_target" ///value to store the minimum eye damage to prevent us from attacking a human #define BB_EYE_DAMAGE_THRESHOLD "BB_eye_damage_threshold" +///the turf in front of our target we move to so our glare lines up +#define BB_GLARE_POSITION "BB_glare_position" // hivebot keys ///the machine we must go to repair @@ -108,6 +122,10 @@ #define BB_TARGET_CANNIBAL "BB_target_cannibal" ///the tree we will burn down #define BB_TARGET_TREE "BB_target_tree" +///cooldown key for sculpting statues +#define BB_WHELP_SCULPT_COOLDOWN "BB_whelp_sculpt_cooldown" +///cooldown key for burning trees +#define BB_WHELP_BURN_COOLDOWN "BB_whelp_burn_cooldown" // Regal Rats /// The rat's ability to corrupt an area. @@ -140,6 +158,8 @@ #define BB_BOULDER_TARGET "BB_boulder_target" /// key that holds the ore_vent we will harvest boulders from #define BB_VENT_TARGET "BB_vent_target" +/// Prevent us from strip-mining Lavaland lol +#define BB_MINING_COOLDOWN "Mining Cooldown" // minebot keys /// key that stores our toggle light ability @@ -174,6 +194,8 @@ #define BB_MINEBOT_REPAIR_DRONE "minebot_repair_drone" ///should we plant mines? #define BB_MINEBOT_PLANT_MINES "minebot_plant_mines" +///should we plant mines? +#define BB_MINEBOT_CRIT_ALERT_COOLDOWN "minebot callcrit cooldown" //seedling keys /// the water can we will pick up @@ -231,6 +253,21 @@ #define BB_MOOK_MUSIC_AUDIENCE "music_audience" /// the bonfire we will light up #define BB_MOOK_BONFIRE_TARGET "bonfire_target" +/// turf we are wandering toward (away from village) +#define BB_WANDER_DESTINATION "BB_wander_destination" +/// turf we will stand on to deposit ores at the material stand +#define BB_DEPOSIT_POSITION "BB_deposit_position" +/// list of /datum/pet_command instances the chief can issue +#define BB_MOOK_COMMANDS "BB_mook_commands" +/// list of things we can heal +#define BB_MOOK_HEAL_TARGETS "heal targets" +/// Cooldown on giving commands +#define BB_COMMAND_COOLDOWN "command cooldown" +///Things to light on fire +#define BB_BONFIRE_TARGETS "bonfire targets" +///Mook mining cooldown +#define BB_MOOK_MINING_COOLDOWN "mining cooldown" + //gutlunch keys ///the trough we will eat from @@ -250,7 +287,7 @@ #define BB_SWIM_ALTERNATE_TURF "swim_alternate_turf" ///key holds our state of swimming #define BB_CURRENTLY_SWIMMING "currently_swimming" -///key holds how long we will be swimming for +///Time between swims #define BB_KEY_SWIMMER_COOLDOWN "key_swimmer_cooldown" //Wizard AI keys /// Key where we store our main targeted spell @@ -301,6 +338,8 @@ #define BB_DEER_GRASS_TARGET "deer_grass_target" ///our tree target #define BB_DEER_TREE_TARGET "deer_tree_target" +///set when we roll the urge to go find another deer to play with +#define BB_DEER_WANTS_TO_PLAY "deer_wants_to_play" ///our temporary playmate #define BB_DEER_PLAYFRIEND "deer_playfriend" ///our home target @@ -309,6 +348,11 @@ #define BB_DEER_RESTING "deer_resting" ///time till our next rest duration #define BB_DEER_NEXT_REST_TIMER "deer_next_rest_timer" +///cooldowns gating how often we graze, drink and mark territory +#define BB_DEER_GRAZE_COOLDOWN "deer_graze_cooldown" +#define BB_DEER_DRINK_COOLDOWN "deer_drink_cooldown" +#define BB_DEER_MARK_COOLDOWN "deer_mark_cooldown" +#define BB_DEER_PLAY_COOLDOWN "deer_play_cooldown" //the thing boss #define BB_THETHING_CHARGE "BB_THETHING_CHARGE" @@ -318,19 +362,25 @@ #define BB_THETHING_CARDTENDRILS "BB_THETHING_CARDTENDRILS" #define BB_THETHING_ACIDSPIT "BB_THETHING_ACIDSPIT" /// Blackboard key for The Thing boss that determines attack mode. TRUE means it will focus on closing the distance and murdering the person in question. Otherwise AOE. -#define BB_THETHING_ATTACKMODE "BB_THETHING_ATTACKMODE" +#define BB_THETHING_MELEEMODE "BB_THETHING_MELEEMODE" /// The Thing will be in attack mode forever if true #define BB_THETHING_NOAOE "BB_THETHING_NOAOE" /// What (first in combo) attack was last executed #define BB_THETHING_LASTAOE "BB_THETHING_LASTAOE" +/// The action object selected by pick_random_ability for the current AOE slot +#define BB_THETHING_SELECTED_AOE "BB_THETHING_SELECTED_AOE" -//turtle -///our tree's ability -#define BB_TURTLE_TREE_ABILITY "turtle_tree_ability" ///people we headbutt! #define BB_TURTLE_HEADBUTT_VICTIM "turtle_headbutt_victim" ///flore we must smell #define BB_TURTLE_FLORA_TARGET "turtle_flora_target" +///Cooldown between headbutts +#define BB_TURTLE_HEADBUTT_COOLDOWN "turtle_headbutt_cooldown" +///Cooldown between smelling flora +#define BB_TURTLE_FLORA_COOLDOWN "turtle_flora_cooldown" +#define BB_TURTLE_HEADBUTT_TYPES "turtle_headbutt_types" +#define BB_TURTLE_FLORA_TYPES "turtle_flora_types" + #define BB_GUNMIMIC_GUN_EMPTY "BB_GUNMIMIC_GUN_EMPTY" diff --git a/code/__DEFINES/ai/pet_commands.dm b/code/__DEFINES/ai/pet_commands.dm index 5f03bf1a5b0d..33b574fdfaf1 100644 --- a/code/__DEFINES/ai/pet_commands.dm +++ b/code/__DEFINES/ai/pet_commands.dm @@ -5,6 +5,10 @@ #define BB_CURRENT_PET_TARGET "BB_current_pet_target" /// Blackboard field for how we target things, as usually we want to be more permissive than normal #define BB_PET_TARGETING_STRATEGY "BB_pet_targeting" +/// Hiding location scratch key used by basic_melee_attack in pet command attack subtrees +#define BB_PET_ATTACK_HIDING_LOCATION "BB_pet_attack_hiding_location" +/// Scratch key holding the active untargeted ability datum for /datum/pet_command/untargeted_ability +#define BB_PET_ACTIVE_ABILITY "BB_pet_active_ability" /// Typecache of weakrefs to mobs this mob is friends with, will follow their instructions and won't attack them #define BB_FRIENDS_LIST "BB_friends_list" /// List of strings we might say to encourage someone to make better choices. diff --git a/code/__DEFINES/ai/pets.dm b/code/__DEFINES/ai/pets.dm index 067569a8a9ac..ef61ebcc4ac4 100644 --- a/code/__DEFINES/ai/pets.dm +++ b/code/__DEFINES/ai/pets.dm @@ -29,6 +29,8 @@ #define BB_FIND_MOM_TYPES "BB_find_mom_types" ///list of types of mobs we must ignore #define BB_IGNORE_MOM_TYPES "BB_ignore_mom_types" +///cooldown between emoting at our parent +#define BB_PARENT_EMOTE_COOLDOWN "BB_parent_emote_cooldown" /// The current string that this parrot will repeat back to someone #define BB_PARROT_REPEAT_STRING "BB_parrot_repeat_string" @@ -52,6 +54,8 @@ #define BB_HOARD_LOCATION_RANGE "hoard_location_range" /// key that holds items we arent interested in hoarding #define BB_IGNORE_ITEMS "ignore_items" +/// cooldown key throttling how often a parrot repeats a phrase +#define BB_PARROT_SPEECH_COOLDOWN "BB_parrot_speech_cooldown" // Cultist pet keys ///our ability to summon runes diff --git a/code/__DEFINES/ai/slime.dm b/code/__DEFINES/ai/slime.dm index ba08dbba556b..9c8c2be10a88 100644 --- a/code/__DEFINES/ai/slime.dm +++ b/code/__DEFINES/ai/slime.dm @@ -12,3 +12,5 @@ #define BB_SLIME_EVOLVE "BB_slime_evolve" ///Our reproduce action #define BB_SLIME_REPRODUCE "BB_slime_reproduce" +///Target to try and eat, separate from retaliation because they use different strats +#define BB_SLIME_EAT_TARGET "BB_slime_eat_target" diff --git a/code/__DEFINES/ai/tourist.dm b/code/__DEFINES/ai/tourist.dm index 5ef27550d6f7..e46db87fde60 100644 --- a/code/__DEFINES/ai/tourist.dm +++ b/code/__DEFINES/ai/tourist.dm @@ -15,3 +15,7 @@ #define BB_CUSTOMER_CURRENT_TARGET "BB_customer_current_target" /// Robot customer has said their can't find seat line at least once. Used to rate limit how often they'll complain after the first time. #define BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE "BB_customer_said_cant_find_seat_line" +/// The exit portal atom the customer walks to when leaving the venue. +#define BB_CUSTOMER_EXIT_PORTAL "BB_customer_exit_portal" +/// Cooldown key used to throttle seat-search retries. +#define BB_CUSTOMER_FIND_SEAT_COOLDOWN "BB_customer_find_seat_cooldown" diff --git a/code/__DEFINES/ai/trader.dm b/code/__DEFINES/ai/trader.dm index 853dd8736b64..5d8c0405296e 100644 --- a/code/__DEFINES/ai/trader.dm +++ b/code/__DEFINES/ai/trader.dm @@ -4,3 +4,5 @@ #define BB_SHOP_SPOT "BB_shop_spot" ///Reference to our first customer to harass with deals #define BB_FIRST_CUSTOMER "BB_first_customer" +///Whether we spook customers with our deals +#define BB_TRADER_RUSH_TO_SELL "BB_rush_to_sell" diff --git a/code/__DEFINES/ai/ventcrawling.dm b/code/__DEFINES/ai/ventcrawling.dm index a60b7fd59401..b672113738de 100644 --- a/code/__DEFINES/ai/ventcrawling.dm +++ b/code/__DEFINES/ai/ventcrawling.dm @@ -14,3 +14,5 @@ #define BB_TIME_TO_GIVE_UP_ON_VENT_PATHING "BB_seconds_until_we_give_up_on_vent_pathing" /// The timer ID of the timer that makes us give up on vent pathing. #define BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID "BB_give_up_on_vent_pathing_timer_id" +/// The world.time when we entered a vent. Null when not in one. +#define BB_VENT_ENTRY_TIME "BB_vent_entry_time" diff --git a/code/__DEFINES/basic_mobs.dm b/code/__DEFINES/basic_mobs.dm index 1a97163a8715..d97418718a16 100644 --- a/code/__DEFINES/basic_mobs.dm +++ b/code/__DEFINES/basic_mobs.dm @@ -127,3 +127,9 @@ GLOBAL_LIST_EMPTY(customized_pets) #define BASIC_MOB_END_ATTACK_CHAIN 1 ///Return value for [/mob/living/basic/proc/early_melee_attack]. Using this value will make the attack end, and sets a cooldown. Useful if you add behavior to early_melee_attack #define BASIC_MOB_END_ATTACK_CHAIN_COOLDOWN 2 + +///Delay between trying to update target selection +#define BASIC_MOB_FIND_TARGET_RATE 1 SECONDS + +///Time between idle behavior execution +#define IDLE_BEHAVIOR_RATE 1.5 SECONDS diff --git a/code/__DEFINES/dcs/signals/signals_ai_controller.dm b/code/__DEFINES/dcs/signals/signals_ai_controller.dm index 1888612d2c4a..5ff3d2419a5a 100644 --- a/code/__DEFINES/dcs/signals/signals_ai_controller.dm +++ b/code/__DEFINES/dcs/signals/signals_ai_controller.dm @@ -3,7 +3,5 @@ #define COMSIG_AI_CONTROLLER_POSSESSED_PAWN "ai_controller_possessed_pawn" ///sent from ai controllers when they stop possessing a pawn: (datum/ai_controller/source_controller) #define COMSIG_AI_CONTROLLER_UNPOSSESSED_PAWN "ai_controller_unpossessed_pawn" -///sent from ai controllers when they pick behaviors: (list/datum/ai_behavior/old_behaviors, list/datum/ai_behavior/new_behaviors) -#define COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS "ai_controller_picked_behaviors" -///sent from ai controllers when a behavior is inserted into the queue: (list/new_arguments) -#define AI_CONTROLLER_BEHAVIOR_QUEUED(type) "ai_controller_behavior_queued_[type]" +///sent from the pawn of an ai controller when a runtime subtree override slot changes: (new_type) new_type is null when cleared +#define COMSIG_AI_OVERRIDE_SLOT_CHANGED(id) "ai_override_slot_changed_[id]" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm index 1c6fcbffbda3..de7093057dd4 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm @@ -14,3 +14,6 @@ ///Signal sent off of ai/movement/proc/start_moving_towards #define COMSIG_MOB_AI_MOVEMENT_STARTED "mob_ai_movement_started" + +///Signal sent off of ai/movement/proc/increment_pathing_failures +#define COMSIG_MOB_AI_MOVEMENT_FAILED "mob_ai_movement_failed" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm index b459c2530b13..95c138ad5796 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm @@ -10,7 +10,7 @@ ///from the ranged_attacks component for basic mobs: (mob/living/basic/firer, atom/target, modifiers) #define COMSIG_BASICMOB_POST_ATTACK_RANGED "basicmob_post_attack_ranged" -/// Sent from /datum/ai_planning_subtree/parrot_as_in_repeat() : () +/// Sent from /datum/bt_node/ai_behavior/parrot_repeat_speech/perform(): () #define COMSIG_NEEDS_NEW_PHRASE "parrot_needs_new_phrase" #define NO_NEW_PHRASE_AVAILABLE (1<<0) //! Cancel to try again later for when we actually get a new phrase diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 7092efd0e2d5..04075907d90c 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -153,6 +153,8 @@ #define COMSIG_MOB_DROPPED_ITEM "mob_dropped_item" ///from base of obj/item/pickup(): (/mob/taker) #define COMSIG_ITEM_PICKUP "item_pickup" +///from base of mob/put_in_hand(), after forceMove item is now in the mob's hand: (mob/holder, hand_index) +#define COMSIG_ITEM_ENTERED_HANDS "item_entered_hands" ///from base of obj/item/on_outfit_equip(): (mob/equipper, visuals_only, slot) #define COMSIG_ITEM_EQUIPPED_AS_OUTFIT "item_equip_as_outfit" ///from base of datum/storage/handle_enter(): (datum/storage/storage) diff --git a/code/__DEFINES/monkeys.dm b/code/__DEFINES/monkeys.dm index 8dfccc937f76..a2280ca95a41 100644 --- a/code/__DEFINES/monkeys.dm +++ b/code/__DEFINES/monkeys.dm @@ -33,12 +33,9 @@ /// amount of aggro to add if someone stole the food we wanted #define MONKEY_FOOD_HATRED_AMOUNT 2 /// probability of reducing aggro by one when the monkey attacks -#define MONKEY_HATRED_REDUCTION_PROB 20 +#define MONKEY_HATRED_REDUCTION_PROB 40 /// Monkey was calmed, such as from weed #define MONKEY_CALMED_HATRED_AMOUNT -2 /// Monkey was angered, such as from alcohol #define MONKEY_ANGERED_HATRED_AMOUNT 2 - -///Monkey recruit cooldown -#define MONKEY_RECRUIT_COOLDOWN (1 MINUTES) diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index 60f6cbbd7625..96cbabe5ed7f 100644 --- a/code/__DEFINES/robots.dm +++ b/code/__DEFINES/robots.dm @@ -81,7 +81,6 @@ /// Default view range for finding targets. #define DEFAULT_SCAN_RANGE 7 //Amount of time that must pass after a Commissioned bot gets saluted to get another. -#define BOT_COMMISSIONED_SALUTE_DELAY (60 SECONDS) //Bot mode defines displaying how Bots act ///The Bot is currently active, and will do whatever it is programmed to do. diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 01d1ee8b30f0..cb48913b23d8 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -127,7 +127,6 @@ // Subsystem fire priority, from lowest to highest priority // If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) -#define FIRE_PRIORITY_UNPLANNED_NPC 3 #define FIRE_PRIORITY_IDLE_NPC 5 #define FIRE_PRIORITY_PING 10 #define FIRE_PRIORITY_SERVER_MAINT 10 @@ -137,13 +136,13 @@ #define FIRE_PRIORITY_BLOOD_DRYING 10 #define FIRE_PRIORITY_GARBAGE 15 #define FIRE_PRIORITY_DATABASE 16 +#define FIRE_PRIORITY_NPC_LOW 19 #define FIRE_PRIORITY_WET_FLOORS 20 #define FIRE_PRIORITY_AIR 20 -#define FIRE_PRIORITY_NPC 20 #define FIRE_PRIORITY_ASSETS 20 #define FIRE_PRIORITY_HYPERSPACE_DRIFT 20 -#define FIRE_PRIORITY_NPC_MOVEMENT 21 -#define FIRE_PRIORITY_NPC_ACTIONS 22 +#define FIRE_PRIORITY_NPC 21 +#define FIRE_PRIORITY_NPC_MOVEMENT 22 #define FIRE_PRIORITY_PATHFINDING 23 #define FIRE_PRIORITY_CLIFF_FALLING 24 #define FIRE_PRIORITY_PROCESS 25 diff --git a/code/_globalvars/lists/basic_ai.dm b/code/_globalvars/lists/basic_ai.dm deleted file mode 100644 index 561d874c4e91..000000000000 --- a/code/_globalvars/lists/basic_ai.dm +++ /dev/null @@ -1,19 +0,0 @@ -///all basic ai subtrees -GLOBAL_LIST_EMPTY(ai_subtrees) - -///basic ai controllers based on status -GLOBAL_LIST_INIT(ai_controllers_by_status, list( - AI_STATUS_ON = list(), - AI_STATUS_OFF = list(), - AI_STATUS_IDLE = list(), -)) - -///basic ai controllers based on their z level -GLOBAL_LIST_EMPTY(ai_controllers_by_zlevel) - -///basic ai controllers that are currently performing idled behaviors -GLOBAL_LIST_INIT_TYPED(unplanned_controllers, /list/datum/ai_controller, list( - AI_STATUS_ON = list(), - AI_STATUS_IDLE = list(), -)) - diff --git a/code/controllers/subsystem/ai_controllers.dm b/code/controllers/subsystem/ai_controllers.dm index 4102806be67f..27d5cdf3281d 100644 --- a/code/controllers/subsystem/ai_controllers.dm +++ b/code/controllers/subsystem/ai_controllers.dm @@ -1,35 +1,87 @@ +/// How many of the most expensive controllers to track per pass for the MC stat entry +#define AI_STAT_EXPENSIVE_TRACKED 5 + /// The subsystem used to tick [/datum/ai_controllers] instances. Handling the re-checking of plans. SUBSYSTEM_DEF(ai_controllers) name = "AI Controller Ticker" - ss_flags = SS_POST_FIRE_TIMING|SS_BACKGROUND + ss_flags = SS_POST_FIRE_TIMING priority = FIRE_PRIORITY_NPC dependencies = list( /datum/controller/subsystem/movement/ai_movement, ) - wait = 0.5 SECONDS //Plan every half second if required, not great not terrible. + wait = 0.25 SECONDS //Plan every 1/4th second if required. In theory your AI should not be planning this much, but its useful because we want planning to be responsive when a previous plan ends. runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME var/list/currentrun = list() ///type of status we are interested in running var/planning_status = AI_STATUS_ON - /// The average tick cost of all active AI, calculated on fire. - var/our_cost - /// The tick cost of all currently processed AI, being summed together + /// CPU cost accumulated by the in-progress pass, summed across fires. var/summing_cost + /// world.time at which the in-progress pass started. + var/pass_started + /// How many controllers the in-progress pass started with. + var/pass_size + /// Average wall-clock duration for a full pass on controllers + var/average_pass_time + /// Longest gap any single controller went between two ticks. + var/longest_tick_gap + /// Running longest tick gap of the in-progress pass. + var/summing_tick_gap + /// Display strings for the most expensive controllers of the last completed pass, most expensive first. + var/list/most_expensive = list() + /// Worst SelectBehaviors cost seen this round. + var/worst_controller_cost = 0 + /// Display string for the controller responsible for worst_controller_cost. + var/worst_controller_name + /// Running top-cost candidates of the in-progress pass. Assoc list of controller -> SelectBehaviors cost in ms, has a capped amount of entries + var/list/summing_expensive = list() + /// Cheapest cost in summing_expensive once it's full; a controller must beat this to enter the list. + var/summing_expensive_cutoff = 0 + /// List of all targeting_strategy singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_targeting_strats() + var/list/targeting_strategies + /// List of all target_priority_strategy singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_target_priority_strats() + var/list/target_priority_strategies + /// List of all target_source singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_target_sources() + var/list/target_sources + ///AI controllers, sorted by their status + var/list/ai_controllers_by_status = list( + AI_STATUS_ON = list(), + AI_STATUS_ON_LOW = list(), + AI_STATUS_OFF = list(), + ) + ///AI controllers, sorted by their z level + var/list/ai_controllers_by_zlevel = list() + +/datum/controller/subsystem/ai_controllers/Recover() + if(islist(SSai_controllers.ai_controllers_by_status)) + ai_controllers_by_status = SSai_controllers.ai_controllers_by_status + if(islist(SSai_controllers.ai_controllers_by_zlevel)) + ai_controllers_by_zlevel = SSai_controllers.ai_controllers_by_zlevel /datum/controller/subsystem/ai_controllers/Initialize() - setup_subtrees() + setup_targeting_strats() + setup_target_priority_strats() + setup_target_sources() return SS_INIT_SUCCESS /datum/controller/subsystem/ai_controllers/stat_entry(msg) - var/list/planning_list = GLOB.ai_controllers_by_status[planning_status] - msg = "\n Planning AIs:[length(planning_list)]/[round(our_cost,1)]%" + msg = "\n Active:[length(SSai_controllers.ai_controllers_by_status[planning_status])]|Off:[length(SSai_controllers.ai_controllers_by_status[AI_STATUS_OFF])]" + msg += "\n Pass:[pass_size - length(currentrun)]/[pass_size]|AvgPass:[round(average_pass_time * 0.1, 0.1)]s|WorstGap:[round(longest_tick_gap * 0.1, 0.1)]s" + if(length(most_expensive)) + msg += "\n Top: [most_expensive.Join(" | ")]" + if(worst_controller_name) + msg += "\n Slowest bozo of the round: [worst_controller_name]" return ..() /datum/controller/subsystem/ai_controllers/fire(resumed) if(!resumed) - var/list/planning_list = GLOB.ai_controllers_by_status[planning_status] + var/list/planning_list = SSai_controllers.ai_controllers_by_status[planning_status] currentrun = planning_list.Copy() summing_cost = 0 + summing_tick_gap = 0 + summing_expensive = list() + summing_expensive_cutoff = 0 + pass_started = world.time + pass_size = length(currentrun) //cache for sanic speed (lists are references anyways) var/list/current_run = src.currentrun @@ -37,12 +89,35 @@ SUBSYSTEM_DEF(ai_controllers) while(length(current_run)) var/datum/ai_controller/ai_controller = current_run[length(current_run)] current_run.len-- - if(!ai_controller.able_to_plan) - continue - ai_controller.SelectBehaviors(wait * 0.1) + // Pass the real time since this controller last ticked, so SPT_PROB rolls and + // time accumulators stay time-correct even when a pass takes several seconds. + var/seconds_per_tick = wait * 0.1 + if(ai_controller.last_bt_tick) + var/tick_gap = world.time - ai_controller.last_bt_tick + summing_tick_gap = max(summing_tick_gap, tick_gap) + seconds_per_tick = tick_gap * 0.1 + ai_controller.last_bt_tick = world.time + var/controller_timer = TICK_USAGE_REAL + ai_controller.SelectBehaviors(seconds_per_tick) - if(!length(ai_controller.current_behaviors)) //Still no plan - ai_controller.planning_failed() + ///Lets check if this is an expensive controller + var/tick_cost = TICK_DELTA_TO_MS(TICK_USAGE_REAL - controller_timer) + if(tick_cost > worst_controller_cost) + worst_controller_cost = tick_cost + worst_controller_name = "[ai_controller.pawn || ai_controller] [round(tick_cost, 0.01)]ms" + if(tick_cost > summing_expensive_cutoff) + summing_expensive[ai_controller] = tick_cost + if(length(summing_expensive) > AI_STAT_EXPENSIVE_TRACKED) + var/cheapest_cost = INFINITY + var/datum/ai_controller/cheapest + for(var/datum/ai_controller/candidate as anything in summing_expensive) + if(summing_expensive[candidate] < cheapest_cost) + cheapest_cost = summing_expensive[candidate] + cheapest = candidate + summing_expensive -= cheapest + summing_expensive_cutoff = INFINITY + for(var/datum/ai_controller/candidate as anything in summing_expensive) + summing_expensive_cutoff = min(summing_expensive_cutoff, summing_expensive[candidate]) if(MC_TICK_CHECK) break @@ -51,20 +126,47 @@ SUBSYSTEM_DEF(ai_controllers) if(MC_TICK_CHECK) return - our_cost = MC_AVERAGE(our_cost, summing_cost) + average_pass_time = MC_AVERAGE(average_pass_time, world.time - pass_started) + longest_tick_gap = summing_tick_gap -///Creates all instances of ai_subtrees and assigns them to the ai_subtrees list. -/datum/controller/subsystem/ai_controllers/proc/setup_subtrees() - if(length(GLOB.ai_subtrees)) - return - for(var/subtree_type in subtypesof(/datum/ai_planning_subtree)) - var/datum/ai_planning_subtree/subtree = new subtree_type - GLOB.ai_subtrees[subtree_type] = subtree + // Publish the pass's most expensive controllers as display strings, sorted most expensive first. + // Only a handful of entries, so a selection sort is fine. + var/list/expensive_entries = list() + while(length(summing_expensive)) + var/costliest_cost = 0 + var/datum/ai_controller/costliest + for(var/datum/ai_controller/candidate as anything in summing_expensive) + if(summing_expensive[candidate] >= costliest_cost) + costliest_cost = summing_expensive[candidate] + costliest = candidate + summing_expensive -= costliest + expensive_entries += "[costliest.pawn || costliest] [round(costliest_cost, 0.01)]ms" + most_expensive = expensive_entries ///Called when the max Z level was changed, updating our coverage. /datum/controller/subsystem/ai_controllers/proc/on_max_z_changed() - if(!length(GLOB.ai_controllers_by_zlevel)) - GLOB.ai_controllers_by_zlevel = new /list(world.maxz,0) - while (GLOB.ai_controllers_by_zlevel.len < world.maxz) - GLOB.ai_controllers_by_zlevel.len++ - GLOB.ai_controllers_by_zlevel[GLOB.ai_controllers_by_zlevel.len] = list() + if(!length(ai_controllers_by_zlevel)) + ai_controllers_by_zlevel = new /list(world.maxz,0) + while (ai_controllers_by_zlevel.len < world.maxz) + ai_controllers_by_zlevel.len++ + ai_controllers_by_zlevel[ai_controllers_by_zlevel.len] = list() + +/datum/controller/subsystem/ai_controllers/proc/setup_targeting_strats() + targeting_strategies = list() + for(var/target_type in subtypesof(/datum/targeting_strategy)) + var/datum/targeting_strategy/target_start = new target_type + targeting_strategies[target_type] = target_start + +/datum/controller/subsystem/ai_controllers/proc/setup_target_priority_strats() + target_priority_strategies = list() + for(var/target_type in subtypesof(/datum/target_priority_strategy)) + var/datum/target_priority_strategy/target_start = new target_type + target_priority_strategies[target_type] = target_start + +/datum/controller/subsystem/ai_controllers/proc/setup_target_sources() + target_sources = list() + for(var/source_type in subtypesof(/datum/target_source)) + var/datum/target_source/source = new source_type + target_sources[source_type] = source + +#undef AI_STAT_EXPENSIVE_TRACKED diff --git a/code/controllers/subsystem/ai_controllers_low_priority.dm b/code/controllers/subsystem/ai_controllers_low_priority.dm new file mode 100644 index 000000000000..a453a982f565 --- /dev/null +++ b/code/controllers/subsystem/ai_controllers_low_priority.dm @@ -0,0 +1,6 @@ +/// Plans background controllers that are active when unwatched but not critical +AI_CONTROLLER_SUBSYSTEM_DEF(low_priority_ai_controllers) + name = "AI Controller Ticker (Low)" + ss_flags = parent_type::ss_flags | SS_BACKGROUND | SS_NO_INIT + planning_status = AI_STATUS_ON_LOW + priority = FIRE_PRIORITY_NPC_LOW diff --git a/code/controllers/subsystem/ai_idle_controllers.dm b/code/controllers/subsystem/ai_idle_controllers.dm deleted file mode 100644 index 94b43207f82f..000000000000 --- a/code/controllers/subsystem/ai_idle_controllers.dm +++ /dev/null @@ -1,10 +0,0 @@ -AI_CONTROLLER_SUBSYSTEM_DEF(ai_idle_controllers) - name = "AI Idle Controllers" - ss_flags = SS_POST_FIRE_TIMING | SS_BACKGROUND - priority = FIRE_PRIORITY_IDLE_NPC - dependencies = list( - /datum/controller/subsystem/ai_controllers, - ) - wait = 5 SECONDS - runlevels = RUNLEVEL_GAME - planning_status = AI_STATUS_IDLE diff --git a/code/controllers/subsystem/movement/movement_types.dm b/code/controllers/subsystem/movement/movement_types.dm index ea1e7f90cda0..dcf01e4bfd4c 100644 --- a/code/controllers/subsystem/movement/movement_types.dm +++ b/code/controllers/subsystem/movement/movement_types.dm @@ -129,12 +129,11 @@ owner?.processing_move_loop_flags = flags var/result = move() //Result is an enum value. Enums defined in __DEFINES/movement.dm - if(result) - EVLOG_PATH(moving, EVLOG_CATEGORY_MOVELOOPS, "Moved using [src]", list(old_loc, moving.loc)) //You might think, this runs a lot; but if not logging, it only does a lookup on the event logger. - if(moving) var/direction = get_dir(old_loc, moving.loc) SEND_SIGNAL(moving, COMSIG_MOVABLE_MOVED_FROM_LOOP, src, old_dir, direction) + if(result) + EVLOG_PATH(moving, EVLOG_CATEGORY_MOVELOOPS, "Moved using [src]", list(old_loc, moving.loc)) //You might think, this runs a lot; but if not logging, it only does a lookup on the event logger. owner?.processing_move_loop_flags = NONE SEND_SIGNAL(src, COMSIG_MOVELOOP_POSTPROCESS, result, delay * visual_delay) @@ -438,7 +437,8 @@ /datum/move_loop/has_target/jps/proc/on_finish_pathing(list/path) movement_path = path is_pathing = FALSE - EVLOG_PATH(moving, EVLOG_CATEGORY_JPS, "Planned AI path", movement_path) + if(moving) + EVLOG_PATH(moving, EVLOG_CATEGORY_JPS, "Planned AI path", movement_path) SEND_SIGNAL(src, COMSIG_MOVELOOP_JPS_FINISHED_PATHING, path) /datum/move_loop/has_target/jps/move() diff --git a/code/controllers/subsystem/processing/ai_behaviors.dm b/code/controllers/subsystem/processing/ai_behaviors.dm deleted file mode 100644 index aa5ef4eb55ce..000000000000 --- a/code/controllers/subsystem/processing/ai_behaviors.dm +++ /dev/null @@ -1,40 +0,0 @@ -/// The subsystem used to tick [/datum/ai_behavior] instances. Handling the individual actions an AI can take like punching someone in the fucking NUTS -PROCESSING_SUBSYSTEM_DEF(ai_behaviors) - name = "AI Behavior Ticker" - ss_flags = SS_POST_FIRE_TIMING|SS_BACKGROUND - priority = FIRE_PRIORITY_NPC_ACTIONS - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - dependencies = list( - /datum/controller/subsystem/movement/ai_movement, - ) - wait = 1 - /// List of all ai_behavior singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_ai_behaviors() - var/list/ai_behaviors - /// List of all targeting_strategy singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_targeting_strats() - var/list/targeting_strategies - /// List of all target_priority_strategy singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_target_priority_strats() - var/list/target_priority_strategies - -/datum/controller/subsystem/processing/ai_behaviors/Initialize() - setup_ai_behaviors() - setup_targeting_strats() - setup_target_priority_strats() - return SS_INIT_SUCCESS - -/datum/controller/subsystem/processing/ai_behaviors/proc/setup_ai_behaviors() - ai_behaviors = list() - for(var/behavior_type in subtypesof(/datum/ai_behavior)) - var/datum/ai_behavior/ai_behavior = new behavior_type - ai_behaviors[behavior_type] = ai_behavior - -/datum/controller/subsystem/processing/ai_behaviors/proc/setup_targeting_strats() - targeting_strategies = list() - for(var/target_type in subtypesof(/datum/targeting_strategy)) - var/datum/targeting_strategy/target_start = new target_type - targeting_strategies[target_type] = target_start - -/datum/controller/subsystem/processing/ai_behaviors/proc/setup_target_priority_strats() - target_priority_strategies = list() - for(var/target_type in subtypesof(/datum/target_priority_strategy)) - var/datum/target_priority_strategy/target_start = new target_type - target_priority_strategies[target_type] = target_start diff --git a/code/controllers/subsystem/processing/ai_idle_behaviors.dm b/code/controllers/subsystem/processing/ai_idle_behaviors.dm deleted file mode 100644 index 2b631da96f3b..000000000000 --- a/code/controllers/subsystem/processing/ai_idle_behaviors.dm +++ /dev/null @@ -1,19 +0,0 @@ -PROCESSING_SUBSYSTEM_DEF(idle_ai_behaviors) - name = "AI Idle Behaviors" - ss_flags = SS_BACKGROUND - wait = 1.5 SECONDS - priority = FIRE_PRIORITY_IDLE_NPC - dependencies = list( - /datum/controller/subsystem/ai_controllers, - ) - ///List of all the idle ai behaviors - var/list/idle_behaviors = list() - -/datum/controller/subsystem/processing/idle_ai_behaviors/Initialize() - setup_idle_behaviors() - return SS_INIT_SUCCESS - -/datum/controller/subsystem/processing/idle_ai_behaviors/proc/setup_idle_behaviors() - for(var/behavior_type in subtypesof(/datum/idle_behavior)) - var/datum/idle_behavior/behavior = new behavior_type - idle_behaviors[behavior_type] = behavior diff --git a/code/controllers/subsystem/unplanned_ai_idle_controllers.dm b/code/controllers/subsystem/unplanned_ai_idle_controllers.dm deleted file mode 100644 index 6385239e18c7..000000000000 --- a/code/controllers/subsystem/unplanned_ai_idle_controllers.dm +++ /dev/null @@ -1,4 +0,0 @@ -UNPLANNED_CONTROLLER_SUBSYSTEM_DEF(idle_unplanned_controllers) - name = "Unplanned AI Idle Controllers" - wait = 2.5 SECONDS - target_status = AI_STATUS_IDLE diff --git a/code/controllers/subsystem/unplanned_controllers.dm b/code/controllers/subsystem/unplanned_controllers.dm deleted file mode 100644 index e2e78e0951ee..000000000000 --- a/code/controllers/subsystem/unplanned_controllers.dm +++ /dev/null @@ -1,39 +0,0 @@ -GLOBAL_LIST_EMPTY(unplanned_controller_subsystems) -/// Handles making mobs perform lightweight "idle" behaviors such as wandering around when they have nothing planned -SUBSYSTEM_DEF(unplanned_controllers) - name = "Unplanned AI Controllers" - ss_flags = SS_POST_FIRE_TIMING|SS_BACKGROUND - priority = FIRE_PRIORITY_UNPLANNED_NPC - dependencies = list( - /datum/controller/subsystem/movement/ai_movement, - ) - wait = 0.25 SECONDS - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - ///what ai status are we interested in - var/target_status = AI_STATUS_ON - var/list/current_run = list() - -/datum/controller/subsystem/unplanned_controllers/Initialize() - ..() - GLOB.unplanned_controller_subsystems += src - return SS_INIT_SUCCESS - -/datum/controller/subsystem/unplanned_controllers/Destroy() - GLOB.unplanned_controller_subsystems -= src - return ..() - -/datum/controller/subsystem/unplanned_controllers/stat_entry(msg) - msg = "\n Planning AIs:[length(GLOB.unplanned_controllers[target_status])]" - return ..() - -/datum/controller/subsystem/unplanned_controllers/fire(resumed) - if(!resumed) - src.current_run = GLOB.unplanned_controllers[target_status].Copy() - var/list/current_run = src.current_run // cache for sonic speed - while(length(current_run)) - var/datum/ai_controller/unplanned = current_run[current_run.len] - current_run.len-- - if(!QDELETED(unplanned)) - unplanned.idle_behavior.perform_idle_behavior(wait * 0.1, unplanned) - if (MC_TICK_CHECK) - return diff --git a/code/datums/actions/mobs/create_legion_skull.dm b/code/datums/actions/mobs/create_legion_skull.dm index bfd997c5d457..e78ddd08aa07 100644 --- a/code/datums/actions/mobs/create_legion_skull.dm +++ b/code/datums/actions/mobs/create_legion_skull.dm @@ -16,4 +16,4 @@ /datum/action/cooldown/mob_cooldown/create_legion_skull/proc/create(atom/target) var/mob/living/basic/mining/legion_brood/minion = new(owner.loc) minion.assign_creator(owner) - minion.ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] = target + minion.ai_controller.blackboard[BB_CURRENT_TARGET] = target diff --git a/code/datums/ai/README.md b/code/datums/ai/README.md index fb4022775e8b..937d869ccc9b 100644 --- a/code/datums/ai/README.md +++ b/code/datums/ai/README.md @@ -8,19 +8,15 @@ Our AI controller system is an attempt at making it possible to create modulariz A datum that can be added to any atom in the game. Similarly to components, they might only support a given subtype (e.g. /mob/living), but the idea is that theoretically, you could apply a specific AI controller to a big a group of different types as possible and it would still work. -These datums handle both the normal movement of mobs, but also their decision making, deciding which actions they will take based on the checks you put into their SelectBehaviors proc. - -If behaviors are selected, and the AI is in range, it will try to perform them. It runs all the behaviors it currently has in parallel; allowing for it to for example screech at someone while trying to attack them. As long as it has behaviors running, it will not try to generate new plans, making it not waste CPU when it already has an active goal. - They also hold data for any of the actions they might need to use, such as cooldowns, whether or not they're currently fighting, etcetera this is stored in the blackboard, more information on that below. ### Blackboard -The blackboard is an associated list keyed with strings and with values of whatever you want. These store information the mob has such as "Am I attacking someone", "Do I have a weapon". By using an associated list like this, no data needs to be stored on the actions themselves, and you could make actions that work on multiple ai controllers if you so pleased by making the key to use a variable. +The blackboard is an associated list keyed with strings and with values of whatever you want. These store information the mob has such as "Am I attacking someone", "Do I have a weapon". By using an associated list like this, You could make actions that work on multiple ai controllers if you so pleased by making the key to use a variable. ## AI Behavior -AI behaviors are the actions an AI can take. These can range from "Do an emote" to "Attack this target until he is dead". They are singletons and should contain nothing but static data. Any dynamic data should be stored in the blackboard, to allow different controllers to use the same behaviors. +AI behaviors are the actions an AI can take. These can range from "Do an emote" to "Attack this target". Any dynamic data such as target should be stored in the blackboard # Guides: diff --git a/code/datums/ai/_ai_behavior.dm b/code/datums/ai/_ai_behavior.dm index a73ca1672147..dd26ab204a8b 100644 --- a/code/datums/ai/_ai_behavior.dm +++ b/code/datums/ai/_ai_behavior.dm @@ -1,46 +1,131 @@ -///Abstract class for an action an AI can take, can range from movement to grabbing a nearby weapon. -/datum/ai_behavior - ///What distance you need to be from the target to perform the action - var/required_distance = 1 - ///Flags for extra behavior +/// Base type for AI behavior leaf nodes in the behavior tree system. +/// setup() is called once on first activation, perform() each tick while running. +/// Returns BT_SUCCESS / BT_FAILURE on completion, BT_RUNNING while active. +/datum/bt_node/ai_behavior + ///Flags for extra behavior (see AI_BEHAVIOR_* defines) var/behavior_flags = NONE - ///Cooldown between actions performances, defaults to the value of CLICK_CD_MELEE because that seemed like a nice standard for the speed of AI behavior - ///Do not read directly or mutate, instead use get_cooldown() - var/action_cooldown = CLICK_CD_MELEE - -/// Returns the delay to use for this behavior in the moment -/// Override to return a conditional delay -/datum/ai_behavior/proc/get_cooldown(datum/ai_controller/cooldown_for) - return action_cooldown - -/// Called by the ai controller when first being added. Additional arguments depend on the behavior type. -/// Return FALSE to cancel -/datum/ai_behavior/proc/setup(datum/ai_controller/controller, ...) + ///Cooldown between perform() calls; do not read directly use get_cooldown() + var/time_between_perform = 0 + /// TRUE after setup() has been called and before finish_action() completes. + var/running = FALSE + /// world.time when perform() may next be called. + var/next_perform_time = 0 + /// TRUE when the last perform() failed and we are waiting out next_perform_time to say we failed + var/failed_last_perform = FALSE + /// TRUE while an async perform kicked off by start_async() is going + VAR_PRIVATE/async_running = FALSE + /// TRUE once async finished and via finish_async + VAR_PRIVATE/async_finished = FALSE + /// AI_BEHAVIOR_* flags that came out of async perform + VAR_PRIVATE/async_result_flags = NONE + +/datum/bt_node/ai_behavior/has_active_descendants() + return running + +/datum/bt_node/ai_behavior/get_status_marker() + if(running) + return "*" + return ..() + +/datum/bt_node/ai_behavior/append_active_nodes(list/lines, indent) + if(running) + lines += "[indent][span_bold("● [label]")]" + +/** + * ai behavior tick. Runs setup() once on first activation, then perform() each tick. + * Respects per-controller cooldowns set by AI_BEHAVIOR_DELAY. + * Returns BT_SUCCESS / BT_FAILURE on completion, BT_RUNNING while active. + */ +/datum/bt_node/ai_behavior/tick(datum/ai_controller/controller, seconds_per_tick) + if(next_perform_time > world.time) + if(!running && failed_last_perform) + return BT_FAILURE + controller.active_execution_index = execution_index + return BT_RUNNING + + if(controller.bt_execution_log != null) // dont track if we're not viewing + if(length(controller.bt_execution_log) < BT_EXECUTION_LOG_MAX) + controller.bt_execution_log += execution_index + + if(!running) + if(!setup(controller)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] [type]: setup() failed") + return BT_FAILURE + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] starting [type]") + running = TRUE + + var/process_flags = perform(seconds_per_tick, controller) + + if(process_flags & AI_BEHAVIOR_DELAY) + next_perform_time = world.time + get_cooldown(controller) + if(process_flags & AI_BEHAVIOR_SUCCEEDED) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] [type]: succeeded") + failed_last_perform = FALSE + finish_action(controller, TRUE) + return BT_SUCCESS + if(process_flags & AI_BEHAVIOR_FAILED) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] [type]: failed") + failed_last_perform = TRUE + finish_action(controller, FALSE) + return BT_FAILURE + controller.active_execution_index = execution_index + return BT_RUNNING + +/// Returns the cooldown to apply after a AI_BEHAVIOR_DELAY perform(). Override for conditional delays. +/datum/bt_node/ai_behavior/proc/get_cooldown(datum/ai_controller/cooldown_for) + return time_between_perform + +/// Called when this behavior first activates on a controller. Return FALSE to abort (returns BT_FAILURE). +/datum/bt_node/ai_behavior/proc/setup(datum/ai_controller/controller) return TRUE -///Called by the AI controller when this action is performed -///Returns a set of flags defined in [code/__DEFINES/ai/ai.dm] -/datum/ai_behavior/proc/perform(seconds_per_tick, datum/ai_controller/controller, ...) +/// Called each tick while the behavior is running. Returns AI_BEHAVIOR_* flags. +/datum/bt_node/ai_behavior/proc/perform(seconds_per_tick, datum/ai_controller/controller) + SHOULD_NOT_SLEEP(TRUE) return -///Called when the action is finished. This needs the same args as perform besides the default ones -/datum/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded, ...) - controller.dequeue_behavior(src) - controller.behavior_args -= type - if(!(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT)) //If this was a movement task, reset our movement target if necessary - return - if(behavior_flags & AI_BEHAVIOR_KEEP_MOVE_TARGET_ON_FINISH) - return - clear_movement_target(controller) - controller.ai_movement.stop_moving_towards(controller) - EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] has [succeeded ? "succeeded" : "failed"] at performing [src]", get_turf(controller.pawn), "Behavior finished: [succeeded ? "Success" : "Failure"]") +/// Called when the behavior finishes (succeeded or failed). Subtypes should call ..(). +/datum/bt_node/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded) + SHOULD_CALL_PARENT(TRUE) + running = FALSE + async_running = FALSE + async_finished = FALSE + async_result_flags = NONE -/// Helper proc to ensure consistency in setting the source of the movement target -/datum/ai_behavior/proc/set_movement_target(datum/ai_controller/controller, atom/target, datum/ai_movement/new_movement) - controller.set_movement_target(type, target, new_movement) +///Checks if we're running async behavior, and if its finished, returns result flags +/datum/bt_node/ai_behavior/proc/handle_async() + if(async_running) + return AI_BEHAVIOR_DELAY + if(async_finished) + return async_result_flags | AI_BEHAVIOR_DELAY + return NONE -/// Clear the controller's movement target only if it was us who last set it -/datum/ai_behavior/proc/clear_movement_target(datum/ai_controller/controller) - if (controller.movement_target_source != type) +///Marks that async behavior has started and runs perform_async +/datum/bt_node/ai_behavior/proc/start_async() + async_running = TRUE + INVOKE_ASYNC(src, PROC_REF(perform_async), owning_controller) + return AI_BEHAVIOR_DELAY + +///Override this if you have sleeping behavior, be sure to implement the other async procs in perform() +/datum/bt_node/ai_behavior/proc/perform_async(datum/ai_controller/controller) + return + +/// Call from an async behavior after its sleeping call, before committing side effects. FALSE means the behavior was aborted/reset mid-flight bail out without side effects. +/datum/bt_node/ai_behavior/proc/async_still_valid() + return async_running && !QDELETED(owning_controller?.pawn) + +/// Call from an async behavior to commit its result. No-op if the behavior was aborted mid-flight. +/datum/bt_node/ai_behavior/proc/finish_async(result_flags) + if(!async_still_valid()) return - controller.set_movement_target(type, null) + async_result_flags = result_flags + async_finished = TRUE + async_running = FALSE + +/datum/bt_node/ai_behavior/proc/modify_cooldown(new_next_perform_time) + next_perform_time = new_next_perform_time + +/datum/bt_node/ai_behavior/reset_tick_state() + if(running) + finish_action(owning_controller, FALSE) + ..() diff --git a/code/datums/ai/_ai_bt_composites.dm b/code/datums/ai/_ai_bt_composites.dm new file mode 100644 index 000000000000..765472df88f7 --- /dev/null +++ b/code/datums/ai/_ai_bt_composites.dm @@ -0,0 +1,307 @@ +/** + * Base composite node. Holds an ordered list of child bt_node instances. + * */ +/datum/bt_node/composite + /// Typepaths of child nodes declared on the type. Resolved to instances at tree construction. + var/list/children_typepaths = null + /// Resolved child instances. Populated at tree construction. Do not set directly. + var/list/children = null + +/datum/bt_node/composite/Destroy() + QDEL_LIST(children) + return ..() + +/datum/bt_node/composite/get_children() + return children + +/datum/bt_node/composite/has_active_descendants() + if(!children) + return FALSE + for(var/datum/bt_node/child as anything in children) + if(child.has_active_descendants()) + return TRUE + return FALSE + +/datum/bt_node/composite/finalize_node(datum/ai_controller/controller, list/to_visit) + ..() + if(!children) + return + for(var/datum/bt_node/child as anything in children) + child.parent_node = src + to_visit += children + +/datum/bt_node/composite/set_descriptor_children(list/children_descs, datum/ai_controller/controller) + var/list/resolved = list() + for(var/child_entry in children_descs) + var/datum/bt_node/child_node = controller.get_or_build_node(child_entry) + if(!isnull(child_node)) + resolved += child_node + children = resolved + +/datum/bt_node/composite/collect_reset_children(list/to_visit) + if(children) + to_visit += children + +/datum/bt_node/composite/assign_execution_indices(counter) + execution_index = counter + counter++ + for(var/datum/bt_node/c in children) + counter = c.assign_execution_indices(counter) + last_execution_index = counter - 1 + return counter + +/** + * Sequence node: ticks children in order. + * Returns BT_FAILURE on first child failure. + * Returns BT_RUNNING on first child returning BT_RUNNING (stops further evaluation). + * Returns BT_SUCCESS only if all children succeed. + * + * Resumes from the last RUNNING child index rather than restarting from child 1. + */ +/datum/bt_node/composite/sequence + node_type = BT_NODE_SEQUENCE + label = "SEQUENCE" + /// Index of the child that last returned BT_RUNNING. + var/running_child_index = 0 + +/datum/bt_node/composite/sequence/tick(datum/ai_controller/controller, seconds_per_tick) + var/result = BT_SUCCESS + var/start = running_child_index || 1 + for(var/i in start to length(children)) + var/datum/bt_node/child = children[i] + var/child_result = child.tick(controller, seconds_per_tick) + if(controller.cancelled_during_tick) + return BT_FAILURE + if(child_result != BT_SUCCESS) + result = child_result + if(child_result == BT_RUNNING) + running_child_index = i + else + running_child_index = 0 + return result + + running_child_index = 0 + return result + +/datum/bt_node/composite/sequence/reset_tick_state() + . = ..() + running_child_index = 0 + +/datum/bt_node/composite/sequence/append_active_nodes(list/lines, indent) + var/found_active = FALSE + for(var/datum/bt_node/child as anything in children) + if(found_active) + lines += "[indent]↑ [child.label]" + else if(child.has_active_descendants()) + found_active = TRUE + child.append_active_nodes(lines, indent) + +/datum/bt_node/composite/sequence/append_full_tree_state(list/lines, indent) + var/child_info = running_child_index ? " (child [running_child_index]/[length(children)])" : "" + lines += "[indent][get_status_marker()] SEQUENCE[child_info]" + for(var/datum/bt_node/child as anything in children) + child.append_full_tree_state(lines, "[indent] ") + +/** + * Selector node: ticks children in order. + * Returns the first non-BT_FAILURE result (BT_SUCCESS or BT_RUNNING), stopping further evaluation. + * Returns BT_FAILURE only if all children fail. + * + * Resumes from the last RUNNING child index rather than restarting from child 1. + + */ +/datum/bt_node/composite/selector + node_type = BT_NODE_SELECTOR + label = "SELECTOR" + /// Index of the child that last returned BT_RUNNING. + var/running_child_index = 0 + +/datum/bt_node/composite/selector/tick(datum/ai_controller/controller, seconds_per_tick) + var/result = BT_FAILURE + var/start = running_child_index || 1 + for(var/i in start to length(children)) + var/datum/bt_node/child = children[i] + var/child_result = child.tick(controller, seconds_per_tick) + if(controller.cancelled_during_tick) + return BT_FAILURE + if(child_result != BT_FAILURE) + result = child_result + if(child_result == BT_RUNNING) + running_child_index = i + else + running_child_index = 0 + return result + + running_child_index = 0 + return result + +/datum/bt_node/composite/selector/reset_tick_state() + . = ..() + running_child_index = 0 + +/datum/bt_node/composite/selector/append_active_nodes(list/lines, indent) + for(var/datum/bt_node/child as anything in children) + if(child.has_active_descendants()) + child.append_active_nodes(lines, indent) + return + +/datum/bt_node/composite/selector/append_full_tree_state(list/lines, indent) + var/child_info = running_child_index ? " (child [running_child_index]/[length(children)])" : "" + lines += "[indent][get_status_marker()] SELECTOR[child_info]" + for(var/datum/bt_node/child as anything in children) + child.append_full_tree_state(lines, "[indent] ") + +/** + * Subplan node: Runs child and applies configurable restart policies + * when the child completes instead of propagating completion directly. + * + * success_policy: + * BT_SUBPLAN_SUCCEED_ON_SUCCESS (default) propagates BT_SUCCESS when all children succeed. + * BT_SUBPLAN_LOOP_ON_SUCCESS resets all children and returns BT_RUNNING, restarting next tick. + * + * failure_policy: + * BT_SUBPLAN_FAIL_ON_FAILURE (default) propagates BT_FAILURE when a child fails. + * BT_SUBPLAN_LOOP_ON_FAILURE resets all children and returns BT_RUNNING, restarting next tick. + * + * Combining both loop policies creates an infinite loop that only exits via an external observer abort or cancel_current_plan(), so be careful pls + */ +/datum/bt_node/composite/subplan + node_type = BT_NODE_SUBPLAN + /// BT_SUBPLAN_SUCCEED_ON_SUCCESS: propagate success (default). BT_SUBPLAN_LOOP_ON_SUCCESS: restart. + var/success_policy = BT_SUBPLAN_SUCCEED_ON_SUCCESS + /// BT_SUBPLAN_FAIL_ON_FAILURE: propagate failure (default). BT_SUBPLAN_LOOP_ON_FAILURE: restart. + var/failure_policy = BT_SUBPLAN_FAIL_ON_FAILURE + /// Minimum delay before ticking again after a loop policy restarts the child. 0 = immediate (default). + var/loop_delay = 0 + /// world.time when this subplan is next allowed to tick after a loop restart. + var/next_loop_time = 0 + +/datum/bt_node/composite/subplan/tick(datum/ai_controller/controller, seconds_per_tick) + if(loop_delay > 0 && next_loop_time > world.time) + return BT_RUNNING + + var/datum/bt_node/child = LAZYACCESS(children, 1) + if(isnull(child)) + next_loop_time = 0 + return BT_FAILURE + + var/child_result = child.tick(controller, seconds_per_tick) + if(controller.cancelled_during_tick) + return BT_FAILURE + + if(child_result == BT_RUNNING) + return BT_RUNNING + + if(child_result == BT_FAILURE) + if(failure_policy == BT_SUBPLAN_LOOP_ON_FAILURE) + child.reset_tick_state() + if(loop_delay > 0) + next_loop_time = world.time + loop_delay + return BT_RUNNING + next_loop_time = 0 + return BT_FAILURE + + if(success_policy == BT_SUBPLAN_LOOP_ON_SUCCESS) + child.reset_tick_state() + if(loop_delay > 0) + next_loop_time = world.time + loop_delay + return BT_RUNNING + next_loop_time = 0 + return BT_SUCCESS + +/datum/bt_node/composite/subplan/reset_tick_state() + . = ..() + next_loop_time = 0 + +/datum/bt_node/composite/subplan/set_descriptor_children(list/children_descs, datum/ai_controller/controller) + ..() + if(length(children) > 1) + var/datum/bt_node/composite/sequence/legacy_subplan_sequence = new + legacy_subplan_sequence.children = children + children = list(legacy_subplan_sequence) + +/** + * Parallel node: ticks ALL children every planning cycle, regardless of intermediate results. + * Success and failure are determined by the configurable success_policy and failure_policy. + * + * Intended use: run multiple independent branches simultaneously, e.g. action + locomotion + */ +/datum/bt_node/composite/parallel + node_type = BT_NODE_PARALLEL + label = "PARALLEL" + /// BT_PARALLEL_SUCCESS_CHILD_ONE: succeed when child 1 succeeds (default). + /// BT_PARALLEL_SUCCESS_ALL: succeed only when all children succeed. + var/success_policy = BT_PARALLEL_SUCCESS_CHILD_ONE + /// BT_PARALLEL_FAILURE_CHILD_ONE: fail when child 1 fails (default). + /// BT_PARALLEL_FAILURE_ANY: fail when any child fails. + var/failure_policy = BT_PARALLEL_FAILURE_CHILD_ONE + /// If TRUE, children 2+ that complete are reset and reticked + var/repeat_secondary = FALSE + /// Minimum delay before a repeat_secondary child can be re-ticked after completing. 0 = immediate (default). + var/repeat_secondary_delay = 0 + /// world.time values for when each secondary child is next allowed to tick. Null when no delays are active. + var/list/secondary_ready_at = null + /// If TRUE, when child 1 finishes (non-RUNNING), all children 2+ are cancelled and the parallel immediately returns child 1's result. + var/finish_on_primary = FALSE + +/datum/bt_node/composite/parallel/tick(datum/ai_controller/controller, seconds_per_tick) + var/succeeded = 0 + var/failed = 0 + var/primary_result + + for(var/i in 1 to length(children)) + var/datum/bt_node/child = children[i] + + if(i > 1 && repeat_secondary && repeat_secondary_delay > 0 && LAZYACCESS(secondary_ready_at, i) > world.time) + continue // secondary child is waiting out its repeat delay + + var/child_result = child.tick(controller, seconds_per_tick) + if(controller.cancelled_during_tick) + return BT_FAILURE + + if(i == 1) + primary_result = child_result + if(child_result == BT_SUCCESS) + succeeded++ + else if(child_result == BT_FAILURE) + failed++ + else if(repeat_secondary && child_result != BT_RUNNING) + child.reset_tick_state() + if(repeat_secondary_delay > 0) + if(length(secondary_ready_at) < i) + LAZYSETLEN(secondary_ready_at, i) + secondary_ready_at[i] = world.time + repeat_secondary_delay + else + if(child_result == BT_SUCCESS) + succeeded++ + else if(child_result == BT_FAILURE) + failed++ + + if(finish_on_primary && primary_result != BT_RUNNING) + // Secondaries may be mid-RUNNING here, so the reset must recurse to cancel any deeper active behaviors. + for(var/i in 2 to length(children)) + var/datum/bt_node/child = children[i] + child.reset_subtree_tick_states() + return primary_result + + if((failure_policy == BT_PARALLEL_FAILURE_CHILD_ONE && primary_result == BT_FAILURE) || \ + (failure_policy == BT_PARALLEL_FAILURE_ANY && failed > 0)) + return BT_FAILURE + if((success_policy == BT_PARALLEL_SUCCESS_CHILD_ONE && primary_result == BT_SUCCESS) || \ + (success_policy == BT_PARALLEL_SUCCESS_ALL && succeeded == length(children))) + return BT_SUCCESS + return BT_RUNNING + +/datum/bt_node/composite/parallel/reset_tick_state() + . = ..() + secondary_ready_at = null + +/datum/bt_node/composite/parallel/append_active_nodes(list/lines, indent) + for(var/datum/bt_node/child as anything in children) + if(child.has_active_descendants()) + child.append_active_nodes(lines, indent) + +/datum/bt_node/composite/parallel/append_full_tree_state(list/lines, indent) + lines += "[indent][get_status_marker()] PARALLEL" + for(var/datum/bt_node/child as anything in children) + child.append_full_tree_state(lines, "[indent] ") diff --git a/code/datums/ai/_ai_bt_decorators.dm b/code/datums/ai/_ai_bt_decorators.dm new file mode 100644 index 000000000000..eec04a978de9 --- /dev/null +++ b/code/datums/ai/_ai_bt_decorators.dm @@ -0,0 +1,242 @@ +/** + * Base decorator node. Wraps a single child with a condition check. + * + * + * Supports observer aborts: register to watch specific signals, which triggers a re-check of the condition, potentially aborting the plan depending on the observer_abort settings. + */ +/datum/bt_node/decorator + node_type = BT_NODE_DECORATOR + /// Typepath of the single child node. Resolved to an instance at tree construction. + var/child_typepath = null + /// Resolved child instance. Populated at tree construction. Do not set directly. + var/datum/bt_node/child = null + /// Observer abort mode. Controls reactive re-planning when watched keys change. BT_ABORT_NONE (default) means no reactivity. BT_ABORT_SELF triggers re-plan if we're inside one of our children and the condition changes, while BT_ABORT_LOWER_PRIORITY triggers re-plan if we are in a lower priority (e.g. further to the right) node and the condition changes. BT_ABORT_BOTH does both. + var/observer_abort = BT_ABORT_NONE + /// If TRUE, the result of check_condition() is inverted before gating the child. + var/invert = FALSE + /// Whether the child is currently BT_RUNNING. This makes tick() skip check_condition() and delegate directly to child.tick(). + var/child_active = FALSE + /// Set to TRUE once register_observe_signals() has been called for this instance. + var/observers_registered = FALSE + /// Set to TRUE when register_observe_signals() registered at least one signal. If this is not true but we are observing; then we need to check the condition every tick; not efficient, but allows for reactivity. + var/has_observer_signals = FALSE + /// Last result seen by poll_condition(). null = not yet polled. Used to detect condition changes when no signal is available. + var/last_poll_result = null + /// TRUE when this decorator is registered in the controller's polling_observers list. + var/is_polled = FALSE + /// When polling (no observer signals), minimum deciseconds between condition re-evaluations. 0 = every controller tick. Please don't run viewers() every tick bro. + var/polling_rate = 0 + /// world.time of the last poll_condition() evaluation. Only meaningful when polling_rate > 0. + VAR_PRIVATE/last_poll_time = 0 + + +/datum/bt_node/decorator/Destroy() + if(observers_registered) + unregister_observe_signals(owning_controller?.pawn) + if(is_polled) + LAZYREMOVE(owning_controller?.polling_observers, src) + is_polled = FALSE + observers_registered = FALSE + has_observer_signals = FALSE + QDEL_NULL(child) + return ..() + +/datum/bt_node/decorator/get_children() + return child ? list(child) : null + +/datum/bt_node/decorator/has_active_descendants() + return child && child.has_active_descendants() + +/datum/bt_node/decorator/finalize_node(datum/ai_controller/controller, list/to_visit) + ..() + if(child) + child.parent_node = src + to_visit += child + +/datum/bt_node/decorator/append_active_nodes(list/lines, indent) + if(child && child.has_active_descendants()) + lines += "[indent][label]" + child.append_active_nodes(lines, "[indent] ") + +/datum/bt_node/decorator/set_descriptor_children(list/children_descs, datum/ai_controller/controller) + var/datum/bt_node/resolved = controller.get_or_build_node(children_descs[1]) + if(!isnull(resolved)) + child = resolved + +/datum/bt_node/decorator/collect_reset_children(list/to_visit) + if(child) + to_visit += child + +/datum/bt_node/decorator/append_full_tree_state(list/lines, indent) + var/observer_text = "" + if(observer_abort != BT_ABORT_NONE) + var/abort_name = "" + if(observer_abort == BT_ABORT_SELF) + abort_name = "SELF" + else if(observer_abort == BT_ABORT_LOWER_PRIORITY) + abort_name = "LOWER" + else if(observer_abort == BT_ABORT_BOTH) + abort_name = "BOTH" + observer_text = " (abort-[abort_name])" + lines += "[indent][get_status_marker()] [label][observer_text]" + if(child) + child.append_full_tree_state(lines, "[indent] ") + +/datum/bt_node/decorator/tick(datum/ai_controller/controller, seconds_per_tick) + if(!observers_registered) + observers_registered = TRUE + if(observer_abort != BT_ABORT_NONE) + has_observer_signals = register_observe_signals(controller.pawn) + if(!has_observer_signals) + is_polled = TRUE + LAZYADDASSOC(controller.polling_observers, src, TRUE) + + var/child_ticked = FALSE + var/result + if(!child) + return BT_FAILURE + var/no_ticking_condition = observer_abort == BT_ABORT_NONE || has_observer_signals + if((no_ticking_condition || is_polled) && child_active) + child_ticked = TRUE + result = child.tick(controller, seconds_per_tick) + else if(check_condition(controller) == invert) + result = BT_FAILURE + else + child_ticked = TRUE + result = child.tick(controller, seconds_per_tick) + + if(controller.cancelled_during_tick) + child_active = FALSE + return BT_FAILURE + + if(no_ticking_condition || is_polled) + child_active = (result == BT_RUNNING) + if(child_ticked && !child_active) + on_child_complete(controller, result) + + return result + +/** + * Called when the child finishes (returns a non-RUNNING result after being ticked). + * NOT called when the condition gate blocks the child, or when the tree is cancelled mid-tick. + */ +/datum/bt_node/decorator/proc/on_child_complete(datum/ai_controller/controller, result) + return + +/** + * Override to implement custom condition logic. + * Return TRUE to allow child.tick() to proceed, FALSE to return BT_FAILURE immediately. + */ +/datum/bt_node/decorator/proc/check_condition(datum/ai_controller/controller) + return TRUE + +/** + * Proc called by the observer system when a watched key changes. + * Return TRUE if the decorator's condition would pass, FALSE otherwise. + */ +/datum/bt_node/decorator/proc/evaluate_for_observer(datum/ai_controller/controller) + return check_condition(controller) != invert + +/** + * Called by the controller's observer handler when a watched blackboard key changes. + * Re-evaluates evaluate_for_observer() and aborts based on observer_abort policy. + * + * BT_ABORT_SELF: condition became FALSE and we're running our children > cancel actions + * BT_ABORT_LOWER_PRIORITY: condition became TRUE and we're running lower priority nodes > cancel actions + */ +/// Called by the controller's polling loop for decorators that have no signal observers. +/// Sets a baseline on first call, then fires on_observed_change() only when the result changes. +/datum/bt_node/decorator/proc/poll_condition(datum/ai_controller/controller) + if(polling_rate && last_poll_time + polling_rate > world.time) + return + last_poll_time = world.time + var/current = evaluate_for_observer(controller) + if(last_poll_result == null) + last_poll_result = current + return + if(current != last_poll_result) + last_poll_result = current + on_observed_change(controller, null) + +/datum/bt_node/decorator/proc/on_observed_change(datum/ai_controller/controller, key) + var/condition_result = evaluate_for_observer(controller) + + if(!condition_result && (observer_abort & BT_ABORT_SELF)) + var/active = controller.active_execution_index + if(!execution_index || (active >= execution_index && active <= last_execution_index)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_DECISIONMAKING, "[controller.pawn] [type]: ABORT_SELF on key=[key] condition lost, replanning") + controller.cancel_current_plan() + + if(condition_result && (observer_abort & BT_ABORT_LOWER_PRIORITY)) + var/active = controller.active_execution_index + if(!execution_index || !active || active > last_execution_index) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_DECISIONMAKING, "[controller.pawn] [type]: ABORT_LOWER on key=[key] condition gained, replanning") + controller.cancel_current_plan() + +/datum/bt_node/decorator/reset_tick_state() + if(observers_registered) + unregister_observe_signals(owning_controller?.pawn) + if(is_polled) + LAZYREMOVE(owning_controller?.polling_observers, src) + is_polled = FALSE + observers_registered = FALSE + has_observer_signals = FALSE + last_poll_result = null + last_poll_time = 0 + child_active = FALSE + ..() + +/datum/bt_node/decorator/assign_execution_indices(counter) + execution_index = counter + counter++ + if(child) + counter = child.assign_execution_indices(counter) + last_execution_index = counter - 1 + return counter + +/// Override to register all signal observers for this decorator. Return TRUE if any were registered. If a decorator does not handle this and we have an observer_abort mode that isn't BT_ABORT_NONE, the system will fall back to ticking the condition every tick, which is less efficient but allows for reactivity without signals. +/datum/bt_node/decorator/proc/register_observe_signals(atom/pawn) + return FALSE + +/// Override to unregister all observers registered by register_observe_signals(). +/datum/bt_node/decorator/proc/unregister_observe_signals(atom/pawn) + return + +/// Shared signal handler. Calls on_observed_change() with owning_controller. +/datum/bt_node/decorator/proc/on_signal_changed(atom/source, ...) + SIGNAL_HANDLER + if(owning_controller) + on_observed_change(owning_controller, null) + + +/// Returns TRUE if the blackboard key holds a non-null, non-deleted value. +/datum/bt_node/decorator/proc/bb_key_exists(datum/ai_controller/controller, key) + return controller.blackboard_key_exists(key) + +/// Gates on whether the named override slot currently has an active override installed. +/// Observes COMSIG_AI_OVERRIDE_SLOT_CHANGED so it reacts immediately when a command is set or cleared. +/datum/bt_node/decorator/override_id_set + /// SUBPLAN_ID_* constant matching the override slot to watch. + var/override_id = null + +/datum/bt_node/decorator/override_id_set/check_condition(datum/ai_controller/controller) + var/datum/bt_node/subtree/potential_subtree = LAZYACCESS(controller.override_slots, override_id) + return !isnull(potential_subtree.override_node) + +/datum/bt_node/decorator/override_id_set/register_observe_signals(atom/pawn) + if(isnull(override_id)) + return FALSE + RegisterSignal(pawn, COMSIG_AI_OVERRIDE_SLOT_CHANGED(override_id), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/override_id_set/unregister_observe_signals(atom/pawn) + if(!isnull(override_id)) + UnregisterSignal(pawn, COMSIG_AI_OVERRIDE_SLOT_CHANGED(override_id)) + +/// Returns TRUE if the blackboard value at key equals the given value. +/datum/bt_node/decorator/proc/bb_key_equals(datum/ai_controller/controller, key, value) + return controller.blackboard[key] == value + +/// Returns TRUE if the blackboard value at key is strictly greater than threshold. +/datum/bt_node/decorator/proc/bb_key_greater(datum/ai_controller/controller, key, threshold) + return controller.blackboard[key] > threshold diff --git a/code/datums/ai/_ai_bt_node.dm b/code/datums/ai/_ai_bt_node.dm new file mode 100644 index 000000000000..fdff4feae20c --- /dev/null +++ b/code/datums/ai/_ai_bt_node.dm @@ -0,0 +1,109 @@ +///Base node for behavior tree nodes +/datum/bt_node + /// Node type identifier. + var/node_type = BT_NODE_LEAF + /// Pre-order depth-first index of this node in the tree. Assigned by finalize_tree(). + var/execution_index = 0 + /// Index of the last descendant node in this subtree. Equal to execution_index for leaves. + var/last_execution_index = 0 + /// Reference to this node's parent in the resolved tree. Set by ai_controller/finalize_tree(). + /// Null for root-level nodes. + var/datum/bt_node/parent_node = null + ///Owning controller for this node + var/datum/ai_controller/owning_controller = null + /// Short display label, set at New() by stripping standard path prefixes from the type. + var/label = "" + +/datum/bt_node/New() + . = ..() + if(!label) + var/t = "[type]" + t = replacetext(t, "/datum/bt_node/decorator/", "") + t = replacetext(t, "/datum/bt_node/ai_behavior/", "") + t = replacetext(t, "/datum/bt_node/subtree/", "") + label = t + +///Ticked by the ai_controller. Returns BT_SUCCESS, BT_FAILURE, or BT_RUNNING which can change how the parent responds. +/datum/bt_node/proc/tick(datum/ai_controller/controller, seconds_per_tick) + SHOULD_NOT_SLEEP(TRUE) + return BT_FAILURE + +/// Resets per-tick state for this node instance. Override in subtypes that hold tick state. +/datum/bt_node/proc/reset_tick_state() + return + +/// Resets this node and all of its descendants, cancelling any behaviors still running in the subtree. +/datum/bt_node/proc/reset_subtree_tick_states() + var/list/to_visit = list(src) + var/index = 1 + while(index <= length(to_visit)) + var/datum/bt_node/node = to_visit[index++] + node.reset_tick_state() + node.collect_reset_children(to_visit) + +/** + * Assigns pre-order depth-first execution indices to this node and its subtree. + * Called once per controller tree by finalize_tree(). + */ +/datum/bt_node/proc/assign_execution_indices(counter) + execution_index = counter + last_execution_index = counter + return counter + 1 + +/// Apply a configuration list to this node instance by assigning vars directly. +/datum/bt_node/proc/configure(list/config) + for(var/var_name in config) + vars[var_name] = config[var_name] + +/** + * Returns the list of direct child bt_node instances for tree traversal. + * Returns null for leaf nodes (default). Overridden in composites, decorators, and subtrees. + */ +/datum/bt_node/proc/get_children() + return null + +/// Returns TRUE if this node or any descendant has an active (running) ai_behavior leaf. +/datum/bt_node/proc/has_active_descendants() + return FALSE + +/// Walks descendants to find the node with the given execution_index. Returns null if not found. +/datum/bt_node/proc/find_by_index(target_index) + if(execution_index == target_index) + return src + var/list/ch = get_children() + if(!ch) + return null + for(var/datum/bt_node/child as anything in ch) + var/found = child.find_by_index(target_index) + if(found) + return found + return null + +/// Appends this node's active/upcoming state to lines for display. No-op for plain leaf nodes. +/datum/bt_node/proc/append_active_nodes(list/lines, indent) + return + +/// Called during finalize_tree() to set owning_controller, register overrides, and enqueue children. +/datum/bt_node/proc/finalize_node(datum/ai_controller/controller, list/to_visit) + owning_controller = controller + +/// Called during build_node_from_descriptor() to resolve and assign child nodes from JSON descriptors. +/datum/bt_node/proc/set_descriptor_children(list/children_descs, datum/ai_controller/controller) + return + +/// Returns a single-character status marker for display. Overridden by ai_behavior to check running. +/datum/bt_node/proc/get_status_marker() + return "o" + +/// Appends this node's full tree state (status + label + children) to lines for display. +/datum/bt_node/proc/append_full_tree_state(list/lines, indent) + lines += "[indent][get_status_marker()] [label]" + +/// Adds all children that must be visited during reset to to_visit. No-op for leaf nodes. +/datum/bt_node/proc/collect_reset_children(list/to_visit) + return + +/datum/bt_node/Destroy() + parent_node = null + owning_controller = null + return ..() diff --git a/code/datums/ai/_ai_bt_subtree.dm b/code/datums/ai/_ai_bt_subtree.dm new file mode 100644 index 000000000000..ea9874e78f75 --- /dev/null +++ b/code/datums/ai/_ai_bt_subtree.dm @@ -0,0 +1,88 @@ +/** + * Subtree node: a named, re-usable BT subgraph. + * + * Subtypes override New() to build an arbitrary internal tree and assign its root node + * to `root`. tick() delegates entirely to root.tick() and returns whatever it returns. + * + * This decouples the subtree's identity (its type path) from any specific composite + * semantics (selector/parallel/sequence). The root can be any node type. + * + * The controller's tree-walk helpers (setup_bt_observers, reset_bt_tick_states) descend + * through the `root` pointer to reach all internal nodes. + */ +/datum/bt_node/subtree + node_type = BT_NODE_SUBTREE + /// Repo-relative path to the .bt.json source file for this subtree (e.g. "code/datums/ai/bots/bot_patrol.bt.json"). + /// resolve_node_children() derives the compiled path from this and loads the tree at runtime. + var/behavior_tree_json = null + /// list of BT node descriptors defining this subtree's root. + /// resolve_node_children() builds `root` from this during tree construction. + var/list/behavior_nodes = null + /// The internal root node. Populated by resolve_node_children(). Do not set directly. + var/datum/bt_node/root = null + /// Set this to allow runtime overriding of this subtree, useful for things like pet commands! + var/override_id = null + /// Active override subtree. When set, tick() delegates to this node instead of root. + /// Set to null to deactivate the override. Managed by set_behavior_tree_override() only. + var/datum/bt_node/subtree/override_node = null + ///Any bindings this subtree has; assigned by the json + var/list/bindings = null + +/datum/bt_node/subtree/Destroy() + QDEL_NULL(root) + QDEL_NULL(override_node) + return ..() + +/datum/bt_node/subtree/tick(datum/ai_controller/controller, seconds_per_tick) + if(override_node) + return override_node.tick(controller, seconds_per_tick) + if(!root) + return BT_FAILURE + return root.tick(controller, seconds_per_tick) + +/datum/bt_node/subtree/get_children() + if(override_node) + return override_node.root ? list(override_node.root) : null + return root ? list(root) : null + +/datum/bt_node/subtree/has_active_descendants() + if(override_node) + return override_node.has_active_descendants() + return root && root.has_active_descendants() + +/datum/bt_node/subtree/finalize_node(datum/ai_controller/controller, list/to_visit) + ..() + if(!isnull(override_id)) + LAZYINITLIST(controller.override_slots) + controller.override_slots[override_id] = src + if(root) + root.parent_node = src + to_visit += root + if(override_node) + override_node.parent_node = src + to_visit += override_node + +/datum/bt_node/subtree/append_active_nodes(list/lines, indent) + if(root && root.has_active_descendants()) + root.append_active_nodes(lines, indent) + +/datum/bt_node/subtree/collect_reset_children(list/to_visit) + if(root) + to_visit += root + if(override_node) + to_visit += override_node + +/datum/bt_node/subtree/append_full_tree_state(list/lines, indent) + ..() + if(root) + root.append_full_tree_state(lines, "[indent] ") + +/datum/bt_node/subtree/assign_execution_indices(counter) + execution_index = counter + counter++ + if(root) + counter = root.assign_execution_indices(counter) + if(override_node) + counter = override_node.assign_execution_indices(counter) + last_execution_index = counter - 1 + return counter diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 520b2ae259a8..98cd64f9395e 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -15,35 +15,38 @@ multiple modular subtrees with behaviors * DO NOT set values in the blackboard directly, and especially not if you're adding a datum reference to this! * Use the setters, this is important for reference handing. */ - var/list/blackboard = list() + var/alist/blackboard = alist() ///Bitfield of traits for this AI to handle extra behavior var/ai_traits = DEFAULT_AI_FLAGS - ///Current actions planned to be performed by the AI in the upcoming plan - var/list/planned_behaviors = list() - ///Current actions being performed by the AI. - var/list/current_behaviors = list() - ///Current actions and their respective last time ran as an assoc list. - var/list/behavior_cooldowns = list() ///Current status of AI (OFF/ON) var/ai_status - ///Current movement target of the AI, generally set by decision making. - var/atom/current_movement_target - ///Identifier for what last touched our movement target, so it can be cleared conditionally - var/movement_target_source - ///Stored arguments for behaviors given during their initial creation - var/list/behavior_args = list() + ///Set by force_ai_off() when an outside system deliberately disables this AI. While TRUE, get_expected_ai_status() always returns AI_STATUS_OFF, so status recalculations (stat changes, z changes, client login/logout) cannot re-enable us. Cleared via clear_forced_off(). + var/forced_off = FALSE ///Tracks recent pathing attempts, if we fail too many in a row we fail our current plans. var/consecutive_pathing_attempts ///Can the AI remain in control if there is a client? var/continue_processing_when_client = FALSE ///distance to give up on target var/max_target_distance = 14 - ///All subtrees this AI has available, will run them in order, so make sure they're in the order you want them to run. On initialization of this type, it will start as a typepath(s) and get converted to references of ai_subtrees found in SSai_controllers when init_subtrees() is called - var/list/planning_subtrees - - ///The idle behavior this AI performs when it has no actions. - var/datum/idle_behavior/idle_behavior = null + /// Repo-relative path to the .bt.json source file for this controller (e.g. "code/datums/ai/basic_mobs/cleanbot.bt.json"). + /// initialize_behavior_tree() derives the compiled path from this and loads the BT tree at runtime. + var/behavior_tree_json = null + ///The root of our tree, which will contain + var/list/behavior_nodes + /// Execution index of the leaf node currently returning BT_RUNNING. 0 = nothing active. + var/active_execution_index = 0 + /// Set to TRUE by cancel_current_plan() when it fires mid-tick. Checked by composites to abort the current tick loop early, preventing running_child_index from being re-established after a reset. Cleared at the start of SelectBehaviors(). + var/cancelled_during_tick = FALSE + /// Draining log of all leaf execution indices that fired since the last bt_viewer poll. Null when no viewer is attached. + var/list/bt_execution_log = null + /// assoc list of override_id -> /datum/bt_node/subtree for runtime subtree replacement. + /// Populated by finalize_tree() when subtrees with override_id are found. Null until then. + var/list/override_slots = null + /// Decorators in polling mode (observer_abort set, no signal registered). Iterated after each SelectBehaviors tick so their condition is re-evaluated even when skipped by composite resume logic. + var/list/polling_observers = null + /// world.time of our last SelectBehaviors() tick from SSai_controllers. Used to derive the real seconds_per_tick under load; 0 means no tick since the last status change, so the first tick falls back to the subsystem wait. + var/last_bt_tick = 0 ///our current cell grid var/datum/cell_tracker/our_cells @@ -56,8 +59,6 @@ multiple modular subtrees with behaviors // The variables below are fucking stupid and should be put into the blackboard at some point. ///AI paused time var/paused_until = 0 - ///Can this AI idle? - var/can_idle = TRUE ///What distance should we be checking for interesting things when considering idling/deidling? Defaults to AI_DEFAULT_INTERESTING_DIST var/interesting_dist = AI_DEFAULT_INTERESTING_DIST /// TRUE if we're able to run, FALSE if we aren't @@ -65,18 +66,10 @@ multiple modular subtrees with behaviors /// Make sure you hook update_able_to_run() in setup_able_to_run() to whatever parameters changing that you added /// Otherwise we will not pay attention to them changing var/able_to_run = FALSE - /// are we even able to plan? - var/able_to_plan = TRUE - /// are we currently on failed planning timeout? - var/on_failed_planning_timeout = FALSE - /datum/ai_controller/New(atom/new_pawn) change_ai_movement_type(ai_movement) - init_subtrees() - - if(idle_behavior) - idle_behavior = SSidle_ai_behaviors.idle_behaviors[idle_behavior] + initialize_behavior_tree() if(!isnull(new_pawn)) // unit tests need the ai_controller to exist in isolation due to list schenanigans i hate it here PossessPawn(new_pawn) @@ -84,51 +77,232 @@ multiple modular subtrees with behaviors /datum/ai_controller/Destroy(force) UnpossessPawn(FALSE) if(ai_status) - GLOB.ai_controllers_by_status[ai_status] -= src + SSai_controllers.ai_controllers_by_status[ai_status] -= src for(var/datum/controller/subsystem/ai_controllers/controller_subsystem in Master.subsystems) if(controller_subsystem.planning_status == ai_status) controller_subsystem.currentrun -= src break our_cells = null - set_movement_target(type, null) if(ai_movement.moving_controllers[src]) ai_movement.stop_moving_towards(src) + QDEL_LIST(behavior_nodes) return ..() -///Sets the current movement target, with an optional param to override the movement behavior -/datum/ai_controller/proc/set_movement_target(source, atom/target, datum/ai_movement/new_movement) - if(current_movement_target) - UnregisterSignal(current_movement_target, list(COMSIG_MOVABLE_MOVED, COMSIG_PREQDELETED)) - if(!isnull(target) && !isatom(target)) - stack_trace("[pawn]'s current movement target is not an atom, rather a [target.type]! Did you accidentally set it to a weakref?") - CancelActions() - return - movement_target_source = source - current_movement_target = target - if(!isnull(current_movement_target)) - RegisterSignal(current_movement_target, COMSIG_MOVABLE_MOVED, PROC_REF(on_movement_target_move)) - RegisterSignal(current_movement_target, COMSIG_PREQDELETED, PROC_REF(on_movement_target_delete)) - if(new_movement) - change_ai_movement_type(new_movement) - ///Overrides the current ai_movement of this controller with a new one /datum/ai_controller/proc/change_ai_movement_type(datum/ai_movement/new_movement) ai_movement = SSai_movement.movement_types[new_movement] -///Completely replaces the planning_subtrees with a new set based on argument provided, list provided must contain specifically typepaths -/datum/ai_controller/proc/replace_planning_subtrees(list/typepaths_of_new_subtrees) - planning_subtrees = typepaths_of_new_subtrees - init_subtrees() +///Completely replaces the behavior_nodes with a new set based on argument provided. +/datum/ai_controller/proc/replace_behavior_nodes(list/typepaths_of_new_subtrees) + var/list/old_nodes = behavior_nodes + behavior_nodes = typepaths_of_new_subtrees + initialize_behavior_tree() + QDEL_LIST(old_nodes) + +/// Resolves the children/child of a composite or decorator node, creating configured instances. +/// Safe to call on any node type; non-composite/non-decorator nodes are a no-op. +/datum/ai_controller/proc/resolve_node_children(datum/bt_node/node) + if(istype(node, /datum/bt_node/composite)) + var/datum/bt_node/composite/comp = node + if(!LAZYLEN(comp.children_typepaths) || LAZYLEN(comp.children)) + return + var/list/resolved_children = list() + for(var/child_type in comp.children_typepaths) + var/list/config = comp.children_typepaths[child_type] + var/datum/bt_node/child = resolve_child_node(child_type, config) + if(isnull(child)) + stack_trace("BT composite [node.type] references unknown child type [child_type]") + continue + resolved_children += child + comp.children = resolved_children + else if(istype(node, /datum/bt_node/decorator)) + var/datum/bt_node/decorator/dec = node + if(isnull(dec.child_typepath) || !isnull(dec.child)) + return + dec.child = resolve_child_node(dec.child_typepath, null) + if(isnull(dec.child)) + stack_trace("BT decorator [node.type] references unknown child type [dec.child_typepath]") + else if(istype(node, /datum/bt_node/subtree)) + var/datum/bt_node/subtree/sub = node + if(!isnull(sub.behavior_nodes) && isnull(sub.root)) + sub.root = build_node_from_descriptor(sub.behavior_nodes) + else if(!isnull(sub.behavior_tree_json) && isnull(sub.root)) + var/file = file(BT_COMPILED_PATH(sub.behavior_tree_json)) + var/list/raw_desc = json_decode(file2text(file)) + if(LAZYLEN(sub.bindings) || !isnull(raw_desc[BT_DESC_BINDINGS])) + raw_desc = apply_bindings_to_descriptor(raw_desc, sub.bindings) + sub.root = build_node_from_descriptor(raw_desc) + +// Always creates a fresh instance regardless of whether config is provided. +/datum/ai_controller/proc/resolve_child_node(child_type, list/config) + if(!ispath(child_type, /datum/bt_node)) + return null + var/datum/bt_node/child = new child_type + if(config) + child.configure(config) + resolve_node_children(child) + return child + +/datum/ai_controller/proc/get_or_build_node(entry) + if(ispath(entry)) + if(!ispath(entry, /datum/bt_node)) + stack_trace("get_or_build_node() received non-BT typepath: [entry]") + return null + var/datum/bt_node/node = new entry + resolve_node_children(node) + return node + if(islist(entry)) + return build_node_from_descriptor(entry) + stack_trace("get_or_build_node() received unexpected entry type: [entry]") + return null + +///Loads and decodes a compiled BT JSON file into a node tree. +/datum/ai_controller/proc/load_tree_from_json(path) + var/file = file(path) + var/list/desc = json_decode(file2text(file)) + return build_node_from_descriptor(desc) -///Loops over the subtrees in planning_subtrees and looks at the ai_controllers to grab a reference, ENSURE planning_subtrees ARE TYPEPATHS AND NOT INSTANCES/REFERENCES BEFORE EXECUTING THIS -/datum/ai_controller/proc/init_subtrees() - if(!LAZYLEN(planning_subtrees)) +/** + * Merges call-site binding overrides with the subtree's declared defaults, + * then substitutes all $name placeholders in the descriptor tree. + * Returns a new descriptor with BT_DESC_BINDINGS stripped and placeholders resolved. + */ +/datum/ai_controller/proc/apply_bindings_to_descriptor(list/desc, list/call_site_bindings) + var/list/merged = list() + var/list/declared = desc[BT_DESC_BINDINGS] + for(var/name in declared) + merged[name] = declared[name]["default"] + for(var/name in call_site_bindings) + merged[name] = call_site_bindings[name] + return _substitute_bindings(desc, merged) + +/// Recursively walks a descriptor list, replacing "$name" strings with their bound values. +/datum/ai_controller/proc/_substitute_bindings(list/desc, list/merged) + var/list/out = list() + for(var/key in desc) + if(key == BT_DESC_BINDINGS) + continue + var/value = desc[key] + if(islist(value)) + out[key] = _substitute_bindings_in_list(value, merged) + else if(istext(value) && copytext(value, 1, 2) == "$") + var/binding_name = copytext(value, 2) + out[key] = isnull(merged[binding_name]) ? value : merged[binding_name] + else + out[key] = value + return out + +/// Substitutes bindings inside a descriptor value list, preserving assoc entries +/datum/ai_controller/proc/_substitute_bindings_in_list(list/input, list/merged) + var/list/resolved_list = list() + for(var/item in input) + var/assoc_value = isnum(item) ? null : input[item] + if(!isnull(assoc_value)) + if(islist(assoc_value)) + resolved_list[item] = _substitute_bindings_in_list(assoc_value, merged) //recursion baby + else if(istext(assoc_value) && copytext(assoc_value, 1, 2) == "$") + var/binding_name = copytext(assoc_value, 2) + resolved_list[item] = isnull(merged[binding_name]) ? assoc_value : merged[binding_name] + else + resolved_list[item] = assoc_value + else if(islist(item)) + resolved_list += list(_substitute_bindings(item, merged)) + else if(istext(item) && copytext(item, 1, 2) == "$") + var/binding_name = copytext(item, 2) + var/resolved = isnull(merged[binding_name]) ? item : merged[binding_name] + if(islist(resolved)) + resolved_list += list(resolved) + else + resolved_list += resolved + else + resolved_list += item + return resolved_list + +/** + * Recursively builds a BT node tree from a descriptor list. + * BT_DESC_TYPE and BT_DESC_CHILDREN are consumed internally; all other keys are written + * as vars onto the node. String values starting with "/" are resolved via text2path so + * typepath args (e.g. "/datum/ai_movement/basic_avoidance") arrive as actual types. + * If you put / in a string then yeah that might cause issues, should probably fix that later! + */ +/datum/ai_controller/proc/build_node_from_descriptor(list/desc) + var/raw_type = desc[BT_DESC_TYPE] + if(!raw_type) // This can happen if we have an overriden type with no binding. (e.g. subtrees not being overriden and default to null) + return null + var/node_type = ispath(raw_type) ? raw_type : text2path(raw_type) + if(isnull(node_type)) + stack_trace("build_node_from_descriptor(): unknown typepath '[raw_type]'") + return null + var/datum/bt_node/node = new node_type + for(var/key in desc) + if(key == BT_DESC_TYPE || key == BT_DESC_CHILDREN || key == BT_DESC_BINDINGS) + continue + var/value = desc[key] + if(islist(value)) + var/list/resolved = value + for(var/i in 1 to length(resolved)) + if(istext(resolved[i])) + var/as_path = text2path(resolved[i]) + if(!isnull(as_path)) + resolved[i] = as_path + else if(istext(value)) + var/as_path = text2path(value) + if(!isnull(as_path)) + value = as_path + node.vars[key] = value + resolve_node_children(node) + var/list/children_descs = desc[BT_DESC_CHILDREN] + if(LAZYLEN(children_descs)) + node.set_descriptor_children(children_descs, src) + return node + +/// Builds the per-controller BT node tree from behavior_nodes typepaths or descriptors, then finalizes it. +/datum/ai_controller/proc/initialize_behavior_tree() + if(!isnull(behavior_tree_json) && !LAZYLEN(behavior_nodes)) + var/compiled_path = BT_COMPILED_PATH(behavior_tree_json) //Find the compiled version of this BT + var/datum/bt_node/root = load_tree_from_json(compiled_path) + if(isnull(root)) + stack_trace("[type] failed to load behavior tree from compiled JSON: [compiled_path]") + return + behavior_nodes = list(root) + finalize_tree() + return + if(!LAZYLEN(behavior_nodes)) return var/list/temp_subtree_list = list() - for(var/subtree in planning_subtrees) - var/subtree_instance = GLOB.ai_subtrees[subtree] - temp_subtree_list += subtree_instance - planning_subtrees = temp_subtree_list + if(!isnull(behavior_nodes[BT_DESC_TYPE])) + var/datum/bt_node/node_instance = get_or_build_node(behavior_nodes) + if(isnull(node_instance)) + stack_trace("[type]'s behavior_nodes BT descriptor could not be built") + else + temp_subtree_list += node_instance + else + for(var/entry in behavior_nodes) + var/datum/bt_node/node_instance = get_or_build_node(entry) + if(isnull(node_instance)) + stack_trace("[type]'s behavior_nodes contains unknown entry: [entry]") + continue + temp_subtree_list += node_instance + behavior_nodes = temp_subtree_list + finalize_tree() + +/// Walks the resolved tree to set owning_controller and parent_node on all nodes, populates +/// override_slots, and assigns pre-order execution indices. Called after initialize_behavior_tree() and +/// after set_behavior_tree_override() installs or removes an override node. +/datum/ai_controller/proc/finalize_tree() + if(!LAZYLEN(behavior_nodes)) + return + override_slots = null + var/list/to_visit = behavior_nodes.Copy() + for(var/datum/bt_node/root in behavior_nodes) + root.parent_node = null + var/index = 1 + while(index <= length(to_visit)) //while loop so we can recursively keep populating this list + var/datum/bt_node/node = to_visit[index++] + node.finalize_node(src, to_visit) + var/counter = 1 + for(var/datum/bt_node/root in behavior_nodes) + counter = root.assign_execution_indices(counter) ///Proc to move from one pawn to another, this will destroy the target's existing controller. /datum/ai_controller/proc/PossessPawn(atom/new_pawn) @@ -145,10 +319,11 @@ multiple modular subtrees with behaviors pawn = new_pawn pawn.ai_controller = src + set_blackboard_key(BB_MY_PAWN, pawn, FALSE) //Don't track the datum we already handle qdel of pawn here. var/turf/pawn_turf = get_turf(pawn) if(pawn_turf) - GLOB.ai_controllers_by_zlevel[pawn_turf.z] += src + SSai_controllers.ai_controllers_by_zlevel[pawn_turf.z] += src SEND_SIGNAL(src, COMSIG_AI_CONTROLLER_POSSESSED_PAWN) @@ -159,32 +334,19 @@ multiple modular subtrees with behaviors RegisterSignal(pawn, COMSIG_QDELETING, PROC_REF(on_pawn_qdeleted)) RegisterSignal(pawn, COMSIG_EVLOGGING_ENABLED, PROC_REF(on_pawn_evlogging_enabled)) RegisterSignal(pawn, COMSIG_EVLOGGING_DISABLED, PROC_REF(on_pawn_evlogging_disabled)) - update_able_to_run() - setup_able_to_run() our_cells = new(interesting_dist, interesting_dist, 1) set_new_cells() + update_able_to_run() + setup_able_to_run() + RegisterSignal(pawn, COMSIG_MOVABLE_MOVED, PROC_REF(update_grid)) /datum/ai_controller/proc/update_grid(datum/source, datum/spatial_grid_cell/new_cell) SIGNAL_HANDLER set_new_cells() - if(current_movement_target) - check_target_max_distance() - -/datum/ai_controller/proc/on_movement_target_move(atom/source) - SIGNAL_HANDLER - check_target_max_distance() - -/datum/ai_controller/proc/on_movement_target_delete(atom/source) - SIGNAL_HANDLER - set_movement_target(source = type, target = null) - -/datum/ai_controller/proc/check_target_max_distance() - if(get_dist(current_movement_target, pawn) > max_target_distance) - CancelActions() /datum/ai_controller/proc/set_new_cells() if(isnull(our_cells)) @@ -206,14 +368,16 @@ multiple modular subtrees with behaviors recalculate_idle() -/datum/ai_controller/proc/should_idle() - if(!can_idle || isnull(our_cells)) +///Returns TRUE if a living mob with a client is in one of our tracked spatial grid cells +/datum/ai_controller/proc/has_nearby_client() + if(isnull(our_cells)) return FALSE for(var/datum/spatial_grid_cell/grid as anything in our_cells.member_cells) if(locate(/mob/living) in grid.client_contents) - return FALSE - return TRUE + return TRUE + return FALSE +///Check if mob should go into idle/low-priority (from spatial cells) /datum/ai_controller/proc/recalculate_idle(datum/exited) if(ai_status == AI_STATUS_OFF) return @@ -229,17 +393,18 @@ multiple modular subtrees with behaviors if(distance <= interesting_dist) //is our target in between interesting cells? return - if(should_idle()) - set_ai_status(AI_STATUS_IDLE) + reset_ai_status() /datum/ai_controller/proc/on_client_enter(datum/source, list/target_list) SIGNAL_HANDLER + if(ai_status == AI_STATUS_ON) + return + if (!(locate(/mob/living) in target_list)) return - if(ai_status == AI_STATUS_IDLE) - set_ai_status(AI_STATUS_ON) + reset_ai_status() /datum/ai_controller/proc/on_client_exit(datum/source, datum/exited) SIGNAL_HANDLER @@ -250,12 +415,37 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/reset_ai_status() set_ai_status(get_expected_ai_status()) +/** + * Deliberately disables this AI until clear_forced_off() is called. + * Unlike a bare set_ai_status(AI_STATUS_OFF), this survives status recalculations + * from stat changes, z-level changes, client login/logout and the like. + */ +/datum/ai_controller/proc/force_ai_off(additional_flags = NONE) + forced_off = TRUE + set_ai_status(AI_STATUS_OFF, additional_flags) + +/// Undoes force_ai_off() and recalculates what status we should be in. +/datum/ai_controller/proc/clear_forced_off() + forced_off = FALSE + reset_ai_status() + /** * Gets the AI status we expect the AI controller to be on at this current moment. - * Returns AI_STATUS_OFF if it's inhabited by a Client and shouldn't be, if it's dead and cannot act while dead, or is on a z level without clients. - * Returns AI_STATUS_ON otherwise. + * Returns AI_STATUS_OFF if it has been forced off, is inhabited by a Client and shouldn't be, is dead and cannot act while dead, + * or is sleeping for performance (off-station with no nearby client and not flagged RUN_WHILE_UNWATCHED; client arrival wakes these automatically). + * Otherwise returns AI_STATUS_ON or AI_STATUS_ON_LOW, see get_active_ai_status(). */ /datum/ai_controller/proc/get_expected_ai_status() + + +/* +#ifdef AI_PERFORMANCE_TESTING + return AI_STATUS_ON +#endif +*/ + if (forced_off) + return AI_STATUS_OFF + if (isnull(get_turf(pawn))) return AI_STATUS_OFF @@ -271,16 +461,35 @@ multiple modular subtrees with behaviors return AI_STATUS_ON return AI_STATUS_OFF - var/turf/pawn_turf = get_turf(mob_pawn) #ifdef TESTING + var/turf/pawn_turf = get_turf(mob_pawn) if(!pawn_turf) CRASH("AI controller [src] controlling pawn ([pawn]) is not on a turf.") #endif - if(!length(SSmobs.clients_by_zlevel[pawn_turf.z]) || on_failed_planning_timeout || !able_to_run) + if(!able_to_run) return AI_STATUS_OFF - if(should_idle()) - return AI_STATUS_IDLE - return AI_STATUS_ON + return get_active_ai_status() + +/** + * Classifies an active AI controller into a priority tier. + * Returns AI_STATUS_ON for controllers on station/shuttle territory, with a nearby client, + * Returns AI_STATUS_ON_LOW for other active controllers unless they have ALWAYS_HIGH_PRIORITY. + * else its AI_STATUS_OFF + */ +/datum/ai_controller/proc/get_active_ai_status() + var/turf/pawn_turf = get_turf(pawn) + var/area/pawn_area = pawn_turf ? get_area(pawn_turf) : null + // AI actually standing on the station or a shuttle always stays high priority + if(istype(pawn_area, /area/station) || istype(pawn_area, /area/shuttle)) + return AI_STATUS_ON + if(has_nearby_client()) + return AI_STATUS_ON + if(ai_traits & RUN_WHILE_UNWATCHED) + if(ai_traits & ALWAYS_HIGH_PRIORITY) + return AI_STATUS_ON + else + return AI_STATUS_ON_LOW + return AI_STATUS_OFF ///Called when the AI controller pawn changes z levels, we check if there's any clients on the new one and wake up the AI if there is. /datum/ai_controller/proc/on_changed_z_level(atom/source, turf/old_turf, turf/new_turf, same_z_layer, notify_contents) @@ -290,10 +499,10 @@ multiple modular subtrees with behaviors if((mob_pawn?.client && !continue_processing_when_client)) return if(old_turf) - GLOB.ai_controllers_by_zlevel[old_turf.z] -= src + SSai_controllers.ai_controllers_by_zlevel[old_turf.z] -= src if(isnull(new_turf)) return - GLOB.ai_controllers_by_zlevel[new_turf.z] += src + SSai_controllers.ai_controllers_by_zlevel[new_turf.z] += src reset_ai_status() ///Abstract proc for initializing the pawn to the new controller @@ -307,6 +516,7 @@ multiple modular subtrees with behaviors return // instantiated without an applicable pawn, fine SEND_SIGNAL(src, COMSIG_AI_CONTROLLER_UNPOSSESSED_PAWN) + reset_bt_tick_states() set_ai_status(AI_STATUS_OFF) UnregisterSignal(pawn, list(COMSIG_MOVABLE_Z_CHANGED, COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT, COMSIG_MOB_STATCHANGE, COMSIG_QDELETING, COMSIG_EVLOGGING_ENABLED)) clear_able_to_run() @@ -314,13 +524,54 @@ multiple modular subtrees with behaviors ai_movement.stop_moving_towards(src) var/turf/pawn_turf = get_turf(pawn) if(pawn_turf) - GLOB.ai_controllers_by_zlevel[pawn_turf.z] -= src - remove_from_unplanned_controllers() + SSai_controllers.ai_controllers_by_zlevel[pawn_turf.z] -= src pawn.ai_controller = null pawn = null if(destroy) qdel(src) +///Call reset tick state on every node in the tree. +/datum/ai_controller/proc/reset_bt_tick_states() + if(!LAZYLEN(behavior_nodes)) + return + var/list/to_visit = behavior_nodes.Copy() + var/index = 1 + while(index <= length(to_visit)) + var/datum/bt_node/node = to_visit[index++] + node.reset_tick_state() + node.collect_reset_children(to_visit) + +/** + * Installs or removes a runtime override on the subtree slot registered with the given id. + * + * id - The ID for this slot + * override_subtree - actual subtree we're setting + */ +/datum/ai_controller/proc/set_behavior_tree_override(id, override_subtree) + var/datum/bt_node/subtree/slot = LAZYACCESS(override_slots, id) + if(isnull(slot)) + return + + var/current_type = isnull(slot.override_node) ? null : slot.override_node.type + if(current_type == override_subtree) + return + + if(slot.override_node) + slot.override_node.reset_tick_state() + QDEL_NULL(slot.override_node) + + if(isnull(override_subtree)) + finalize_tree() + SEND_SIGNAL(pawn, COMSIG_AI_OVERRIDE_SLOT_CHANGED(id), null) + return + + var/datum/bt_node/subtree/new_node = new override_subtree + resolve_node_children(new_node) + slot.override_node = new_node + finalize_tree() + cancel_current_plan() // Reset, not ideal; Maybe later on we can do this more gracefully. + SEND_SIGNAL(pawn, COMSIG_AI_OVERRIDE_SLOT_CHANGED(id), override_subtree) + /datum/ai_controller/proc/setup_able_to_run() // paused_until is handled by PauseAi() manually RegisterSignals(pawn, list(SIGNAL_ADDTRAIT(TRAIT_AI_PAUSED), SIGNAL_REMOVETRAIT(TRAIT_AI_PAUSED)), PROC_REF(update_able_to_run)) @@ -333,7 +584,7 @@ multiple modular subtrees with behaviors var/run_flags = get_able_to_run() if(run_flags & AI_UNABLE_TO_RUN) able_to_run = FALSE - GLOB.move_manager.stop_looping(pawn) //stop moving + ai_movement.fail_movement(src) else able_to_run = TRUE set_ai_status(get_expected_ai_status(), run_flags) @@ -347,19 +598,16 @@ multiple modular subtrees with behaviors return NONE ///Can this pawn interact with objects? -/datum/ai_controller/proc/ai_can_interact() - SHOULD_CALL_PARENT(TRUE) - return !QDELETED(pawn) +/datum/ai_controller/proc/ai_can_interact(atom/target) + return !QDELETED(pawn) && !QDELETED(target) ///Interact with objects /datum/ai_controller/proc/ai_interact(target, combat_mode, list/modifiers) - if(!ai_can_interact()) - return FALSE - var/atom/final_target = isdatum(target) ? target : blackboard[target] //incase we got a blackboard key instead - if(QDELETED(final_target)) + if(!ai_can_interact(final_target)) return FALSE + var/params = list2params(modifiers) var/mob/living/living_pawn = pawn if(isnull(combat_mode)) @@ -372,217 +620,43 @@ multiple modular subtrees with behaviors living_pawn.set_combat_mode(old_combat_mode) return TRUE -///Runs any actions that are currently running -/datum/ai_controller/process(seconds_per_tick) - - for(var/datum/ai_behavior/current_behavior as anything in current_behaviors) - - // Convert the current behaviour action cooldown to realtime seconds from deciseconds.current_behavior - // Then pick the max of this and the seconds_per_tick passed to ai_controller.process() - // Action cooldowns cannot happen faster than seconds_per_tick, so seconds_per_tick should be the value used in this scenario. - var/action_seconds_per_tick = max(current_behavior.get_cooldown(src) * 0.1, seconds_per_tick) - - if(!(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT)) - if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown - continue - ProcessBehavior(action_seconds_per_tick, current_behavior) - return - - if(isnull(current_movement_target)) - fail_behavior(current_behavior) - return - - ///Stops pawns from performing such actions that should require the target to be adjacent. - var/atom/movable/moving_pawn = pawn - var/can_reach = !(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_REACH) || current_movement_target.IsReachableBy(moving_pawn) - if(can_reach && current_behavior.required_distance >= get_dist(moving_pawn, current_movement_target)) ///Are we close enough to engage? - if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop. - ai_movement.stop_moving_towards(src) - - if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown - continue - ProcessBehavior(action_seconds_per_tick, current_behavior) - return - - if(ai_movement.moving_controllers[src] != current_movement_target) //We're too far, if we're not already moving start doing it. - ai_movement.start_moving_towards(src, current_movement_target, current_behavior.required_distance) //Then start moving - - if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //If we can move and perform then do so. - if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown - continue - ProcessBehavior(action_seconds_per_tick, current_behavior) - return ///This is where you decide what actions are taken by the AI. /datum/ai_controller/proc/SelectBehaviors(seconds_per_tick) - SHOULD_NOT_SLEEP(TRUE) //Fuck you don't sleep in procs like this. - planned_behaviors.Cut() - - for(var/datum/ai_planning_subtree/subtree as anything in planning_subtrees) - if(subtree.SelectBehaviors(src, seconds_per_tick) == SUBTREE_RETURN_FINISH_PLANNING) + SHOULD_NOT_SLEEP(TRUE) + cancelled_during_tick = FALSE + if(LAZYLEN(polling_observers)) + for(var/datum/bt_node/decorator/dec as anything in polling_observers.Copy()) + dec.poll_condition(src) + for(var/datum/bt_node/node as anything in behavior_nodes) + if(node.tick(src, seconds_per_tick) == BT_RUNNING) break - SEND_SIGNAL(src, COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS, current_behaviors, planned_behaviors) - for(var/datum/ai_behavior/forgotten_behavior as anything in current_behaviors - planned_behaviors) - var/list/arguments = list(src, FALSE) - var/list/stored_arguments = behavior_args[type] - if(stored_arguments) - arguments += stored_arguments - forgotten_behavior.finish_action(arglist(arguments)) - - if(IS_EVLOGGING) - var/list/event_text = list() - event_text += "New plan starting!" - - for(var/datum/ai_behavior/behavior in planned_behaviors) - event_text += "Queued behavior [behavior.type]" - - EVLOG_TEXT(src, EVLOG_CATEGORY_AI_DECISIONMAKING, jointext(event_text, "\n")) - -///This proc handles changing ai status, and starts/stops processing if required. +///This proc handles changing ai status and updates the planning subsystem list. /datum/ai_controller/proc/set_ai_status(new_ai_status, additional_flags = NONE) if(ai_status == new_ai_status) return FALSE //no change //remove old status, if we've got one if(ai_status) - GLOB.ai_controllers_by_status[ai_status] -= src + SSai_controllers.ai_controllers_by_status[ai_status] -= src for(var/datum/controller/subsystem/ai_controllers/controller_subsystem in Master.subsystems) if(controller_subsystem.planning_status == ai_status) controller_subsystem.currentrun -= src break - remove_from_unplanned_controllers() - stop_previous_processing() ai_status = new_ai_status - GLOB.ai_controllers_by_status[new_ai_status] += src + last_bt_tick = 0 // don't count time spent in the previous status towards the next tick's seconds_per_tick + SSai_controllers.ai_controllers_by_status[new_ai_status] += src if(ai_status == AI_STATUS_OFF) if(!(additional_flags & AI_PREVENT_CANCEL_ACTIONS)) - CancelActions() - return - if(!length(current_behaviors)) - add_to_unplanned_controllers() - return - start_ai_processing() - -/datum/ai_controller/proc/start_ai_processing() - switch(ai_status) - if(AI_STATUS_ON) - START_PROCESSING(SSai_behaviors, src) - if(AI_STATUS_IDLE) - START_PROCESSING(SSidle_ai_behaviors, src) - -/datum/ai_controller/proc/stop_previous_processing() - switch(ai_status) - if(AI_STATUS_ON) - STOP_PROCESSING(SSai_behaviors, src) - if(AI_STATUS_IDLE) - STOP_PROCESSING(SSidle_ai_behaviors, src) - -/datum/ai_controller/proc/PauseAi(time) - paused_until = world.time + time - update_able_to_run() - addtimer(CALLBACK(src, PROC_REF(update_able_to_run)), time) - -/datum/ai_controller/proc/add_to_unplanned_controllers() - if(isnull(ai_status) || ai_status == AI_STATUS_OFF || isnull(idle_behavior)) - return - GLOB.unplanned_controllers[ai_status][src] = TRUE - -/datum/ai_controller/proc/remove_from_unplanned_controllers() - if(isnull(ai_status) || ai_status == AI_STATUS_OFF) - return - GLOB.unplanned_controllers[ai_status] -= src - for(var/datum/controller/subsystem/unplanned_controllers/potential_holder as anything in GLOB.unplanned_controller_subsystems) - if(potential_holder.target_status == ai_status) - potential_holder.current_run -= src - -/datum/ai_controller/proc/modify_cooldown(datum/ai_behavior/behavior, new_cooldown) - behavior_cooldowns[behavior] = new_cooldown - -///Call this to add a behavior to the stack. -/datum/ai_controller/proc/queue_behavior(behavior_type, ...) - var/datum/ai_behavior/behavior = GET_AI_BEHAVIOR(behavior_type) - if(!behavior) - CRASH("Behavior [behavior_type] not found.") - var/list/arguments = args.Copy() - arguments[1] = src - - if(current_behaviors[behavior]) ///It's still in the plan, don't add it again to current_behaviors but do keep it in the planned behavior list so its not cancelled - planned_behaviors[behavior] = TRUE - return - - if(!behavior.setup(arglist(arguments))) - return - - var/should_exit_unplanned = !length(current_behaviors) - planned_behaviors[behavior] = TRUE - current_behaviors[behavior] = TRUE - - arguments.Cut(1, 2) - if(length(arguments)) - behavior_args[behavior_type] = arguments - else - behavior_args -= behavior_type + cancel_current_plan() - if(!(behavior.behavior_flags & AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION)) //this one blocks planning! - able_to_plan = FALSE - if(should_exit_unplanned) - exit_unplanned_mode() - SEND_SIGNAL(src, AI_CONTROLLER_BEHAVIOR_QUEUED(behavior_type), arguments) - -/datum/ai_controller/proc/check_able_to_plan() - for(var/datum/ai_behavior/current_behavior as anything in current_behaviors) - if(!(current_behavior.behavior_flags & AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION)) //We have a behavior that blocks planning - return FALSE - return TRUE - -/datum/ai_controller/proc/dequeue_behavior(datum/ai_behavior/behavior) - current_behaviors -= behavior - able_to_plan = check_able_to_plan() - if(!length(current_behaviors)) - enter_unplanned_mode() - -/datum/ai_controller/proc/exit_unplanned_mode() - remove_from_unplanned_controllers() - start_ai_processing() - -/datum/ai_controller/proc/enter_unplanned_mode() - add_to_unplanned_controllers() - stop_previous_processing() - -/datum/ai_controller/proc/ProcessBehavior(seconds_per_tick, datum/ai_behavior/behavior) - var/list/arguments = list(seconds_per_tick, src) - var/list/stored_arguments = behavior_args[behavior.type] - if(stored_arguments) - arguments += stored_arguments - - var/process_flags = behavior.perform(arglist(arguments)) - if(process_flags & AI_BEHAVIOR_DELAY) - behavior_cooldowns[behavior] = world.time + behavior.get_cooldown(src) - if(process_flags & AI_BEHAVIOR_FAILED) - arguments[1] = src - arguments[2] = FALSE - behavior.finish_action(arglist(arguments)) - else if (process_flags & AI_BEHAVIOR_SUCCEEDED) - arguments[1] = src - arguments[2] = TRUE - behavior.finish_action(arglist(arguments)) - -/datum/ai_controller/proc/CancelActions() - if(!length(current_behaviors)) - return - for(var/datum/ai_behavior/current_behavior as anything in current_behaviors) - fail_behavior(current_behavior) - EVLOG_TEXT(src, EVLOG_CATEGORY_AI_DECISIONMAKING, "Actions were cancelled!") - -/datum/ai_controller/proc/fail_behavior(datum/ai_behavior/current_behavior) - var/list/arguments = list(src, FALSE) - var/list/stored_arguments = behavior_args[current_behavior.type] - if(stored_arguments) - arguments += stored_arguments - current_behavior.finish_action(arglist(arguments)) +/datum/ai_controller/proc/cancel_current_plan() + active_execution_index = 0 + cancelled_during_tick = TRUE + reset_bt_tick_states() /// Turn the controller on or off based on if you're alive, we only register to this if the flag is present so don't need to check again /datum/ai_controller/proc/on_stat_changed(mob/living/source, new_stat) @@ -600,14 +674,14 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/on_sentience_lost() SIGNAL_HANDLER UnregisterSignal(pawn, COMSIG_MOB_LOGOUT) - set_ai_status(AI_STATUS_IDLE) //Can't do anything while player is connected + reset_ai_status() //resume AI control now that the client is gone RegisterSignal(pawn, COMSIG_MOB_LOGIN, PROC_REF(on_sentience_gained)) // Turn the controller off if the pawn has been qdeleted -/datum/ai_controller/proc/on_pawn_qdeleted() +/datum/ai_controller/proc/on_pawn_qdeleted(datum/source) SIGNAL_HANDLER + sig_remove_from_blackboard(source) set_ai_status(AI_STATUS_OFF) - set_movement_target(type, null) if(ai_movement.moving_controllers[src]) ai_movement.stop_moving_towards(src) @@ -618,25 +692,19 @@ multiple modular subtrees with behaviors var/mob/living/living_pawn = pawn return living_pawn.get_access() -///Returns the minimum required distance to preform one of our current behaviors. Honestly this should just be cached or something but fuck you -/datum/ai_controller/proc/get_minimum_distance() - var/minimum_distance = max_target_distance - // right now I'm just taking the shortest minimum distance of our current behaviors, at some point in the future - // we should let whatever sets the current_movement_target also set the min distance and max path length - // (or at least cache it on the controller) - for(var/datum/ai_behavior/iter_behavior as anything in current_behaviors) - if(iter_behavior.required_distance < minimum_distance) - minimum_distance = iter_behavior.required_distance - return minimum_distance - -/datum/ai_controller/proc/planning_failed() - on_failed_planning_timeout = TRUE - set_ai_status(get_expected_ai_status()) - addtimer(CALLBACK(src, PROC_REF(resume_planning)), AI_FAILED_PLANNING_COOLDOWN) +/// Returns TRUE if the pawn can path to the target. minimum_distance is how close the path must get (0 = onto/adjacent to the target's turf); searches pass it from their own acquire_target leaf. +/datum/ai_controller/proc/can_reach_target(atom/target, distance = 10, minimum_distance = 0) + if(!isdatum(target)) //we dont need to check if its not a datum! + return TRUE + if(get_turf(pawn) == get_turf(target)) + return TRUE + var/list/path = get_path_to(pawn, target, simulated_only = !HAS_TRAIT(pawn, TRAIT_SPACEWALK), mintargetdist = minimum_distance, max_distance = distance, access = get_access()) + return (!!length(path)) -/datum/ai_controller/proc/resume_planning() - on_failed_planning_timeout = FALSE - set_ai_status(get_expected_ai_status()) + +/// Called when a target was found but couldn't be reached. Base no-op; override to record the target (e.g. add it to an ignore list). +/datum/ai_controller/proc/note_unreachable_target(atom/target) + return /// Returns true if we have a blackboard key with the provided key and it is not qdeleting /datum/ai_controller/proc/blackboard_key_exists(key) @@ -697,8 +765,9 @@ multiple modular subtrees with behaviors * * * key - A blackboard key * * thing - a value to set the blackboard key to. + * * track_datum - whether we should track this ref for deletion, this should always be TRUE unless you really know wtf you're doing */ -/datum/ai_controller/proc/set_blackboard_key(key, thing) +/datum/ai_controller/proc/set_blackboard_key(key, thing, track_datum = TRUE) // Assume it is an error when trying to set a value overtop a list if(islist(blackboard[key])) CRASH("set_blackboard_key attempting to set a blackboard value to key [key] when it's a list!") @@ -710,7 +779,8 @@ multiple modular subtrees with behaviors if(!isnull(blackboard[key])) clear_blackboard_key(key) - TRACK_AI_DATUM_TARGET(thing, key) + if(track_datum) + TRACK_AI_DATUM_TARGET(thing, key) blackboard[key] = thing post_blackboard_key_set(key) @@ -828,6 +898,7 @@ multiple modular subtrees with behaviors LAZYINITLIST(blackboard[key]) TRACK_AI_DATUM_TARGET(thing, key) blackboard[key] |= thing + post_blackboard_key_set(key) /** * Adds the value to the inner list at key with the inner key set to "thing" @@ -875,7 +946,7 @@ multiple modular subtrees with behaviors blackboard[key] = null if(isnull(pawn)) return - SEND_SIGNAL(pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)) + SEND_SIGNAL(pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(key), key) /** * Remove the passed thing from the associated blackboard key @@ -983,19 +1054,31 @@ multiple modular subtrees with behaviors . = ..() UnregisterSignal(src, COMSIG_EVLOG_EVENT_ADDED) + /// Called whenever an event is logged for this controller. Attaches a snapshot of current behaviors and blackboard state to the event via track_info. /datum/ai_controller/proc/on_evlog_event_added(datum/source, datum/event_logger_track/track, list/event_data) SIGNAL_HANDLER var/list/track_info = list() - var/list/current_behavior_info = list() - for(var/datum/ai_behavior/behavior in planned_behaviors) - if(behavior in current_behaviors) //Not really ideal; we should find a better way to do this. - current_behavior_info += "ACTIVE: [span_bold("[behavior.type]")]" - else - current_behavior_info += "[behavior.type]" - EVLOG_TRACK_INFO_ENTRY(track_info, "Behaviors", "Current Behavior", jointext(current_behavior_info, "\n")) + // Build full tree state view showing all nodes with status markers + var/list/tree_lines = list() + for(var/datum/bt_node/root_node as anything in behavior_nodes) + root_node.append_full_tree_state(tree_lines, "") + EVLOG_TRACK_INFO_ENTRY(track_info, "Behaviors", "Full Tree State", length(tree_lines) ? jointext(tree_lines, "\n") : "(none)") + + // Add execution context section + var/active_node_label = "(none)" + if(active_execution_index) + for(var/datum/bt_node/root_node as anything in behavior_nodes) + var/datum/bt_node/found = root_node.find_by_index(active_execution_index) + if(found) + active_node_label = found.label + break + EVLOG_TRACK_INFO_ENTRY(track_info, "Execution Context", "Active Execution Index", "[active_execution_index] ([active_node_label])") + EVLOG_TRACK_INFO_ENTRY(track_info, "Execution Context", "AI Status", ai_status) + EVLOG_TRACK_INFO_ENTRY(track_info, "Execution Context", "Able to Run", able_to_run ? "TRUE" : "FALSE") + // Blackboard snapshot for(var/blackboard_key_name, blackboard_value in blackboard) var/value_string if(isatom(blackboard_value)) diff --git a/code/datums/ai/_ai_planning_subtree.dm b/code/datums/ai/_ai_planning_subtree.dm deleted file mode 100644 index b3f0ae9fb533..000000000000 --- a/code/datums/ai/_ai_planning_subtree.dm +++ /dev/null @@ -1,10 +0,0 @@ -///A subtree is attached to a controller and is occasionally called by /ai_controller/SelectBehaviors(), this mainly exists to act as a way to subtype and modify SelectBehaviors() without needing to subtype the ai controller itself -/datum/ai_planning_subtree - /// A list of typepaths of "operational datums" (elements/components) we absolutely NEED to run. Checked in unit tests, as well as be a nice reminder to developers that such a thing might be needed. - /// Note that in the Attach/Inititalize/New (or any future equivalent for these procs), you will need to add the trait TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM to the mob in question - /// in order for unit tests to succeed. This will break obviously enough if you don't do this and declare the required datum here however. - var/list/operational_datums = null - -///Determines what behaviors should the controller try processing; if this returns SUBTREE_RETURN_FINISH_PLANNING then the controller won't go through the other subtrees should multiple exist in controller.planning_subtrees -/datum/ai_planning_subtree/proc/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - return diff --git a/code/datums/ai/_item_behaviors.dm b/code/datums/ai/_item_behaviors.dm index c9f3441fdd68..b07ccb4f5f3e 100644 --- a/code/datums/ai/_item_behaviors.dm +++ b/code/datums/ai/_item_behaviors.dm @@ -1,60 +1,31 @@ -///This behavior is for obj/items, it is used to free themselves out of the hands of whoever is holding them -/datum/ai_behavior/item_escape_grasp - -/datum/ai_behavior/item_escape_grasp/perform(seconds_per_tick, datum/ai_controller/controller) - var/obj/item/item_pawn = controller.pawn - var/mob/item_holder = item_pawn.loc - if(!istype(item_holder)) - //We're no longer being held. abort abort!! - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - item_pawn.visible_message(span_warning("[item_pawn] slips out of the hands of [item_holder]!")) - item_holder.dropItemToGround(item_pawn, TRUE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - -///This behavior is for obj/items, it is used to move closer to a target and throw themselves towards them. -/datum/ai_behavior/item_move_close_and_attack - required_distance = 3 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - action_cooldown = 20 - ///Sound to use - var/attack_sound - ///Max attemps to make - var/max_attempts = 3 - -/datum/ai_behavior/item_move_close_and_attack/setup(datum/ai_controller/controller, target_key, throw_count_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if (isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/item_move_close_and_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, throw_count_key) +///yeet yourself at a thing +/datum/bt_node/ai_behavior/throw_attack + /// Sound played on each throw. + var/attack_sound = 'sound/items/haunted/ghostitemattack.ogg' + /// Maximum throws before the attack is exhausted. + var/max_attempts = 4 + /// Blackboard key holding the throw target. + var/target_key + /// Blackboard key tracking how many throws have happened. + var/throw_count_key + +/datum/bt_node/ai_behavior/throw_attack/perform(seconds_per_tick, datum/ai_controller/controller) var/obj/item/item_pawn = controller.pawn var/atom/throw_target = controller.blackboard[target_key] - + if(QDELETED(throw_target)) + controller.clear_blackboard_key(target_key) + controller.set_blackboard_key(throw_count_key, 0) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED item_pawn.visible_message(span_warning("[item_pawn] hurls towards [throw_target]!")) - item_pawn.throw_at(throw_target, rand(4,5), 9) + item_pawn.throw_at(throw_target, rand(4, 5), 9) playsound(item_pawn.loc, attack_sound, 100, TRUE) controller.add_blackboard_key(throw_count_key, 1) if(controller.blackboard[throw_count_key] >= max_attempts) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/item_move_close_and_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, throw_count_key) - . = ..() - reset_blackboard(controller, succeeded, target_key, throw_count_key) + return on_throws_exhausted(controller, throw_target, target_key, throw_count_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/item_move_close_and_attack/proc/reset_blackboard(datum/ai_controller/controller, succeeded, target_key, throw_count_key) +/// Clears target and resets throw count. Override to add extra on-exhaust logic. +/datum/bt_node/ai_behavior/throw_attack/proc/on_throws_exhausted(datum/ai_controller/controller, atom/throw_target, target_key, throw_count_key) controller.clear_blackboard_key(target_key) controller.set_blackboard_key(throw_count_key, 0) - -/datum/ai_behavior/item_move_close_and_attack/ghostly - attack_sound = 'sound/items/haunted/ghostitemattack.ogg' - max_attempts = 4 - -/datum/ai_behavior/item_move_close_and_attack/ghostly/haunted - -/datum/ai_behavior/item_move_close_and_attack/ghostly/haunted/finish_action(datum/ai_controller/controller, succeeded, target_key, throw_count_key) - controller.add_blackboard_key_assoc(BB_TO_HAUNT_LIST, controller.blackboard[target_key], -1) - return ..() + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED diff --git a/code/datums/ai/babies/babies_behaviors.dm b/code/datums/ai/babies/babies_behaviors.dm index c12f4acc6fac..497d72f8fa42 100644 --- a/code/datums/ai/babies/babies_behaviors.dm +++ b/code/datums/ai/babies/babies_behaviors.dm @@ -1,23 +1,30 @@ #define FIND_PARTNER_COOLDOWN 1 MINUTES -/** - * Find a compatible, living partner, if we're also alone. - */ -/datum/ai_behavior/find_partner - action_cooldown = 5 SECONDS - /// Range to look. +/// Find someone to erp with +/datum/bt_node/ai_behavior/find_partner + var/target_key + var/partner_types_key + var/child_types_key + time_between_perform = 5 SECONDS var/range = 7 - /// Maximum number of nearby pop var/max_nearby_pop = 3 -/datum/ai_behavior/find_partner/perform(seconds_per_tick, datum/ai_controller/controller, target_key, partner_types_key, child_types_key) - var/maximum_pop = controller.blackboard[BB_MAX_CHILDREN] || max_nearby_pop +/datum/bt_node/ai_behavior/find_partner/setup(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(living_pawn.gender == FEMALE) + return FALSE + if(!controller.blackboard[partner_types_key] || !controller.blackboard[child_types_key]) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/find_partner/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/pawn_mob = controller.pawn - var/list/similar_species_types = controller.blackboard[partner_types_key] + controller.blackboard[child_types_key] var/mob/living/living_pawn = controller.pawn + var/maximum_pop = controller.blackboard[BB_MAX_CHILDREN] || max_nearby_pop + var/list/similar_species_types = controller.blackboard[partner_types_key] + controller.blackboard[child_types_key] var/list/possible_partners = list() - var/nearby_pop = 0 + for(var/mob/living/other in oview(range, pawn_mob)) if(!pawn_mob.faction_check_atom(other)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED @@ -32,10 +39,10 @@ if(!HAS_TRAIT(other, TRAIT_MOB_BREEDER) || other.ckey) continue - if(other.stat != CONSCIOUS) //Check if it's conscious FIRST. + if(other.stat != CONSCIOUS) continue - if(other.gender != living_pawn.gender && !(other.flags_1 & HOLOGRAM_1)) //Better safe than sorry ;_; + if(other.gender != living_pawn.gender && !(other.flags_1 & HOLOGRAM_1)) possible_partners += other if(!length(possible_partners)) @@ -44,28 +51,25 @@ controller.set_blackboard_key(target_key, pick(possible_partners)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/** - * Reproduce. - */ -/datum/ai_behavior/make_babies - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH +/// I'm not saying what this behavior does :flushed: +/datum/bt_node/ai_behavior/make_babies + var/target_key + var/child_types_key -/datum/ai_behavior/make_babies/setup(datum/ai_controller/controller, target_key, child_types_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(!target) - return FALSE - set_movement_target(controller, target) +/datum/bt_node/ai_behavior/make_babies/setup(datum/ai_controller/controller) + var/mob/target = controller.blackboard[target_key] + return !QDELETED(target) && target.stat == CONSCIOUS -/datum/ai_behavior/make_babies/perform(seconds_per_tick, datum/ai_controller/controller, target_key, child_types_key) +/datum/bt_node/ai_behavior/make_babies/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/target = controller.blackboard[target_key] if(QDELETED(target) || target.stat != CONSCIOUS) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.ai_interact(target = target, combat_mode = FALSE) + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, FALSE) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/make_babies/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/make_babies/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) + #undef FIND_PARTNER_COOLDOWN diff --git a/code/datums/ai/babies/babies_subtrees.dm b/code/datums/ai/babies/babies_subtrees.dm deleted file mode 100644 index 7cadd1e8c551..000000000000 --- a/code/datums/ai/babies/babies_subtrees.dm +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Reproduce with a similar mob. - */ -/datum/ai_planning_subtree/make_babies - operational_datums = list(/datum/component/breed) - /// Chance to make babies - var/chance = 5 - /// Make babies behavior we will use - var/datum/ai_behavior/reproduce_behavior = /datum/ai_behavior/make_babies - /// Find partner behavior we will use - var/datum/ai_behavior/partner_behavior = /datum/ai_behavior/find_partner - -/datum/ai_planning_subtree/make_babies/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - - if(!SPT_PROB(chance, seconds_per_tick) || controller.blackboard[BB_PARTNER_SEARCH_TIMEOUT] >= world.time) - return - - if(controller.blackboard_key_exists(BB_BABIES_TARGET)) - controller.queue_behavior(reproduce_behavior, BB_BABIES_TARGET, BB_BABIES_CHILD_TYPES) - return SUBTREE_RETURN_FINISH_PLANNING - - if(controller.pawn.gender == FEMALE || !controller.blackboard[BB_BREED_READY]) - return - - if(!controller.blackboard[BB_BABIES_PARTNER_TYPES] || !controller.blackboard[BB_BABIES_CHILD_TYPES]) - return - - // Find target - controller.queue_behavior(partner_behavior, BB_BABIES_TARGET, BB_BABIES_PARTNER_TYPES, BB_BABIES_CHILD_TYPES) diff --git a/code/datums/ai/babies/make_babies.bt.json b/code/datums/ai/babies/make_babies.bt.json new file mode 100644 index 000000000000..6051613a60d7 --- /dev/null +++ b/code/datums/ai/babies/make_babies.bt.json @@ -0,0 +1,31 @@ +{ + "dm_type": "/datum/bt_node/subtree/make_babies", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_BABIES_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BABIES_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/make_babies", + "vars": { + "target_key": "BB_BABIES_TARGET", + "child_types_key": "BB_BABIES_CHILD_TYPES" + } + } + ] + } +} diff --git a/code/datums/ai/bane/bane.bt.json b/code/datums/ai/bane/bane.bt.json new file mode 100644 index 000000000000..b0e92e321eba --- /dev/null +++ b/code/datums/ai/bane/bane.bt.json @@ -0,0 +1,43 @@ +{ + "dm_type": "/datum/ai_controller/bane", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BANE_BATMAN" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BANE_BATMAN", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_spine/bane", + "vars": { + "target_key": "BB_BANE_BATMAN" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_BANE_BATMAN", + "target_source": "/datum/target_source/oview_single_type/living_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_mob", + "vision_range": 7 + } + } + ] +} diff --git a/code/datums/ai/bane/bane_behaviors.dm b/code/datums/ai/bane/bane_behaviors.dm deleted file mode 100644 index c47c455f7f9c..000000000000 --- a/code/datums/ai/bane/bane_behaviors.dm +++ /dev/null @@ -1,9 +0,0 @@ - -/datum/ai_behavior/break_spine/bane/finish_action(datum/ai_controller/controller, succeeded, target_key) - if(succeeded) - var/list/bane_quotes = strings("bane.json", "bane") - var/mob/living/bane = controller.pawn - if(QDELETED(bane)) // pawn can be null at this point - return ..() - bane.say(pick(bane_quotes)) - return ..() diff --git a/code/datums/ai/bane/bane_controller.dm b/code/datums/ai/bane/bane_controller.dm index 580e80440a41..3144d0c21ced 100644 --- a/code/datums/ai/bane/bane_controller.dm +++ b/code/datums/ai/bane/bane_controller.dm @@ -4,8 +4,8 @@ And the only victory you achieved was a lie. Now you understand Gotham is beyond */ /datum/ai_controller/bane movement_delay = 0.4 SECONDS + behavior_tree_json = "code/datums/ai/bane/bane.bt.json" blackboard = list(BB_BANE_BATMAN = null) - planning_subtrees = list(/datum/ai_planning_subtree/bane_hunting) /datum/ai_controller/bane/TryPossessPawn(atom/new_pawn) if(!isliving(new_pawn)) diff --git a/code/datums/ai/bane/bane_subtrees.dm b/code/datums/ai/bane/bane_subtrees.dm deleted file mode 100644 index e7f651737bfc..000000000000 --- a/code/datums/ai/bane/bane_subtrees.dm +++ /dev/null @@ -1,16 +0,0 @@ -///The bat is broken! -/datum/ai_planning_subtree/bane_hunting/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/batman = controller.blackboard[BB_BANE_BATMAN] - if(QDELETED(batman)) - for(var/mob/living/possibly_the_dark_knight in oview(7, controller.pawn)) - if(IS_DEAD_OR_INCAP(possibly_the_dark_knight)) //I HAVE BROKEN THE BAT - continue - controller.set_blackboard_key(BB_BANE_BATMAN, possibly_the_dark_knight) - batman = possibly_the_dark_knight - break - - if(isnull(batman)) - return - - controller.queue_behavior(/datum/ai_behavior/break_spine/bane, BB_BANE_BATMAN) - return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/basic_mobs/base_basic_controller.dm b/code/datums/ai/basic_mobs/base_basic_controller.dm index 9537869bd051..136c135eb1b6 100644 --- a/code/datums/ai/basic_mobs/base_basic_controller.dm +++ b/code/datums/ai/basic_mobs/base_basic_controller.dm @@ -1,5 +1,6 @@ /datum/ai_controller/basic_controller movement_delay = 0.4 SECONDS + behavior_tree_json = ABSTRACT_AI_CLASS /datum/ai_controller/basic_controller/TryPossessPawn(atom/new_pawn) if(!isliving(new_pawn)) @@ -62,9 +63,9 @@ /datum/ai_controller/proc/on_tamed(datum/source, mob/living/new_friend) SIGNAL_HANDLER forgive_target(new_friend) - clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + clear_blackboard_key(BB_CURRENT_TARGET) clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST) //we have just been tamed by a new party, clean slate for everyone! - CancelActions() + cancel_current_plan() RegisterSignal(new_friend, COMSIG_LIVING_MADE_NEW_FRIEND, PROC_REF(on_master_tame)) /datum/ai_controller/proc/on_untamed(datum/source, mob/living/old_friend) @@ -77,7 +78,7 @@ /datum/ai_controller/proc/forgive_target(atom/target) var/static/list/keys_to_check = list( - BB_BASIC_MOB_CURRENT_TARGET, + BB_CURRENT_TARGET, BB_CURRENT_PET_TARGET, ) for(var/key in keys_to_check) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm index 5b41ae5254d2..2be8c0b21ee1 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm @@ -1,144 +1,121 @@ /// Amount of time to wait before executing attack if not specified #define DEFAULT_ATTACK_DELAY (0.4 SECONDS) -/datum/ai_behavior/basic_melee_attack - action_cooldown = 0.2 SECONDS // We gotta check unfortunately often because we're in a race condition with nextmove - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - ///do we finish this action after hitting once? - var/terminate_after_action = FALSE - ///do we have any alternate movement behavior? - var/movement_behavior - -/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) +/// Perform a melee attack on the target specified. +/datum/bt_node/ai_behavior/basic_melee_attack + var/target_key + var/targeting_strategy = BB_TARGETING_STRATEGY + var/hiding_location_key + +/datum/bt_node/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller) . = ..() - if(!controller.blackboard[targeting_strategy_key]) + + + if(!ispath(targeting_strategy)) + targeting_strategy = controller.blackboard[targeting_strategy] + + if(!targeting_strategy) CRASH("No targeting strategy was supplied in the blackboard for [controller.pawn]") - //Hiding location is priority var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] if(QDELETED(target)) return FALSE - set_movement_target(controller, target, movement_behavior) - -/datum/ai_behavior/basic_melee_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/basic_melee_attack/perform(seconds_per_tick, datum/ai_controller/controller) var/atom/target = controller.blackboard[target_key] if (isnull(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if (!can_attack(controller, target)) - return AI_BEHAVIOR_INSTANT - - if (isliving(controller.pawn)) - var/mob/living/pawn = controller.pawn - if (world.time < pawn.next_move) - return AI_BEHAVIOR_INSTANT - - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targeting_strategy.can_attack(controller.pawn, target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/hiding_target = targeting_strategy.find_hidden_mobs(controller.pawn, target) //If this is valid, theyre hidden in something! - - controller.set_blackboard_key(hiding_location_key, hiding_target) - - var/atom/final_target = hiding_target || target - controller.ai_interact(target = final_target, combat_mode = TRUE) - if(terminate_after_action) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/basic_melee_attack/proc/can_attack(datum/ai_controller/controller, atom/target) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED if (!target.IsReachableBy(controller.pawn)) controller.clear_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER) - return FALSE + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/can_attack_time = controller.blackboard[BB_BASIC_MOB_MELEE_COOLDOWN_TIMER] if (isnull(can_attack_time)) var/blackboard_delay = controller.blackboard[BB_BASIC_MOB_MELEE_DELAY] var/attack_delay = isnull(blackboard_delay) ? DEFAULT_ATTACK_DELAY : blackboard_delay controller.set_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER, world.time + attack_delay) - return FALSE - + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED if (can_attack_time > world.time) - return FALSE + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - return TRUE + if (isliving(controller.pawn)) + var/mob/living/pawn = controller.pawn + if (world.time < pawn.next_move) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED -/datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - controller.clear_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER) - if(movement_behavior) - controller.change_ai_movement_type(initial(controller.ai_movement)) - if(!succeeded) + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(targeting_strategy) + if(!strategy.is_valid_target(controller.pawn, target, controller = controller)) controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED -/datum/ai_behavior/basic_melee_attack/interact_once - terminate_after_action = TRUE + var/hiding_target = strategy.find_hidden_mobs(controller.pawn, target) //If this is valid, theyre hidden in something! -/datum/ai_behavior/basic_melee_attack/interact_once/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) + controller.set_blackboard_key(hiding_location_key, hiding_target) + + var/atom/final_target = hiding_target || target + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), final_target, TRUE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/// Single-hit variant: terminates after one successful attack and always clears the target key. +/datum/bt_node/ai_behavior/basic_melee_attack/interact_once + +/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) -/datum/ai_behavior/basic_ranged_attack - action_cooldown = 0.6 SECONDS - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - required_distance = 3 - /// range we will try chasing the target before giving up - var/chase_range = 9 - ///do we care about avoiding friendly fire? +//Basic ranged attack behavior +/datum/bt_node/ai_behavior/basic_ranged_attack + var/target_key + var/targeting_strategy = BB_TARGETING_STRATEGY + var/hiding_location_key + time_between_perform = 0.6 SECONDS + /// Max range at which we can fire. Make sure your movement actually gets you this close please + var/max_range = 9 + /// Avoid shooting through friendlies. var/avoid_friendly_fire = FALSE -/datum/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller) . = ..() if(HAS_TRAIT(controller.pawn, TRAIT_HANDS_BLOCKED)) return FALSE var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] if(QDELETED(target)) return FALSE - set_movement_target(controller, target) + return TRUE -/datum/ai_behavior/basic_ranged_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/basic_ranged_attack/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/basic_mob = controller.pawn - //targeting strategy will kill the action if not real anymore var/atom/target = controller.blackboard[target_key] - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targeting_strategy.can_attack(basic_mob, target, chase_range)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!ispath(targeting_strategy)) + targeting_strategy = controller.blackboard[targeting_strategy] + + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(targeting_strategy) - var/atom/hiding_target = targeting_strategy.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! + var/atom/hiding_target = strategy.find_hidden_mobs(basic_mob, target) var/atom/final_target = hiding_target ? hiding_target : target + controller.set_blackboard_key(hiding_location_key, hiding_target) - if(!can_see(basic_mob, final_target, required_distance)) - return AI_BEHAVIOR_INSTANT + if(!can_see(basic_mob, final_target, max_range)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - if(avoid_friendly_fire && check_friendly_in_path(basic_mob, target, targeting_strategy)) + if(avoid_friendly_fire && check_friendly_in_path(basic_mob, target, strategy)) adjust_position(basic_mob, target) - return AI_BEHAVIOR_DELAY + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(hiding_location_key, hiding_target) basic_mob.RangedAttack(final_target) - return AI_BEHAVIOR_DELAY //only start the cooldown when the shot is shot + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/basic_ranged_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - if(!succeeded) - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/basic_ranged_attack/proc/check_friendly_in_path(mob/living/source, atom/target, datum/targeting_strategy/targeting_strategy) +/datum/bt_node/ai_behavior/basic_ranged_attack/proc/check_friendly_in_path(mob/living/source, atom/target, datum/targeting_strategy/targeting_strategy) var/list/turfs_list = calculate_trajectory(source, target) for(var/turf/possible_turf as anything in turfs_list) - for(var/mob/living/potential_friend in possible_turf) - if(!targeting_strategy.can_attack(source, potential_friend)) + if(!targeting_strategy.is_valid_target(source, potential_friend)) return TRUE - return FALSE -/datum/ai_behavior/basic_ranged_attack/proc/adjust_position(mob/living/living_pawn, atom/target) +/datum/bt_node/ai_behavior/basic_ranged_attack/proc/adjust_position(mob/living/living_pawn, atom/target) var/turf/our_turf = get_turf(living_pawn) var/list/possible_turfs = list() - for(var/direction in GLOB.alldirs) var/turf/target_turf = get_step(our_turf, direction) if(isnull(target_turf)) @@ -146,13 +123,12 @@ if(target_turf.is_blocked_turf() || get_dist(target_turf, target) > get_dist(living_pawn, target)) continue possible_turfs += target_turf - if(!length(possible_turfs)) return var/turf/picked_turf = get_closest_atom(/turf, possible_turfs, target) step(living_pawn, get_dir(living_pawn, picked_turf)) -/datum/ai_behavior/basic_ranged_attack/proc/calculate_trajectory(mob/living/source , atom/target) +/datum/bt_node/ai_behavior/basic_ranged_attack/proc/calculate_trajectory(mob/living/source, atom/target) var/list/turf_list = get_line(source, target) var/list_length = length(turf_list) - 1 for(var/i in 1 to list_length) @@ -161,17 +137,14 @@ var/direction_to_turf = get_dir(current_turf, next_turf) if(!ISDIAGONALDIR(direction_to_turf)) continue - for(var/cardinal_direction in GLOB.cardinals) if(cardinal_direction & direction_to_turf) turf_list += get_step(current_turf, cardinal_direction) - turf_list -= get_turf(source) turf_list -= get_turf(target) - return turf_list -/datum/ai_behavior/basic_ranged_attack/avoid_friendly_fire +/datum/bt_node/ai_behavior/basic_ranged_attack/avoid_friendly_fire avoid_friendly_fire = TRUE #undef DEFAULT_ATTACK_DELAY diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm index 865b028e156c..cd50aa734d73 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm @@ -1,19 +1,23 @@ -///behavior to befriend any targets -/datum/ai_behavior/befriend_target +/datum/bt_node/ai_behavior/befriend_target + var/target_key + var/befriend_message + var/long_range_friendship = FALSE + var/forget_target = TRUE -/datum/ai_behavior/befriend_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, befriend_message) +/datum/bt_node/ai_behavior/befriend_target/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn var/mob/living/living_target = controller.blackboard[target_key] if(QDELETED(living_target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - + if(!long_range_friendship && get_dist(living_pawn, living_target) > 1) + return AI_BEHAVIOR_INSTANT living_pawn.befriend(living_target) var/befriend_text = controller.blackboard[befriend_message] if(befriend_text) to_chat(living_target, span_nicegreen("[living_pawn] [befriend_text]")) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/befriend_target/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/befriend_target/finish_action(datum/ai_controller/controller, succeeded) . = ..() - controller.clear_blackboard_key(target_key) + if(forget_target) + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/call_reinforcements.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/call_reinforcements.dm new file mode 100644 index 000000000000..4888f3d9054e --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/call_reinforcements.dm @@ -0,0 +1,50 @@ +/// Emotes a reinforcement call and alerts nearby faction members, adding the current target to their retaliate lists. +/// Returns FAILURE when there is no valid target or the target is a friend. +/datum/bt_node/ai_behavior/call_reinforcements + /// How far to look for reinforcements + var/reinforcements_range = 15 + ///Target to call reinforcements on + var/target_key = BB_CURRENT_TARGET + +/datum/bt_node/ai_behavior/call_reinforcements/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/target = controller.blackboard[target_key] + if(!istype(target, /mob)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/mob/pawn_mob = controller.pawn + var/list/friends = controller.blackboard[BB_FRIENDS_LIST] + if(friends && (target in friends)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/say_text = controller.blackboard[BB_REINFORCEMENTS_SAY] + if(!isnull(say_text)) + pawn_mob.say(say_text, forced = "AI Controller") + else + var/emote_text = controller.blackboard[BB_REINFORCEMENTS_EMOTE] + if(!isnull(emote_text)) + pawn_mob.manual_emote(emote_text) + + for(var/mob/other_mob in oview(reinforcements_range, pawn_mob)) + if(!pawn_mob.faction_check_atom(other_mob) || isnull(other_mob.ai_controller)) + continue + other_mob.ai_controller.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, target, world.time) + other_mob.ai_controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENT_TARGET, pawn_mob) + + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Mining/swarm variant: boosts priority rather than forcing retaliation, shorter range and faster cooldown. +/datum/bt_node/ai_behavior/call_reinforcements/mining + reinforcements_range = 7 + +/datum/bt_node/ai_behavior/call_reinforcements/mining/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/target = controller.blackboard[target_key] + if(!istype(target, /mob)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/mob/pawn_mob = controller.pawn + for(var/mob/other_mob in oview(reinforcements_range, pawn_mob)) + if(!pawn_mob.faction_check_atom(other_mob) || isnull(other_mob.ai_controller)) + continue + var/list/existing_requests = other_mob.ai_controller.blackboard[BB_MINING_MOB_REINFORCEMENTS_REQUESTS] + if(!existing_requests || !existing_requests[target]) + other_mob.ai_controller.set_blackboard_key_assoc_lazylist(BB_MINING_MOB_REINFORCEMENTS_REQUESTS, target, list()) + other_mob.ai_controller.add_blackboard_key_assoc(BB_MINING_MOB_REINFORCEMENTS_REQUESTS, target, world.time) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/clear_key.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/clear_key.dm deleted file mode 100644 index ae0711789faa..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/clear_key.dm +++ /dev/null @@ -1,14 +0,0 @@ -/// Clears a blackboard key (or keys), simply if you want to do this after an action without making a subtype -/datum/ai_behavior/clear_key - -/datum/ai_behavior/clear_key/perform(seconds_per_tick, datum/ai_controller/controller, list/to_clear) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/clear_key/finish_action(datum/ai_controller/controller, succeeded, list/to_clear) - . = ..() - if (!to_clear) - return - if (!islist(to_clear)) - to_clear = list(to_clear) - for (var/key in to_clear) - controller.clear_blackboard_key(key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/climb_tree.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/climb_tree.dm deleted file mode 100644 index c30943caf50c..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/climb_tree.dm +++ /dev/null @@ -1,35 +0,0 @@ -/datum/ai_behavior/find_and_set/valid_tree - -/datum/ai_behavior/find_and_set/valid_tree/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/valid_trees = list() - for (var/obj/structure/flora/tree/tree_target in oview(search_range, controller.pawn)) - if(istype(tree_target, /obj/structure/flora/tree/dead)) //no died trees - continue - valid_trees += tree_target - - if(valid_trees.len) - return pick(valid_trees) - -/datum/ai_behavior/climb_tree - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/climb_tree/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - - set_movement_target(controller, target) - -/datum/ai_behavior/climb_tree/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/obj/structure/flora/target_tree = controller.blackboard[target_key] - var/mob/living/basic/living_pawn = controller.pawn - if(QDELETED(living_pawn)) // pawn can be null at this point - return - SEND_SIGNAL(living_pawn, COMSIG_LIVING_CLIMB_TREE, target_tree) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/climb_tree/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(succeeded) - controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.json b/code/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.json new file mode 100644 index 000000000000..54e4af4336a5 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.json @@ -0,0 +1,46 @@ +{ + "dm_type": "/datum/bt_node/subtree/consider_venting", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_in_vent", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/exit_vent" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_ENTRY_VENT_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_VENTING_COOLDOWN", + "cooldown_duration": "BB_VENTCRAWL_COOLDOWN" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ENTRY_VENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enter_vent" + } + ] + } + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm deleted file mode 100644 index 7960301d7044..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm +++ /dev/null @@ -1,28 +0,0 @@ -/datum/ai_behavior/emote_on_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - - -/datum/ai_behavior/emote_on_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/hunt_target = controller.blackboard[target_key] - if (isnull(hunt_target)) - return FALSE - set_movement_target(controller, hunt_target) - - -/datum/ai_behavior/emote_on_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, list/emote_list) - var/atom/target = controller.blackboard[target_key] - if(!length(emote_list) || isnull(target)) - return AI_BEHAVIOR_FAILED | AI_BEHAVIOR_DELAY - run_emote(controller.pawn, target, emote_list) - return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY - - -/datum/ai_behavior/emote_on_target/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(succeeded) - controller.clear_blackboard_key(target_key) - - -/datum/ai_behavior/emote_on_target/proc/run_emote(mob/living/living_pawn, atom/target, list/emote_list) - living_pawn.manual_emote("[pick(emote_list)] [target]") diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/find_flee_location.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_flee_location.dm new file mode 100644 index 000000000000..9efe9d6fc873 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_flee_location.dm @@ -0,0 +1,37 @@ +/// Finds the best adjacent turf to flee to away from a threat and stores it in a blackboard key. +/// Tries get_step_away first, then falls back to shuffled directions if blocked. +/// Returns INSTANT SUCCESS if a step was found, INSTANT FAILURE if completely cornered. +/datum/bt_node/ai_behavior/find_flee_location + var/target_key + var/hiding_location_key + var/destination_key + +/datum/bt_node/ai_behavior/find_flee_location/perform(seconds_per_tick, datum/ai_controller/controller) + var/run_distance = controller.blackboard[BB_BASIC_MOB_FLEE_DISTANCE] || DEFAULT_BASIC_FLEE_DISTANCE + var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] + if(QDELETED(target) || !can_see(controller.pawn, target, run_distance)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/turf/flee_turf = get_flee_step(controller, target) + if(!flee_turf) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(destination_key, flee_turf) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/find_flee_location/proc/get_flee_step(datum/ai_controller/controller, atom/target) + var/mob/living/pawn = controller.pawn + var/turf/pawn_turf = get_turf(pawn) + var/datum/can_pass_info/pass_info = new(pawn, controller.get_access()) + var/turf/next_step = get_step_away(pawn, target) + if(!isnull(next_step) && next_step != pawn_turf && !next_step.density && !pawn_turf.LinkBlockedWithAccess(next_step, pass_info)) + return next_step + var/list/all_dirs = GLOB.alldirs.Copy() + all_dirs -= get_dir(pawn, next_step) + all_dirs -= get_dir(pawn, target) + shuffle_inplace(all_dirs) + for(var/dir in all_dirs) + next_step = get_step(pawn, dir) + if(!isnull(next_step) && !next_step.density && !pawn_turf.LinkBlockedWithAccess(next_step, pass_info)) + return next_step + return null diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/find_parent.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_parent.dm index 31735f99231b..7e8a0a4d3913 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/find_parent.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_parent.dm @@ -1,35 +1,51 @@ -/datum/ai_behavior/find_mom - ///range to look for the mom +/// Looks around for a nearby adult of one of BB_FIND_MOM_TYPES (skipping BB_IGNORE_MOM_TYPES) and stores it. +/datum/bt_node/ai_behavior/find_mom + time_between_perform = 2 SECONDS + /// How far to look for our parent. var/look_range = 7 + /// Blackboard key holding the list of typepaths we accept as parents. + var/mom_types_key = BB_FIND_MOM_TYPES + /// Blackboard key holding typepaths to skip even if they match (e.g. other babies). + var/ignore_types_key = BB_IGNORE_MOM_TYPES + /// Blackboard key to store the found parent in. + var/found_mom_key = BB_FOUND_MOM -/datum/ai_behavior/find_mom/perform(seconds_per_tick, datum/ai_controller/controller, mom_key, ignore_mom_key, found_mom) +/datum/bt_node/ai_behavior/find_mom/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living_pawn = controller.pawn - var/list/all_moms = list() - var/list/mom_types = controller.blackboard[mom_key] - var/list/ignore_types = controller.blackboard[ignore_mom_key] - + var/list/mom_types = controller.blackboard[mom_types_key] if(!length(mom_types)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/list/ignore_types = controller.blackboard[ignore_types_key] + var/list/all_moms = list() for(var/mob/mother in oview(look_range, living_pawn)) - if (is_possible_mom(mother, mom_types, ignore_types)) + if(is_possible_mom(mother, mom_types, ignore_types)) all_moms += mother if(length(all_moms)) - controller.set_blackboard_key(found_mom, pick(all_moms)) + controller.set_blackboard_key(found_mom_key, pick(all_moms)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED -/datum/ai_behavior/find_mom/proc/is_possible_mom(mob/mother, list/mom_types, list/ignore_types) +/datum/bt_node/ai_behavior/find_mom/proc/is_possible_mom(mob/mother, list/mom_types, list/ignore_types) if(!is_type_in_list(mother, mom_types)) return FALSE - if(is_type_in_list(mother, ignore_types)) // so the not permanent baby and the permanent baby subtype dont followed each other + if(is_type_in_list(mother, ignore_types)) return FALSE return TRUE -/datum/ai_behavior/find_mom/raptor/is_possible_mom(mob/mother, list/mom_types, list/ignore_types) - . = ..() - if (!. || !istype(mother, /mob/living/basic/raptor)) - return FALSE - var/mob/living/basic/raptor/raptor = mother - return raptor.growth_stage == RAPTOR_ADULT +/// A baby emotes at its parent: crying if the parent is dead, dancing happily otherwise. +/datum/bt_node/ai_behavior/look_to_parent + /// Blackboard key holding the parent to react to. + var/parent_key = BB_FOUND_MOM + +/datum/bt_node/ai_behavior/look_to_parent/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/parent = controller.blackboard[parent_key] + if(QDELETED(parent)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/baby = controller.pawn + if(parent.stat == DEAD) + baby.manual_emote("cries for their parent!") + else + baby.manual_emote("dances around their parent!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/interact_with_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/interact_with_target.dm deleted file mode 100644 index e3b202c5e164..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/interact_with_target.dm +++ /dev/null @@ -1,29 +0,0 @@ -///behavior for general interactions with any targets -/datum/ai_behavior/interact_with_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - ///should we be clearing the target after the fact? - var/clear_target = TRUE - ///should our combat mode be off during interaction? - var/combat_mode = TRUE - -/datum/ai_behavior/interact_with_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/interact_with_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target) || !pre_interact(controller, target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.ai_interact(target, combat_mode) - return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY - -/datum/ai_behavior/interact_with_target/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(clear_target || !succeeded) - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/interact_with_target/proc/pre_interact(datum/ai_controller/controller, target) - return TRUE diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targeting.dm index 8a570375cba1..9a0d6d041ef2 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targeting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targeting.dm @@ -1,13 +1,6 @@ -/// Picks targets based on which one is closest to you, choice between targets at equal distance is arbitrary -/datum/ai_behavior/find_potential_targets/nearest +/// Pick nearest instead of any, we should probably move this into a datum of some kind in the future? +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest -/datum/ai_behavior/find_potential_targets/nearest/pick_final_target(datum/ai_controller/controller, list/filtered_targets) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest/pick_final_target(datum/ai_controller/controller, list/filtered_targets) var/turf/our_position = get_turf(controller.pawn) return get_closest_atom(/atom/, filtered_targets, our_position) - -/// As above but targets have been filtered from the 'retaliate' blackboard list -/datum/ai_behavior/target_from_retaliate_list/nearest - -/datum/ai_behavior/target_from_retaliate_list/nearest/pick_final_target(datum/ai_controller/controller, list/enemies_list) - var/turf/our_position = get_turf(controller.pawn) - return get_closest_atom(/atom/, enemies_list, our_position) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/pick_up_item.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/pick_up_item.dm deleted file mode 100644 index 1b65eaa507c9..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/pick_up_item.dm +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Simple behaviour for picking up an item we are already in range of. - * The blackboard storage key isn't very safe because it doesn't make sense to register signals in here. - * Use the AI held item component to manage this. - */ -/datum/ai_behavior/pick_up_item - -/datum/ai_behavior/pick_up_item/setup(datum/ai_controller/controller, target_key, storage_key) - . = ..() - var/obj/item/target = controller.blackboard[target_key] - return isitem(target) && isturf(target.loc) && !target.anchored - -/datum/ai_behavior/pick_up_item/perform(seconds_per_tick, datum/ai_controller/controller, target_key, storage_key) - var/obj/item/target = controller.blackboard[target_key] - if(QDELETED(target) || !isturf(target.loc)) // Someone picked it up or it got deleted - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(!controller.pawn.Adjacent(target)) // It teleported - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - pickup_item(controller, target, storage_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/pick_up_item/finish_action(datum/ai_controller/controller, success, target_key, storage_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/pick_up_item/proc/pickup_item(datum/ai_controller/controller, obj/item/target, storage_key) - var/atom/pawn = controller.pawn - drop_existing_item(controller, storage_key) - pawn.visible_message(span_notice("[pawn] picks up [target].")) - target.forceMove(pawn) - controller.set_blackboard_key(storage_key, target) - return TRUE - -/datum/ai_behavior/pick_up_item/proc/drop_existing_item(datum/ai_controller/controller, storage_key) - var/obj/item/carried_item = controller.blackboard[storage_key] - if(!carried_item) - return - controller.clear_blackboard_key(storage_key) - var/atom/pawn = controller.pawn - if(carried_item.loc != pawn) - return - pawn.visible_message(span_notice("[pawn] drops [carried_item].")) - carried_item.forceMove(get_turf(pawn)) - return TRUE diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/play_dead.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/play_dead.dm new file mode 100644 index 000000000000..49f27564b972 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/play_dead.dm @@ -0,0 +1,25 @@ +/// Plays dead until a per-tick probability check (default 10%) triggers revival. +/datum/bt_node/ai_behavior/play_dead + var/probability = 10 + +/datum/bt_node/ai_behavior/play_dead/setup(datum/ai_controller/controller) + var/mob/living/basic/pawn = controller.pawn + if(!istype(pawn) || pawn.stat) + return FALSE + INVOKE_ASYNC(pawn, TYPE_PROC_REF(/mob, emote), "deathgasp", intentional = FALSE) + ADD_TRAIT(pawn, TRAIT_FAKEDEATH, BASIC_MOB_DEATH_TRAIT) + pawn.look_dead() + +/datum/bt_node/ai_behavior/play_dead/perform(seconds_per_tick, datum/ai_controller/controller) + if(SPT_PROB(probability, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY + +/datum/bt_node/ai_behavior/play_dead/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + var/mob/living/basic/pawn = controller.pawn + if(QDELETED(pawn) || pawn.stat) + return + pawn.visible_message(span_notice("[pawn] miraculously springs back to life!")) + REMOVE_TRAIT(pawn, TRAIT_FAKEDEATH, BASIC_MOB_DEATH_TRAIT) + pawn.look_alive() diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/pull_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/pull_target.dm deleted file mode 100644 index 900b122dcb3f..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/pull_target.dm +++ /dev/null @@ -1,22 +0,0 @@ -/datum/ai_behavior/pull_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/pull_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/pull_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/movable/target = controller.blackboard[target_key] - if(QDELETED(target) || target.anchored || target.pulledby) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/our_mob = controller.pawn - our_mob.start_pulling(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/pull_target/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(!succeeded) - controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm deleted file mode 100644 index f23707e72b8c..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm +++ /dev/null @@ -1,76 +0,0 @@ -/// Move to a position further away from your current target -/datum/ai_behavior/run_away_from_target - required_distance = 0 - action_cooldown = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// How far do we try to run? Further makes for smoother running, but potentially weirder pathfinding - var/run_distance = DEFAULT_BASIC_FLEE_DISTANCE - /// Clear target if we finish the action unsuccessfully - var/clear_failed_targets = TRUE - -/datum/ai_behavior/run_away_from_target/setup(datum/ai_controller/controller, target_key, hiding_location_key) - var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - run_distance = controller.blackboard[BB_BASIC_MOB_FLEE_DISTANCE] || initial(run_distance) - if(!plot_path_away_from(controller, target)) - return FALSE - return ..() - -/datum/ai_behavior/run_away_from_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) - if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return AI_BEHAVIOR_DELAY - var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] - if (QDELETED(target) || !can_see(controller.pawn, target, run_distance)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if (get_dist(controller.pawn, controller.current_movement_target) > required_distance) - return AI_BEHAVIOR_DELAY // Still heading over - if (plot_path_away_from(controller, target)) - return AI_BEHAVIOR_DELAY - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/run_away_from_target/proc/plot_path_away_from(datum/ai_controller/controller, atom/target) - var/turf/target_destination = get_turf(controller.pawn) - var/static/list/offset_angles = list(45, 90, 135, 180, 225, 270) - for(var/angle in offset_angles) - var/turf/test_turf = get_furthest_turf(controller.pawn, angle, target) - if(isnull(test_turf)) - continue - var/distance_from_target = get_dist(target, test_turf) - if(distance_from_target <= get_dist(target, target_destination)) - continue - target_destination = test_turf - if(distance_from_target == run_distance) //we already got the max running distance - break - - if (target_destination == get_turf(controller.pawn)) - return FALSE - set_movement_target(controller, target_destination) - return TRUE - -/datum/ai_behavior/run_away_from_target/proc/get_furthest_turf(atom/source, angle, atom/target) - var/turf/return_turf - var/list/airlocks = SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/airlock) - for(var/i in 1 to run_distance) - var/turf/test_destination = get_ranged_target_turf_direct(source, target, range = i, offset = angle) - if(test_destination.is_blocked_turf(source_atom = source, ignore_atoms = airlocks)) - break - return_turf = test_destination - return return_turf - -/datum/ai_behavior/run_away_from_target/finish_action(datum/ai_controller/controller, succeeded, target_key, hiding_location_key) - . = ..() - if (clear_failed_targets) - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/run_away_from_target/run_and_shoot - clear_failed_targets = FALSE - -/datum/ai_behavior/run_away_from_target/run_and_shoot/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/living_pawn = controller.pawn - living_pawn.RangedAttack(target) - return ..() - diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm deleted file mode 100644 index 6bf6dbb7fdbc..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm +++ /dev/null @@ -1,10 +0,0 @@ -/datum/ai_behavior/set_travel_destination - -/datum/ai_behavior/set_travel_destination/perform(seconds_per_tick, datum/ai_controller/controller, target_key, location_key) - var/atom/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(location_key, target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm deleted file mode 100644 index 7cb3a7b9f140..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm +++ /dev/null @@ -1,74 +0,0 @@ -/** - * # Step towards turf - * Moves a short distance towards a location repeatedly until you arrive at the destination. - * You'd use this over travel_towards if you're travelling a long distance over a long time, because the AI controller has a maximum range. - */ -/datum/ai_behavior/step_towards_turf - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// How far ahead do we plot movement per action? Further means longer until we return to the decision tree, fewer means jerkier movement - /// This can still result in long moves because this is "a tile x tiles away" not "only move x tiles", you might path around some walls - var/step_distance = 3 - -/datum/ai_behavior/step_towards_turf/setup(datum/ai_controller/controller, turf_key) - var/turf/target_turf = controller.blackboard[turf_key] - if (QDELETED(target_turf) || target_turf.is_blocked_turf(exclude_mobs = TRUE)) - target_turf = find_destination_turf(args) - if (!target_turf) - return FALSE - controller.set_blackboard_key(turf_key, target_turf) - - if (target_turf.z != controller.pawn.z) - return FALSE - - var/turf/destination = plot_movement(controller, target_turf) - if (!destination) - return FALSE - set_movement_target(controller, destination) - return ..() - -/** - * Get a turf to aim towards if we don't already have one, the default behaviour is actually to not do this but we want to extend it - * Gets passed all of the arguments from `setup` - */ -/datum/ai_behavior/step_towards_turf/proc/find_destination_turf() - return null - -/** - * Figure out where we're going to move to, which isn't all the way to the destination in one go - */ -/datum/ai_behavior/step_towards_turf/proc/plot_movement(datum/ai_controller/controller, turf/target_turf) - var/distance_to_destination = get_dist(controller.pawn, target_turf) - if (distance_to_destination <= step_distance) - return target_turf - - var/direction_to_destination = get_dir(controller.pawn, target_turf) - return get_ranged_target_turf(controller.pawn, direction_to_destination, step_distance) - -// We actually only wanted the movement so if we've arrived we're done -/datum/ai_behavior/step_towards_turf/perform(seconds_per_tick, datum/ai_controller/controller, area_key, turf_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/** - * # Step towards turf in area - * Moves a short distance towards a location in an area - * Unlike step_towards_turf it will reacquire a new turf from the area if it loses its target - */ -/datum/ai_behavior/step_towards_turf/in_area - -/datum/ai_behavior/step_towards_turf/in_area/setup(datum/ai_controller/controller, turf_key, area_key) - var/area/target_area = controller.blackboard[area_key] - if (!target_area) - return FALSE - - return ..() - -// Return the first valid turf in the area to replace a lost target -/datum/ai_behavior/step_towards_turf/in_area/find_destination_turf(datum/ai_controller/controller, turf_key, area_key) - var/area/target_area = controller.blackboard[area_key] - var/list/target_area_turfs = get_area_turfs(target_area.type) - for (var/turf/potential_target as anything in target_area_turfs) - if (potential_target.is_blocked_turf(exclude_mobs = TRUE)) - continue - return potential_target - return null diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm index 4ce1f877b18a..a5a60d726383 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm @@ -1,27 +1,29 @@ -/// Makes a mob simply stop and stare at a movable... yea... -/datum/ai_behavior/stop_and_stare - behavior_flags = AI_BEHAVIOR_MOVE_AND_PERFORM +/// Faces a nearby scary atom and holds still for a while. +/datum/bt_node/ai_behavior/stop_and_stare + /// Blackboard key holding the atom we're staring at. + var/target_key = BB_STATIONARY_CAUSE + /// Blackboard key holding how long (in deciseconds) to stay frozen for. + var/stare_duration_key = BB_STATIONARY_SECONDS -/datum/ai_behavior/stop_and_stare/setup(datum/ai_controller/controller, target_key) - . = ..() +/datum/bt_node/ai_behavior/stop_and_stare/setup(datum/ai_controller/controller) var/atom/movable/target = controller.blackboard[target_key] return ismovable(target) && isturf(target.loc) && ismob(controller.pawn) -/datum/ai_behavior/stop_and_stare/get_cooldown(datum/ai_controller/cooldown_for) - return cooldown_for.blackboard[BB_STATIONARY_COOLDOWN] +/datum/bt_node/ai_behavior/stop_and_stare/get_cooldown(datum/ai_controller/cooldown_for) + return cooldown_for.blackboard[stare_duration_key] || ..() -/datum/ai_behavior/stop_and_stare/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/stop_and_stare/perform(seconds_per_tick, datum/ai_controller/controller) var/atom/movable/target = controller.blackboard[target_key] - if(!ismovable(target) || !isturf(target.loc)) // just to make sure that nothing funky happened between setup and perform - return AI_BEHAVIOR_DELAY + if(!ismovable(target) || !isturf(target.loc)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/mob/pawn_mob = controller.pawn - var/turf/pawn_turf = get_turf(pawn_mob) - pawn_mob.face_atom(target) pawn_mob.balloon_alert_to_viewers("stops and stares...") - set_movement_target(controller, pawn_turf, /datum/ai_movement/complete_stop) + // Returning a long cooldown keeps this leaf RUNNING (and thus the mob standing still) for the stare. + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if(controller.blackboard[BB_STATIONARY_MOVE_TO_TARGET]) - addtimer(CALLBACK(src, PROC_REF(set_movement_target), controller, target, initial(controller.ai_movement)), (controller.blackboard[BB_STATIONARY_SECONDS] + 1 SECONDS)) - return AI_BEHAVIOR_DELAY +/datum/bt_node/ai_behavior/stop_and_stare/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + // Forget the cause so we can be spooked fresh next time it wanders into view. + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm index 1e438ea09799..6b1f18e2441c 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm @@ -1,55 +1,55 @@ -/** - * # Targeted Mob Ability - * Attempts to use a mob's cooldown ability on a target - */ -/datum/ai_behavior/targeted_mob_ability +/// Tries to use a specified ability on the current target +/datum/bt_node/ai_behavior/targeted_mob_ability + var/ability_key = BB_GENERIC_ACTION + var/target_key + /// Maximum distance at which the ability can fire (inclusive cuz this is tg :) ) + var/maximum_distance = 0 + ///Does this require adjacency? + var/require_adjacency = FALSE + +/datum/bt_node/ai_behavior/targeted_mob_ability/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags -/datum/ai_behavior/targeted_mob_ability/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) var/datum/action/cooldown/ability = controller.blackboard[ability_key] var/mob/living/target = controller.blackboard[target_key] if(QDELETED(ability) || QDELETED(target)) return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - var/mob/pawn = controller.pawn - pawn.face_atom(target) + if(maximum_distance && get_dist(controller.pawn, target) > maximum_distance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(require_adjacency && !controller.pawn.Adjacent(target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!ability.IsAvailable()) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/mob/pawn_mob = controller.pawn + pawn_mob.face_atom(target) + return start_async() + +/datum/bt_node/ai_behavior/targeted_mob_ability/perform_async(datum/ai_controller/controller) + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + var/atom/target = controller.blackboard[target_key] var/result = ability.Trigger(target = target) - if(result) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - -/** - * # Try Mob Ability and plan execute - * Attempts to use a mob's cooldown ability on a target and then move the target into a special target blackboard datum - * Doesn't need another subtype to clear BB_BASIC_MOB_EXECUTION_TARGET because it will be the target key for the normal action - */ -/datum/ai_behavior/targeted_mob_ability/and_plan_execute - -/datum/ai_behavior/targeted_mob_ability/and_plan_execute/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) + if(!async_still_valid()) + return + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/// Variant for abilities that require adjacency (distance ≤ 1). +/datum/bt_node/ai_behavior/targeted_mob_ability/melee + require_adjacency = TRUE + + +/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute + +/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute/finish_action(datum/ai_controller/controller, succeeded) controller.set_blackboard_key(BB_BASIC_MOB_EXECUTION_TARGET, controller.blackboard[target_key]) return ..() -/** - * # Try Mob Ability and clear target - * Attempts to use a mob's cooldown ability on a target and releases the target when the action completes - */ -/datum/ai_behavior/targeted_mob_ability/and_clear_target +/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target -/datum/ai_behavior/targeted_mob_ability/and_clear_target/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) +/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) -/** - * Attempts to move into the provided range and then use a mob's cooldown ability on a target - */ -/datum/ai_behavior/targeted_mob_ability/min_range - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - required_distance = 6 - -/datum/ai_behavior/targeted_mob_ability/min_range/setup(datum/ai_controller/controller, ability_key, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) -/datum/ai_behavior/targeted_mob_ability/min_range/short - required_distance = 3 diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm index ce46d4c1ab98..2fd2e494ef16 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm @@ -5,101 +5,74 @@ GLOBAL_ALIST_EMPTY(hostile_machines_by_z) /// Must be kept up to date with the contents of hostile_machines GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/porta_turret, /obj/vehicle/sealed/mecha))) -/datum/ai_behavior/find_potential_targets - action_cooldown = 2 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// How far can we see stuff? - var/vision_range = 9 + +///Used to find combat targets; Allow finding things hidden in things such as lockers too. +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets + target_source = /datum/target_source/hearers + targeting_strategy = BB_TARGETING_STRATEGY + vision_range = 9 + target_loss_distance = 16 /// Blackboard key for aggro range, uses vision range if not specified var/aggro_range_key = BB_AGGRO_RANGE - /// Range in which we can acquire a new target - var/aggro_grab_range_key = BB_AGGRO_GRAB_RANGE - /// Blackboard key for the target priority strategy + /// Blackboard key holding the hiding-location atom (e.g. closet the target ducked into) + var/hiding_location_key + /// Blackboard key holding the /datum/target_priority_strategy typepath for selection var/priority_strategy_key = BB_TARGET_PRIORITY_STRATEGY /// If we have a priority strategy set, how often do we refresh our target search? var/priority_refresh_cooldown = 6 SECONDS -/datum/ai_behavior/find_potential_targets/get_cooldown(datum/ai_controller/cooldown_for) - if(cooldown_for.blackboard[BB_FIND_TARGETS_FIELD(type)]) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/get_cooldown(datum/ai_controller/controller) + if(controller.blackboard[BB_FIND_TARGETS_FIELD(type)]) return 60 SECONDS return ..() -/datum/ai_behavior/find_potential_targets/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - var/mob/living/living_mob = controller.pawn - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - - if(!targeting_strategy) - CRASH("No target datum was supplied in the blackboard for [controller.pawn]") +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/can_search(datum/ai_controller/controller) + return !(controller.blackboard[BB_FIND_TARGETS_FIELD(type)]) - var/atom/current_target = controller.blackboard[target_key] +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/should_keep_target(datum/ai_controller/controller, datum/targeting_strategy/strategy, atom/current_target) + if(!current_target) + return FALSE + if(!strategy.is_valid_target(controller.pawn, current_target, vision_range)) + return FALSE var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[priority_strategy_key]) - if((!priority_strategy || controller.blackboard[BB_BASIC_MOB_TARGET_REFRESH_COOLDOWN] > world.time) && current_target && targeting_strategy.can_attack(living_mob, current_target, vision_range)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/aggro_range = vision_range - if(isnull(current_target) && !isnull(controller.blackboard[aggro_grab_range_key])) - aggro_range = controller.blackboard[aggro_grab_range_key] - else if(!isnull(controller.blackboard[aggro_range_key])) - aggro_range = controller.blackboard[aggro_range_key] - - controller.clear_blackboard_key(target_key) - - // If we're using a field rn, just don't do anything yeah? - if(controller.blackboard[BB_FIND_TARGETS_FIELD(type)]) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/list/potential_targets = hearers(aggro_range, get_turf(controller.pawn)) - living_mob //Remove self, so we don't suicide - - var/turf/mob_turf = get_turf(living_mob) - if(mob_turf?.z) - for (var/atom/hostile_machine as anything in GLOB.hostile_machines_by_z[mob_turf.z]) - if (can_see(living_mob, hostile_machine, aggro_range)) - potential_targets += hostile_machine - - if(!potential_targets.len) - if(!current_target) - failed_to_find_anyone(controller, target_key, targeting_strategy_key, hiding_location_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/list/filtered_targets = list() - var/current_priority = 0 - if(priority_strategy) - current_priority = priority_strategy.get_target_priority(controller, current_target) - - for(var/atom/pot_target in potential_targets) - if(!targeting_strategy.can_attack(living_mob, pot_target)) + if(!priority_strategy) + return TRUE + return controller.blackboard[BB_BASIC_MOB_TARGET_REFRESH_COOLDOWN] > world.time + +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/on_no_candidates(datum/ai_controller/controller, atom/current_target, datum/targeting_strategy/strategy, range) + if(current_target && strategy.can_keep_target(controller.pawn, current_target, target_loss_distance)) + return list(current_target) + if(!current_target) + failed_to_find_anyone(controller, target_key, targeting_strategy, hiding_location_key) + return list() + +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/on_no_valid_candidates(datum/ai_controller/controller, atom/current_target) + if(!current_target) + failed_to_find_anyone(controller, target_key, targeting_strategy, hiding_location_key) + +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/filter_candidates(datum/ai_controller/controller, list/candidates, datum/targeting_strategy/strategy, atom/current_target) + var/mob/living/pawn = controller.pawn + var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[priority_strategy_key]) + var/current_priority = priority_strategy ? priority_strategy.get_target_priority(controller, current_target) : 0 + var/list/filtered = list() + for(var/atom/candidate as anything in candidates) + if(!strategy.is_valid_target(pawn, candidate, vision_range, controller)) continue - if (priority_strategy && priority_strategy.get_target_priority(controller, pot_target) < current_priority) + if(priority_strategy && priority_strategy.get_target_priority(controller, candidate) < current_priority) continue - filtered_targets += pot_target - - if(!filtered_targets.len) - if(!current_target) - failed_to_find_anyone(controller, target_key, targeting_strategy_key, hiding_location_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + filtered += candidate + return filtered - var/atom/target = pick_final_target(controller, filtered_targets) - - EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [target] as a target for blackboard key [target_key]! Behavior: [src]", get_turf(target), "Target: [target]") - EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(target)) - - controller.set_blackboard_key(target_key, target) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) controller.set_blackboard_key(BB_BASIC_MOB_TARGET_REFRESH_COOLDOWN, world.time + priority_refresh_cooldown) - - var/atom/potential_hiding_location = targeting_strategy.find_hidden_mobs(living_mob, target) - - if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially. + var/atom/potential_hiding_location = strategy.find_hidden_mobs(controller.pawn, target) + if(potential_hiding_location) controller.set_blackboard_key(hiding_location_key, potential_hiding_location) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_potential_targets/proc/failed_to_find_anyone(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - var/aggro_range = vision_range - if(!isnull(controller.blackboard[aggro_grab_range_key])) - aggro_range = controller.blackboard[aggro_grab_range_key] - else if(!isnull(controller.blackboard[aggro_range_key])) - aggro_range = controller.blackboard[aggro_range_key] - +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/proc/failed_to_find_anyone(datum/ai_controller/controller, target_key, targeting_strategy, hiding_location_key) + // TEMP DISABLED nuke this if performance improves + /* + var/aggro_range = controller.blackboard[aggro_range_key] || vision_range // takes the larger between our range() input and our implicit hearers() input (world.view) aggro_range = max(aggro_range, ROUND_UP(max(getviewsize(world.view)) / 2)) // Alright, here's the interesting bit @@ -112,13 +85,16 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ src, controller, target_key, - targeting_strategy_key, + targeting_strategy, hiding_location_key, ) // We're gonna store this field in our blackboard, so we can clear it away if we end up finishing successsfully controller.set_blackboard_key(BB_FIND_TARGETS_FIELD(type), detection_field) + */ + controller.clear_blackboard_key(target_key) + -/datum/ai_behavior/find_potential_targets/proc/new_turf_found(turf/found, datum/ai_controller/controller, datum/targeting_strategy/strategy) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/proc/new_turf_found(turf/found, datum/ai_controller/controller, datum/targeting_strategy/strategy) var/valid_found = FALSE var/mob/pawn = controller.pawn for(var/maybe_target in found) @@ -126,7 +102,7 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ continue if(!is_type_in_typecache(maybe_target, GLOB.target_interested_atoms)) continue - if(!strategy.can_attack(pawn, maybe_target)) + if(!strategy.is_valid_target(pawn, maybe_target)) continue valid_found = TRUE break @@ -136,18 +112,18 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ var/datum/proximity_monitor/field = controller.blackboard[BB_FIND_TARGETS_FIELD(type)] qdel(field) // autoclears so it's fine // Fire instantly, you should find something I hope - controller.modify_cooldown(src, world.time) + modify_cooldown(world.time) -/datum/ai_behavior/find_potential_targets/proc/atom_allowed(atom/movable/checking, datum/targeting_strategy/strategy, mob/pawn) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/proc/atom_allowed(atom/movable/checking, datum/targeting_strategy/strategy, mob/pawn) if(checking == pawn) return FALSE if(!ismob(checking) && !is_type_in_typecache(checking, GLOB.target_interested_atoms)) return FALSE - if(!strategy.can_attack(pawn, checking)) + if(!strategy.is_valid_target(pawn, checking)) return FALSE return TRUE -/datum/ai_behavior/find_potential_targets/proc/new_atoms_found(list/atom/movable/found, datum/ai_controller/controller, target_key, datum/targeting_strategy/strategy, hiding_location_key) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/proc/new_atoms_found(list/atom/movable/found, datum/ai_controller/controller, target_key, datum/targeting_strategy/strategy, hiding_location_key) var/mob/pawn = controller.pawn var/list/accepted_targets = list() for(var/maybe_target in found) @@ -156,7 +132,7 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ // Need to better handle viewers here if(!ismob(maybe_target) && !is_type_in_typecache(maybe_target, GLOB.target_interested_atoms)) continue - if(!strategy.can_attack(pawn, maybe_target)) + if(!strategy.is_valid_target(pawn, maybe_target)) continue accepted_targets += maybe_target @@ -165,36 +141,46 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [target] as a target for blackboard key [target_key]! Behavior: [src]", get_turf(target), "Target: [target]") EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(target)) controller.set_blackboard_key(target_key, target) - - var/atom/potential_hiding_location = strategy.find_hidden_mobs(pawn, target) - - if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially. - controller.set_blackboard_key(hiding_location_key, potential_hiding_location) + on_target_found(controller, target, strategy) finish_action(controller, succeeded = TRUE) -/datum/ai_behavior/find_potential_targets/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/finish_action(datum/ai_controller/controller, succeeded) . = ..() if (succeeded) var/datum/proximity_monitor/field = controller.blackboard[BB_FIND_TARGETS_FIELD(type)] qdel(field) // autoclears so it's fine - controller.CancelActions() // On retarget cancel any further queued actions so that they will setup again with new target - controller.modify_cooldown(src, get_cooldown(controller)) + modify_cooldown(get_cooldown(controller)) -/// Returns the desired final target from the filtered list of targets -/datum/ai_behavior/find_potential_targets/proc/pick_final_target(datum/ai_controller/controller, list/filtered_targets) +/// Picks the final target, preferring higher-priority candidates when a priority strategy is set. +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/pick_final_target(datum/ai_controller/controller, list/filtered_targets) var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[priority_strategy_key]) - if (!priority_strategy) - return pick(filtered_targets) + if(!priority_strategy) + return filtered_targets[1] return priority_strategy.select_target(controller, filtered_targets) -/// Targets with the trait specified by the BB_TARGET_PRIORITY_TRAIT blackboard key will be prioritized over the rest. -/datum/ai_behavior/find_potential_targets/prioritize_trait +/// Picks targets based on which one has the lowest health. +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded + +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/list/living_targets = list() + for(var/mob/living/living_target in filtered_targets) + living_targets += living_target + if(living_targets.len) + sortTim(living_targets, GLOBAL_PROC_REF(cmp_mob_health)) + return living_targets[living_targets.len] + return ..() + +/// Prioritizes targets carrying the trait named by our trait_key blackboard key over the rest. +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait + /// Blackboard key holding the trait that marks a target as high-priority. + var/trait_key = BB_TARGET_PRIORITY_TRAIT -/datum/ai_behavior/find_potential_targets/prioritize_trait/pick_final_target(datum/ai_controller/controller, list/filtered_targets) - var/priority_targets = list() +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/list/priority_targets = list() + var/priority_trait = controller.blackboard[trait_key] for(var/atom/target as anything in filtered_targets) - if(HAS_TRAIT(target, controller.blackboard[BB_TARGET_PRIORITY_TRAIT])) + if(HAS_TRAIT(target, priority_trait)) priority_targets += target if(length(priority_targets)) return ..(controller, priority_targets) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/tipped_reaction.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/tipped_reaction.dm index c734d961b5ef..288276b358c3 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/tipped_reaction.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/tipped_reaction.dm @@ -1,15 +1,13 @@ +///A tipped-over mob looks to a nearby person for help, then resigns itself to its fate. +/datum/bt_node/ai_behavior/tipped_reaction + /// Blackboard key holding the mob that tipped us over. + var/tipper_key = BB_BASIC_MOB_TIPPER + /// Blackboard key holding whether we are still reacting to being tipped. + var/reacting_key = BB_BASIC_MOB_TIP_REACTING -///type of tipped reaction that is akin to puppy dog eyes -/datum/ai_behavior/tipped_reaction - -/datum/ai_behavior/tipped_reaction/perform(seconds_per_tick, datum/ai_controller/controller, tipper_key, reacting_key) +/datum/bt_node/ai_behavior/tipped_reaction/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/carbon/tipper = controller.blackboard[tipper_key] - // visible part of the visible message - var/seen_message = "" - // self part of the visible message - var/self_message = "" - // the mob we're looking to for aid var/mob/living/carbon/savior // look for someone in a radius around us for help. If our original tipper is in range, prioritize them for(var/mob/living/carbon/potential_aid in oview(3, get_turf(controller.pawn))) @@ -18,6 +16,8 @@ break savior = potential_aid + var/seen_message + var/self_message if(prob(75) && savior) var/text = pick("imploringly", "pleadingly", "with a resigned expression") seen_message = "[controller.pawn] looks at [savior] [text]." @@ -28,7 +28,7 @@ controller.pawn.visible_message(span_notice("[seen_message]"), span_notice("[self_message]")) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/tipped_reaction/finish_action(datum/ai_controller/controller, succeeded, tipper_key, reacting_key) +/datum/bt_node/ai_behavior/tipped_reaction/finish_action(datum/ai_controller/controller, succeeded) . = ..() //I'VE SAID MY PEACE... controller.set_blackboard_key(reacting_key, FALSE) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm deleted file mode 100644 index f78eb3db4ec0..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm +++ /dev/null @@ -1,53 +0,0 @@ -/** - * # Travel Towards - * Moves towards the atom in the passed blackboard key. - * Planning continues during this action so it can be interrupted by higher priority actions. - */ -/datum/ai_behavior/travel_towards - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// If true we will get rid of our target on completion - var/clear_target = FALSE - ///should we use a different movement type? - var/new_movement_type - -/datum/ai_behavior/travel_towards/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target, new_movement_type) - -/datum/ai_behavior/travel_towards/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/travel_towards/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if (clear_target) - controller.clear_blackboard_key(target_key) - if(new_movement_type) - controller.change_ai_movement_type(initial(controller.ai_movement)) - -/datum/ai_behavior/travel_towards/stop_on_arrival - clear_target = TRUE - -/datum/ai_behavior/travel_towards/adjacent - required_distance = 1 - -/** - * # Travel Towards Atom - * Travel towards an atom you pass directly from the controller rather than a blackboard key. - * You might need to do this to avoid repeating some checks in both a controller and an action. - */ -/datum/ai_behavior/travel_towards_atom - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - -/datum/ai_behavior/travel_towards_atom/setup(datum/ai_controller/controller, atom/target_atom) - . = ..() - if(isnull(target_atom)) - return FALSE - set_movement_target(controller, target_atom) - -/datum/ai_behavior/travel_towards_atom/perform(seconds_per_tick, datum/ai_controller/controller, atom/target_atom) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm deleted file mode 100644 index 34e651f8e528..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm +++ /dev/null @@ -1,11 +0,0 @@ -/datum/ai_behavior/unbuckle_mob - -/datum/ai_behavior/unbuckle_mob/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - var/atom/movable/buckled_to = living_pawn.buckled - - if(isnull(buckled_to)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - buckled_to.unbuckle_mob(living_pawn) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/use_mob_ability.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/use_mob_ability.dm new file mode 100644 index 000000000000..847a54758dfd --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/use_mob_ability.dm @@ -0,0 +1,39 @@ +/// Triggers a mob ability stored in a blackboard key. Returns INSTANT SUCCESS if triggered, INSTANT FAILURE if unavailable or trigger fails. +/datum/bt_node/ai_behavior/use_mob_ability + var/ability_key = BB_GENERIC_ACTION + +/datum/bt_node/ai_behavior/use_mob_ability/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/datum/action/using_action = get_valid_ability(controller) + if(QDELETED(using_action)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return start_async() + +/// Returns the action to trigger, or null if it isn't available. Override to prep the action before it fires. +/datum/bt_node/ai_behavior/use_mob_ability/proc/get_valid_ability(datum/ai_controller/controller) + var/datum/action/using_action = controller.blackboard[ability_key] + if(QDELETED(using_action) || !using_action.IsAvailable()) + return null + return using_action + +/datum/bt_node/ai_behavior/use_mob_ability/perform_async(datum/ai_controller/controller) + var/datum/action/using_action = controller.blackboard[ability_key] + var/result = using_action.Trigger() + if(!async_still_valid()) + return + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/// Triggers a shapeshift ability, picking a random shape if none has been selected yet (AI can't use context wheels). +/datum/bt_node/ai_behavior/use_mob_ability/shapeshift + ability_key = BB_SHAPESHIFT_ACTION + +/datum/bt_node/ai_behavior/use_mob_ability/shapeshift/get_valid_ability(datum/ai_controller/controller) + var/datum/action/cooldown/spell/shapeshift/using_action = ..() + if(QDELETED(using_action)) + return null + if(isnull(using_action.shapeshift_type)) + using_action.shapeshift_type = pick(using_action.possible_shapes) + return using_action diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm index 06fcfa147262..7e1f3a763724 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm @@ -1,143 +1,190 @@ -/// We hop into the vents through a vent outlet, and then crawl around a bit. Jolly good times. -/// This also assumes that we are on the turf that the vent outlet is on. If it isn't, shit. +///uhm...sus? +/datum/bt_node/subtree/consider_venting + behavior_tree_json = "code/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.json" -/// Warning: this was really snowflake code lifted from an obscure feature that likely has not been touched for over five years years. -/// Something that isn't implemented is the ability to actually crawl through vents ourselves because I think that's just a waste of time for the same effect (instead of psuedo-teleportation, do REAL forceMoving) -/// If you are seriously considering using this component, it would be a great idea to extend this proc to be more versatile/less overpowered - the mobs that currently implement this benefit the most -/// since they are weak as shit with only five health. Up to you though, don't take what's written here as gospel. -/datum/ai_behavior/crawl_through_vents - action_cooldown = 10 SECONDS -/datum/ai_behavior/crawl_through_vents/get_cooldown(datum/ai_controller/cooldown_for) - return cooldown_for.blackboard[BB_VENTCRAWL_COOLDOWN] || initial(action_cooldown) +/// Enters a vent stored in entry_vent_key. Sets BB_EXIT_VENT_TARGET and BB_VENT_ENTRY_TIME on success. +/datum/bt_node/ai_behavior/enter_vent + var/entry_vent_key = BB_ENTRY_VENT_TARGET + /// TRUE while the async crawl-in is running. perform() holds at DELAY until it resolves. + var/is_starting_crawl = FALSE + /// Set by the async action when the crawl finished but we did not end up in the vent. + var/failed_ventcrawl = FALSE -/datum/ai_behavior/crawl_through_vents/setup(datum/ai_controller/controller, target_key) - . = ..() - var/obj/machinery/atmospherics/components/unary/vent_pump/target = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET] - return istype(target) && isliving(controller.pawn) // only mobs can vent crawl in the current framework - -/datum/ai_behavior/crawl_through_vents/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET] +/datum/bt_node/ai_behavior/enter_vent/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/cached_pawn = controller.pawn - if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING) || !controller.blackboard[BB_CURRENTLY_TARGETING_VENT] || !is_vent_valid(entry_vent)) - return AI_BEHAVIOR_DELAY - - if(!cached_pawn.can_enter_vent(entry_vent, provide_feedback = FALSE)) // we're an AI we scoff at feedback - // "never enter a hole you can't get out of" - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/vent_we_exit_out_of = calculate_exit_vent(controller, target_key) - if(isnull(vent_we_exit_out_of)) // don't get into the vents if we can't get out of them, that's SILLY. - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + // We kicked off the crawl on a previous tick; report its result once it resolves. Flags reset in finish_action. + if(failed_ventcrawl) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(is_starting_crawl) + if(!HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) + return AI_BEHAVIOR_DELAY // still climbing in + controller.set_blackboard_key(BB_VENT_ENTRY_TIME, world.time) + if(prob(50)) + cached_pawn.visible_message( + span_warning("[cached_pawn] scrambles into the ventilation ducts!"), + span_hear("You hear something scampering through the ventilation ducts."), + ) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[entry_vent_key] + if(!is_vent_valid(entry_vent) || !isliving(cached_pawn)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + if(!cached_pawn.can_enter_vent(entry_vent, provide_feedback = FALSE)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = calculate_exit_vent(controller) + if(isnull(exit_vent)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(BB_EXIT_VENT_TARGET, exit_vent) + is_starting_crawl = TRUE + INVOKE_ASYNC(src, PROC_REF(perform_ventcrawl_action), controller, entry_vent) + return AI_BEHAVIOR_DELAY - controller.set_blackboard_key(BB_CURRENTLY_TARGETING_VENT, FALSE) // must be done here because we have a do_after sleep in handle_ventcrawl unfortunately and double dipping could lead to erroneous suicide pill calls. +/// Runs the sleeping ventcrawl off the tick. Flags failure if we didn't end up in the vent, so perform() never has to sleep. +/datum/bt_node/ai_behavior/enter_vent/proc/perform_ventcrawl_action(datum/ai_controller/controller, obj/machinery/atmospherics/components/unary/vent_pump/entry_vent) + var/mob/living/cached_pawn = controller.pawn cached_pawn.handle_ventcrawl(entry_vent) if(!HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) //something failed and we ARE NOT IN THE VENT even though the earlier check said we were good to go! odd. - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + failed_ventcrawl = TRUE - controller.set_blackboard_key(BB_EXIT_VENT_TARGET, vent_we_exit_out_of) +/datum/bt_node/ai_behavior/enter_vent/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + is_starting_crawl = FALSE + failed_ventcrawl = FALSE + if(!succeeded) + controller.clear_blackboard_key(entry_vent_key) + +/// Returns TRUE if the vent exists and isn't welded shut. +/datum/bt_node/ai_behavior/enter_vent/proc/is_vent_valid(obj/machinery/atmospherics/components/unary/vent_pump/vent) + return !QDELETED(vent) && !vent.welded + +/// Picks a random valid vent on the same pipeline as the entry vent. Falls back to the entry vent itself; returns null if nothing is usable. +/datum/bt_node/ai_behavior/enter_vent/proc/calculate_exit_vent(datum/ai_controller/controller) + var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[entry_vent_key] + if(QDELETED(entry_vent)) + return null - if(prob(50)) - cached_pawn.visible_message( - span_warning("[src] scrambles into the ventilation ducts!"), - span_hear("You hear something scampering through the ventilation ducts."), - ) + var/datum/pipeline/parent_pipe = entry_vent.parents[1] + var/list/candidates = list() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/vent in parent_pipe.other_atmos_machines) + if(is_vent_valid(vent)) + candidates += vent - var/lower_vent_time_limit = controller.blackboard[BB_LOWER_VENT_TIME_LIMIT] // the least amount of time we spend in the vents - var/upper_vent_time_limit = controller.blackboard[BB_UPPER_VENT_TIME_LIMIT] // the most amount of time we spend in the vents + if(length(candidates)) + return pick(candidates) - addtimer(CALLBACK(src, PROC_REF(exit_the_vents), controller), rand(lower_vent_time_limit, upper_vent_time_limit)) - controller.set_blackboard_key(BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID, addtimer(CALLBACK(src, PROC_REF(delayed_suicide_pill), controller, target_key), controller.blackboard[BB_TIME_TO_GIVE_UP_ON_VENT_PATHING], TIMER_STOPPABLE)) - return AI_BEHAVIOR_DELAY + if(is_vent_valid(entry_vent)) + return null // in the pipeline already; let the caller handle it -/// Figure out an exit vent that we should head towards. If we don't have one, default to the entry vent. If they're all kaput, we die. -/datum/ai_behavior/crawl_through_vents/proc/calculate_exit_vent(datum/ai_controller/controller, target_key) - var/obj/machinery/atmospherics/components/unary/vent_pump/returnable_vent - var/obj/machinery/atmospherics/components/unary/vent_pump/vent_we_entered_through = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET] + return entry_vent - var/datum/pipeline/entry_vent_parent = vent_we_entered_through.parents[1] - var/list/potential_exits = list() - for(var/obj/machinery/atmospherics/components/unary/vent_pump/vent in entry_vent_parent.other_atmos_machines) - if(is_vent_valid(vent)) - potential_exits.Add(vent) +/// Waits inside a vent for a randomised duration, then exits. Handles the give-up timeout. +/datum/bt_node/ai_behavior/exit_vent + time_between_perform = 1 SECONDS + var/target_exit_time = 0 + /// TRUE while the async crawl-out is running. perform() holds at DELAY until it resolves. + var/is_exiting_crawl = FALSE + /// Set by the async action when the crawl finished but we are somehow still in the vent. + var/failed_ventcrawl = FALSE - if(length(potential_exits)) - returnable_vent = pick(potential_exits) - return returnable_vent +/datum/bt_node/ai_behavior/exit_vent/setup(datum/ai_controller/controller) + . = ..() + var/lower = controller.blackboard[BB_LOWER_VENT_TIME_LIMIT] + var/upper = controller.blackboard[BB_UPPER_VENT_TIME_LIMIT] + var/entry_time = controller.blackboard[BB_VENT_ENTRY_TIME] || world.time + target_exit_time = entry_time + rand(lower, upper) + return TRUE - // if we're here, we're in "what the flarp" mode... okay maybe we can default to the vent we entered in. - returnable_vent = vent_we_entered_through - if(is_vent_valid(vent_we_entered_through)) - // AH WHAT THE FUCK. okay, maybe we're not inside the vents yet? let's return null and we can pick up on that based on the wider context of the proc that invokes it. - return null +/datum/bt_node/ai_behavior/exit_vent/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/cached_pawn = controller.pawn - return returnable_vent // we return null in case something yonked between then and now so it's all good man + // We kicked off the crawl-out on a previous tick; report its result once it resolves. Flags reset in finish_action. + if(failed_ventcrawl) + return suicide_pill(cached_pawn) + if(is_exiting_crawl) + if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) + return AI_BEHAVIOR_DELAY // still climbing out + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + if(world.time < target_exit_time) + var/give_up = controller.blackboard[BB_TIME_TO_GIVE_UP_ON_VENT_PATHING] + var/entry_time = controller.blackboard[BB_VENT_ENTRY_TIME] + if(give_up && entry_time && world.time > entry_time + give_up) + return suicide_pill(cached_pawn) + return AI_BEHAVIOR_DELAY -/// We've had enough horsing around in the vents, it's time to get out. -/datum/ai_behavior/crawl_through_vents/proc/exit_the_vents(datum/ai_controller/controller, target_key) - var/obj/machinery/atmospherics/components/unary/vent_pump/emergency_vent // vent we will scramble to search for in case plan A is a bust (exit vent) var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = controller.blackboard[BB_EXIT_VENT_TARGET] - var/mob/living/living_pawn = controller.pawn - - if(!HAS_TRAIT(living_pawn, TRAIT_MOVE_VENTCRAWLING) && isturf(get_turf(living_pawn))) // we're out of the vents, so no need to do an exit - // assume that we got yeeted out somehow and return this so we can halt the suicide pill timer. - finish_action(controller, TRUE, target_key) - return - - living_pawn.forceMove(exit_vent) - if(!living_pawn.can_enter_vent(exit_vent, provide_feedback = FALSE)) - // oh shit, something happened while we were waiting on that timer. let's figure out a different way to get out of here. - emergency_vent = calculate_exit_vent(controller) - if(isnull(emergency_vent)) - // it's joever. we cooked too hard. - suicide_pill(controller) - return - - controller.set_blackboard_key(BB_EXIT_VENT_TARGET, emergency_vent) // assign and go again - addtimer(CALLBACK(src, PROC_REF(exit_the_vents), controller), (rand(controller.blackboard[BB_LOWER_VENT_TIME_LIMIT], controller.blackboard[BB_UPPER_VENT_TIME_LIMIT]) / 2)) // we're in danger mode, so scurry out at half the time it would normally take. - return - - living_pawn.handle_ventcrawl(exit_vent) - if(HAS_TRAIT(living_pawn, TRAIT_MOVE_VENTCRAWLING)) // how'd we fail? what the fuck - stack_trace("We failed to exit the vents, even though we should have been fine? This is very weird.") - suicide_pill(controller) - return + if(!is_vent_valid(exit_vent)) + exit_vent = calculate_exit_vent(controller) + if(isnull(exit_vent)) + return suicide_pill(cached_pawn) + controller.set_blackboard_key(BB_EXIT_VENT_TARGET, exit_vent) + + cached_pawn.forceMove(exit_vent) + if(!cached_pawn.can_enter_vent(exit_vent, provide_feedback = FALSE)) + // vent became unusable while we waited; try an emergency exit next tick + var/emergency = calculate_exit_vent(controller) + if(isnull(emergency)) + return suicide_pill(cached_pawn) + controller.set_blackboard_key(BB_EXIT_VENT_TARGET, emergency) + target_exit_time = world.time // retry immediately next tick + return AI_BEHAVIOR_DELAY - finish_action(controller, TRUE, target_key) - return + is_exiting_crawl = TRUE + INVOKE_ASYNC(src, PROC_REF(perform_ventcrawl_action), controller, exit_vent) + return AI_BEHAVIOR_DELAY -/// Incredibly stripped down version of the overarching `can_enter_vent` proc on `/mob, just meant for rapid rechecking of a vent. Will be TRUE if not blocked, FALSE otherwise. -/datum/ai_behavior/crawl_through_vents/proc/is_vent_valid(obj/machinery/atmospherics/components/unary/vent_pump/checkable) - return !QDELETED(checkable) && !checkable.welded +/// Runs the sleeping ventcrawl off the tick. Flags failure if we're somehow still in the vent, so perform() never has to sleep. +/datum/bt_node/ai_behavior/exit_vent/proc/perform_ventcrawl_action(datum/ai_controller/controller, obj/machinery/atmospherics/components/unary/vent_pump/exit_vent) + var/mob/living/cached_pawn = controller.pawn + cached_pawn.handle_ventcrawl(exit_vent) + if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) + stack_trace("[cached_pawn] [type]: exited vent but still has TRAIT_MOVE_VENTCRAWLING") + failed_ventcrawl = TRUE -/// Wraps a delayed defeat, so we gotta handle the return value properly ya feel? -/datum/ai_behavior/crawl_through_vents/proc/delayed_suicide_pill(datum/ai_controller/controller, target_key) - if(suicide_pill(controller) & AI_BEHAVIOR_FAILED) - finish_action(controller, FALSE, target_key) +/datum/bt_node/ai_behavior/exit_vent/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + is_exiting_crawl = FALSE + failed_ventcrawl = FALSE + controller.clear_blackboard_key(BB_VENT_ENTRY_TIME) + controller.clear_blackboard_key(BB_EXIT_VENT_TARGET) + controller.clear_blackboard_key(BB_ENTRY_VENT_TARGET) -/// Aw fuck, we may have been bested somehow. Regardless of what we do, we can't exit through a vent! Let's end our misery and prevent useless endless calculations. -/datum/ai_behavior/crawl_through_vents/proc/suicide_pill(datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn +/// Kills the pawn if it has no client, then returns INSTANT FAILED. +/datum/bt_node/ai_behavior/exit_vent/proc/suicide_pill(mob/living/pawn) + if(istype(pawn) && isnull(pawn.client)) + pawn.death(TRUE) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - if(istype(living_pawn)) - if(isnull(living_pawn.client)) // only call death if we don't have a client because maybe their natural intelligence can pick up where our AI calculations have failed - living_pawn.death(TRUE) // call gibbed as true because we are never coming back it is so fucking joever +/// Returns TRUE if the vent exists and isn't welded shut. +/datum/bt_node/ai_behavior/exit_vent/proc/is_vent_valid(obj/machinery/atmospherics/components/unary/vent_pump/vent) + return !QDELETED(vent) && !vent.welded - return AI_BEHAVIOR_FAILED +/// Picks a random valid vent on the same pipeline as BB_ENTRY_VENT_TARGET. Returns null if nothing is usable. +/datum/bt_node/ai_behavior/exit_vent/proc/calculate_exit_vent(datum/ai_controller/controller) + var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[BB_ENTRY_VENT_TARGET] + if(QDELETED(entry_vent)) + return null - if(QDELETED(living_pawn)) // we got deleted by some other means, just presume the action is a wash and get outta here - return NONE + var/datum/pipeline/parent_pipe = entry_vent.parents[1] + var/list/candidates = list() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/vent in parent_pipe.other_atmos_machines) + if(is_vent_valid(vent)) + candidates += vent - qdel(living_pawn) // failover, we really should've been caught in the istype() but lets just bow out of existing at this point - return NONE + if(length(candidates)) + return pick(candidates) -/datum/ai_behavior/crawl_through_vents/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() + if(is_vent_valid(entry_vent)) + return null - deltimer(controller.blackboard[BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID]) - controller.clear_blackboard_key(target_key) - controller.clear_blackboard_key(BB_ENTRY_VENT_TARGET) - controller.clear_blackboard_key(BB_EXIT_VENT_TARGET) - controller.set_blackboard_key(BB_CURRENTLY_TARGETING_VENT, FALSE) // just in case + return entry_vent diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm deleted file mode 100644 index 46037fdc076e..000000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm +++ /dev/null @@ -1,11 +0,0 @@ -/// Picks targets based on which one has the lowest health -/datum/ai_behavior/find_potential_targets/most_wounded - -/datum/ai_behavior/find_potential_targets/most_wounded/pick_final_target(datum/ai_controller/controller, list/filtered_targets) - var/list/living_targets = list() - for(var/mob/living/living_target in filtered_targets) - living_targets += filtered_targets - if(living_targets.len) - sortTim(living_targets, GLOBAL_PROC_REF(cmp_mob_health)) - return pop(living_targets) - return ..() diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/write_on_paper.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/write_on_paper.dm index 51826676e9e8..e5dab148bd56 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/write_on_paper.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/write_on_paper.dm @@ -1,15 +1,23 @@ -/datum/ai_behavior/write_on_paper +/// Scrawls a random line from the writing list onto the carried paper, then drops it. Clears the carry key on finish. +/datum/bt_node/ai_behavior/write_on_paper + /// Blackboard key holding the paper to write on (also the virtual carry slot). + var/paper_key + /// Blackboard key holding the list of phrases to choose from. + var/writing_list_key -/datum/ai_behavior/write_on_paper/perform(seconds_per_tick, datum/ai_controller/controller, found_paper, list_of_writings) +/datum/bt_node/ai_behavior/write_on_paper/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/wizard = controller.pawn - var/list/writing_list = controller.blackboard[list_of_writings] - var/obj/item/paper/target = controller.blackboard[found_paper] + var/obj/item/paper/target = controller.blackboard[paper_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/list/writing_list = controller.blackboard[writing_list_key] if(length(writing_list)) target.add_raw_text(pick(writing_list)) target.update_appearance() - wizard.dropItemToGround(target) + if(target.loc == wizard) + target.forceMove(get_turf(wizard)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/write_on_paper/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/write_on_paper/finish_action(datum/ai_controller/controller, succeeded) . = ..() - controller.clear_blackboard_key(target_key) + controller.clear_blackboard_key(paper_key) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm deleted file mode 100644 index 98cf1654f713..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm +++ /dev/null @@ -1,35 +0,0 @@ -/// Attack something which is already adjacent to us, without ending planning -/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/opportunistic - end_planning = FALSE - -/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target) || !controller.pawn.Adjacent(target)) - return - if (isliving(controller.pawn)) - var/mob/living/pawn = controller.pawn - if (LAZYLEN(pawn.do_afters)) - return - controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - -/// Attack something which is already adjacent to us without moving -/datum/ai_behavior/basic_melee_attack/opportunistic - action_cooldown = 0.2 SECONDS // We gotta check unfortunately often because we're in a race condition with nextmove - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/basic_melee_attack/opportunistic/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - if (!controller.blackboard_key_exists(targeting_strategy_key)) - CRASH("No target datum was supplied in the blackboard for [controller.pawn]") - return controller.blackboard_key_exists(target_key) - -/datum/ai_behavior/basic_melee_attack/opportunistic/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - var/atom/movable/atom_pawn = controller.pawn - var/atom/atom_target = controller.blackboard[target_key] - if (QDELETED(atom_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(!atom_target.IsReachableBy(atom_pawn)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - . = ..() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm deleted file mode 100644 index bc8efdeb4ff5..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm +++ /dev/null @@ -1,85 +0,0 @@ -/// If there's something between us and our target then we need to queue a behaviour to make it not be there -/datum/ai_planning_subtree/attack_obstacle_in_path - /// Blackboard key containing current target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// The action to execute, extend to add a different cooldown or something - var/attack_behaviour = /datum/ai_behavior/attack_obstructions - -/datum/ai_planning_subtree/attack_obstacle_in_path/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return - - var/turf/next_step = get_step_towards(controller.pawn, target) - if (!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn)) - return - - controller.queue_behavior(attack_behaviour, target_key) - // Don't cancel future planning, maybe we can move now - -/// Something is in our way, get it outta here -/datum/ai_behavior/attack_obstructions - action_cooldown = 2 SECONDS - /// If we should attack walls, be prepared for complaints about breaches - var/can_attack_turfs = FALSE - /// For if you want your mob to be able to attack dense objects - var/can_attack_dense_objects = FALSE - -/datum/ai_behavior/attack_obstructions/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/basic/basic_mob = controller.pawn - var/atom/target = controller.blackboard[target_key] - - if (QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/turf/next_step = get_step_towards(basic_mob, target) - var/dir_to_next_step = get_dir(basic_mob, next_step) - // If moving diagonally we need to punch both ways, or more accurately the one we are blocked in - var/list/dirs_to_move = list() - if (ISDIAGONALDIR(dir_to_next_step)) - for(var/direction in GLOB.cardinals) - if(direction & dir_to_next_step) - dirs_to_move += direction - else - dirs_to_move += dir_to_next_step - - for (var/direction in dirs_to_move) - if (attack_in_direction(controller, basic_mob, direction)) - return AI_BEHAVIOR_DELAY - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/attack_obstructions/proc/attack_in_direction(datum/ai_controller/controller, mob/living/basic/basic_mob, direction) - var/turf/next_step = get_step(basic_mob, direction) - if (!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn)) - return FALSE - - for (var/obj/object as anything in next_step.contents) - if (!can_smash_object(basic_mob, object)) - continue - basic_mob.melee_attack(object) - return TRUE - - if (can_attack_turfs) - basic_mob.melee_attack(next_step) - return TRUE - return FALSE - -/datum/ai_behavior/attack_obstructions/proc/can_smash_object(mob/living/basic/basic_mob, obj/object) - if (!object.density && !can_attack_dense_objects) - return FALSE - if (object.IsObscured()) - return FALSE - if (basic_mob.see_invisible < object.invisibility) - return FALSE - var/list/whitelist = basic_mob.ai_controller.blackboard[BB_OBSTACLE_TARGETING_WHITELIST] - if(whitelist && !is_type_in_typecache(object, whitelist)) - return FALSE - - return TRUE // It's in our way, let's get it out of our way - -/datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - -/datum/ai_planning_subtree/attack_obstacle_in_path/pet_target - target_key = BB_CURRENT_PET_TARGET diff --git a/code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm b/code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm deleted file mode 100644 index 46886680d528..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm +++ /dev/null @@ -1,68 +0,0 @@ -/// Calls all nearby mobs that share a faction to give backup in combat -/datum/ai_planning_subtree/call_reinforcements - /// Blackboard key containing something to say when calling reinforcements (takes precedence over emotes) - var/say_key = BB_REINFORCEMENTS_SAY - /// Blackboard key containing an emote to perform when calling reinforcements - var/emote_key = BB_REINFORCEMENTS_EMOTE - /// Reinforcement-calling behavior to use - var/call_type = /datum/ai_behavior/call_reinforcements - -/datum/ai_planning_subtree/call_reinforcements/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if (!decide_to_call(controller) || controller.blackboard[BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN] > world.time) - return - - var/call_say = controller.blackboard[BB_REINFORCEMENTS_SAY] - var/call_emote = controller.blackboard[BB_REINFORCEMENTS_EMOTE] - - if(!isnull(call_say)) - controller.queue_behavior(/datum/ai_behavior/perform_speech, call_say) - else if(!isnull(call_emote)) - controller.queue_behavior(/datum/ai_behavior/perform_emote, call_emote) - - controller.queue_behavior(call_type) - -/// Decides when to call reinforcements, can be overridden for alternate behavior -/datum/ai_planning_subtree/call_reinforcements/proc/decide_to_call(datum/ai_controller/controller) - return controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) && istype(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET], /mob) - -/datum/ai_planning_subtree/call_reinforcements/mining - call_type = /datum/ai_behavior/call_reinforcements/mining - -/// Call out to all mobs in the specified range for help -/datum/ai_behavior/call_reinforcements - /// How frequently can we call for reinforcements? - var/cooldown = 30 SECONDS - /// Range to call reinforcements from - var/reinforcements_range = 15 - -/datum/ai_behavior/call_reinforcements/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/pawn_mob = controller.pawn - for(var/mob/other_mob in oview(reinforcements_range, pawn_mob)) - if(!pawn_mob.faction_check_atom(other_mob) || isnull(other_mob.ai_controller)) - continue - // Add our current target to their retaliate list so that they'll attack our aggressor - other_mob.ai_controller.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET], world.time) - other_mob.ai_controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENT_TARGET, pawn_mob) - - controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN, world.time + cooldown) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/// Does not force retaliation, but increases targeting priority instead -/datum/ai_behavior/call_reinforcements/mining - cooldown = 1 SECONDS - reinforcements_range = 7 - -/datum/ai_behavior/call_reinforcements/mining/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/pawn_mob = controller.pawn - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - for(var/mob/other_mob in oview(reinforcements_range, pawn_mob)) - if(!pawn_mob.faction_check_atom(other_mob) || isnull(other_mob.ai_controller)) - continue - var/list/existing_requests = other_mob.ai_controller.blackboard[BB_MINING_MOB_REINFORCEMENTS_REQUESTS] - if (!existing_requests || !existing_requests[target]) - other_mob.ai_controller.set_blackboard_key_assoc_lazylist(BB_MINING_MOB_REINFORCEMENTS_REQUESTS, target, list()) - other_mob.ai_controller.add_blackboard_key_assoc(BB_MINING_MOB_REINFORCEMENTS_REQUESTS, target, world.time) - - controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN, world.time + cooldown) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm index 211e01be1f28..54578a21d385 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm @@ -1,65 +1,59 @@ -/// Add or remove people to our retaliation shitlist just on an arbitrary whim -/datum/ai_planning_subtree/capricious_retaliate - /// Blackboard key which tells us how to select valid targets - var/targeting_strategy_key = BB_TARGETING_STRATEGY - /// Whether we should skip checking faction for our decision - var/ignore_faction = TRUE +///Random chance to add things to our retaliate list +/datum/bt_node/ai_behavior/capricious_retaliate + var/targeting_strategy = BB_TARGETING_STRATEGY + var/ignore_faction + time_between_perform = 1 SECONDS -/datum/ai_planning_subtree/capricious_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(/datum/ai_behavior/capricious_retaliate, targeting_strategy_key, ignore_faction) - -/// Add or remove people to our retaliation shitlist just on an arbitrary whim -/datum/ai_behavior/capricious_retaliate - action_cooldown = 1 SECONDS - -/datum/ai_behavior/capricious_retaliate/perform(seconds_per_tick, datum/ai_controller/controller, targeting_strategy_key, ignore_faction) +/datum/bt_node/ai_behavior/capricious_retaliate/perform(seconds_per_tick, datum/ai_controller/controller) var/atom/pawn = controller.pawn - if (controller.blackboard_key_exists(BB_BASIC_MOB_RETALIATE_LIST)) + + if(controller.blackboard_key_exists(BB_BASIC_MOB_RETALIATE_LIST)) var/deaggro_chance = controller.blackboard[BB_RANDOM_DEAGGRO_CHANCE] || 10 - if (!SPT_PROB(deaggro_chance, seconds_per_tick)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - pawn.visible_message(span_notice("[pawn] calms down.")) // We can blackboard key this if anyone else actually wants to customise it - controller.clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST) - controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) - controller.CancelActions() // Otherwise they will try and get one last kick in - return AI_BEHAVIOR_DELAY + if(prob(deaggro_chance)) //Chance to chill the fuck out. This prob() should be matched with the frequency of calling. + pawn.visible_message(span_notice("[pawn] calms down.")) + controller.clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST) + controller.clear_blackboard_key(BB_CURRENT_TARGET) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED // De-aggroed var/aggro_chance = controller.blackboard[BB_RANDOM_AGGRO_CHANCE] || 0.5 - if (!SPT_PROB(aggro_chance, seconds_per_tick)) + if(!prob(aggro_chance)) //Check if we should get pissed at someone REEE return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/aggro_range = controller.blackboard[BB_AGGRO_RANGE] || 9 var/list/potential_targets = hearers(aggro_range, get_turf(pawn)) - pawn - if (!length(potential_targets)) + if(!length(potential_targets)) failed_targeting(pawn) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/datum/targeting_strategy/target_helper = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + if(!ispath(targeting_strategy)) + targeting_strategy = controller.blackboard[targeting_strategy] - var/mob/living/final_target = null - if (ignore_faction) + var/datum/targeting_strategy/target_helper = GET_TARGETING_STRATEGY(targeting_strategy) + + if(ignore_faction) controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) - while (isnull(final_target) && length(potential_targets)) + + var/mob/living/final_target = null + while(isnull(final_target) && length(potential_targets)) var/mob/living/test_target = pick_n_take(potential_targets) - if (target_helper.can_attack(pawn, test_target, vision_range = aggro_range)) + if(target_helper.is_valid_target(pawn, test_target, vision_range = aggro_range)) final_target = test_target - if (isnull(final_target)) + if(isnull(final_target)) failed_targeting(pawn) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + // Add to shitlist set_blackboard_key_assoc_lazylist calls post_blackboard_key_set, waking the combat branch controller.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, final_target, world.time) pawn.visible_message(span_warning("[pawn] glares grumpily at [final_target]!")) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/// Called if we try but fail to target something -/datum/ai_behavior/capricious_retaliate/proc/failed_targeting(atom/pawn) - pawn.visible_message(span_notice("[pawn] grumbles.")) // We're pissed off but with no outlet to vent our frustration upon +/datum/bt_node/ai_behavior/capricious_retaliate/proc/failed_targeting(atom/pawn) + pawn.visible_message(span_notice("[pawn] grumbles.")) -/datum/ai_behavior/capricious_retaliate/finish_action(datum/ai_controller/controller, succeeded, ignore_faction) +/datum/bt_node/ai_behavior/capricious_retaliate/finish_action(datum/ai_controller/controller, succeeded) . = ..() - if (succeeded || !ignore_faction) + if(succeeded || !ignore_faction) return var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm b/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm deleted file mode 100644 index bad349030f1b..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm +++ /dev/null @@ -1,15 +0,0 @@ -/datum/ai_planning_subtree/climb_trees - operational_datums = list(/datum/component/tree_climber) - ///chance to climb a tree - var/climb_chance = 35 - -/datum/ai_planning_subtree/climb_trees/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - if(!SPT_PROB(climb_chance, seconds_per_tick)) - return - - if(controller.blackboard_key_exists(BB_CLIMBED_TREE)) - controller.queue_behavior(/datum/ai_behavior/climb_tree, BB_CLIMBED_TREE) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_tree, BB_CLIMBED_TREE, /obj/structure/flora/tree) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm b/code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm deleted file mode 100644 index 3bdb1ab8f588..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm +++ /dev/null @@ -1,60 +0,0 @@ - -///simple behavior to make mobs randomly drag things around -/datum/ai_planning_subtree/steal_items/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.pulling) - if(prob(controller.blackboard[BB_GUILTY_CONSCIOUS_CHANCE])) - controller.queue_behavior(/datum/ai_behavior/stop_dragging) - return - if(!prob(controller.blackboard[BB_STEAL_CHANCE])) - return - if(!controller.blackboard_key_exists(BB_ITEM_TO_STEAL)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/find_stealable, BB_ITEM_TO_STEAL, /obj/item) - return - controller.queue_behavior(/datum/ai_behavior/drag_target, BB_ITEM_TO_STEAL) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/find_and_set/find_stealable - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = 2 MINUTES - -/datum/ai_behavior/find_and_set/find_stealable/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - - var/list/possible_items = shuffle_inplace(oview(search_range, controller.pawn)) - for(var/obj/item/possible_item in possible_items) - if(possible_item.pulledby || possible_item.anchored) - continue - if(can_see(living_pawn, possible_item)) - return possible_item - - -/datum/ai_behavior/stop_dragging - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/stop_dragging/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - living_pawn.stop_pulling() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/drag_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/drag_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/drag_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/movable/target = controller.blackboard[target_key] - if(QDELETED(target) || target.anchored || target.pulledby) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/our_mob = controller.pawn - our_mob.start_pulling(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/drag_target/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/enrage.dm b/code/datums/ai/basic_mobs/basic_subtrees/enrage.dm index cf3a922a5a44..8523adb9ef5f 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/enrage.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/enrage.dm @@ -1,44 +1,33 @@ -// Performs the enrage behavior when health is below given threshold, and calm down behavior if above that value afterwards -/datum/ai_planning_subtree/enrage +/// Halves the basic mob's melee attack cooldown while its health is at or below a threshold, and restores it once recovered. +/datum/bt_node/ai_behavior/enrage + /// Fraction of max health at or below which the mob becomes enraged. var/health_threshold = 0.5 - var/enrage_behavior = /datum/ai_behavior/enrage -/datum/ai_planning_subtree/enrage/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) +/datum/bt_node/ai_behavior/enrage/perform(seconds_per_tick, datum/ai_controller/controller) if(!isbasicmob(controller.pawn)) - return + return AI_BEHAVIOR_FAILED + var/mob/living/basic/basic_pawn = controller.pawn var/low_health = (basic_pawn.health / basic_pawn.maxHealth) <= health_threshold - var/is_enraged = controller.blackboard_key_exists(BB_BASIC_MOB_ENRAGE) - if(low_health && !is_enraged) - controller.queue_behavior(enrage_behavior, FALSE) - else if(!low_health && is_enraged) - controller.queue_behavior(enrage_behavior, TRUE) + if(low_health && !is_enraged) + var/current_cooldown = basic_pawn.melee_attack_cooldown + controller.set_blackboard_key(BB_BASIC_MOB_ENRAGE, TRUE) + controller.set_blackboard_key(BB_BASIC_MOB_PREVIOUS_MELEE_COOLDOWN, current_cooldown) + basic_pawn.melee_attack_cooldown = current_cooldown / 2 + + if(controller.blackboard_key_exists(BB_CURRENT_TARGET)) + basic_pawn.visible_message(span_danger("\The [basic_pawn] gets an enraged look at [controller.blackboard[BB_CURRENT_TARGET]]!")) + else + basic_pawn.visible_message(span_danger("\The [basic_pawn] gets an enraged look!")) + return AI_BEHAVIOR_SUCCEEDED -/// Cuts down basic mob's melee attack cooldown in half -/datum/ai_behavior/enrage - -/datum/ai_behavior/enrage/perform(seconds_per_tick, datum/ai_controller/controller, calm_down) - var/mob/living/basic/basic_pawn = controller.pawn - if(calm_down) - var/previous_delay = controller.blackboard[BB_BASIC_MOB_PREVIOUS_MELEE_COOLDOWN] + if(!low_health && is_enraged) // Technically something else could have modified the cooldown before/after but that requires further consideration so don't use this behavior in these scenarios - basic_pawn.melee_attack_cooldown = previous_delay + basic_pawn.melee_attack_cooldown = controller.blackboard[BB_BASIC_MOB_PREVIOUS_MELEE_COOLDOWN] controller.clear_blackboard_key(BB_BASIC_MOB_PREVIOUS_MELEE_COOLDOWN) controller.clear_blackboard_key(BB_BASIC_MOB_ENRAGE) return AI_BEHAVIOR_SUCCEEDED - var/current_cooldown = basic_pawn.melee_attack_cooldown - var/new_attack_cooldown = current_cooldown / 2 - - controller.set_blackboard_key(BB_BASIC_MOB_ENRAGE, TRUE) - controller.set_blackboard_key(BB_BASIC_MOB_PREVIOUS_MELEE_COOLDOWN, current_cooldown) - basic_pawn.melee_attack_cooldown = new_attack_cooldown - - if(controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - var/current_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - controller.pawn.visible_message(span_danger("\The [controller.pawn] gets an enraged look at [current_target]!")) - else - controller.pawn.visible_message(span_danger("\The [controller.pawn] gets an enraged look!")) - return AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_FAILED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.json new file mode 100644 index 000000000000..9bdb05c4b056 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.json @@ -0,0 +1,82 @@ +{ + "dm_type": "/datum/bt_node/subtree/escape_captivity", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/buckle_target_dangerous", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_out_of_object/from_bb", + "vars": { + "target_key": "BB_BASIC_MOB_ESCAPE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_contained_in_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/container_attackable", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_out_of_object/from_bb", + "vars": { + "target_key": "BB_BASIC_MOB_ESCAPE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.dm b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.dm index 759a5c69eb85..eeb05455c1ba 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.dm @@ -1,70 +1,7 @@ -/// Generically try to escape from being trapped -/datum/ai_planning_subtree/escape_captivity - /// Targeting strategy for use deciding if we can attack a mob grabbing us - var/targeting_strategy_key = BB_TARGETING_STRATEGY - /// If true we will never attack objects - var/pacifist = FALSE +///Tries to escape activity, has observers to cancel if needed +/datum/bt_node/subtree/escape_captivity + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.json" -/datum/ai_planning_subtree/escape_captivity/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - - if (isobj(living_pawn.buckled)) - // we can just stand up we don't need to freak out - if (pacifist || !HAS_TRAIT(living_pawn.buckled, TRAIT_DANGEROUS_BUCKLE)) - controller.queue_behavior(/datum/ai_behavior/resist) - // otherwise beat the shit out of we we gotta get out NOW - else - controller.queue_behavior(/datum/ai_behavior/break_out_of_object, living_pawn.buckled) - return SUBTREE_RETURN_FINISH_PLANNING - - if (!isturf(living_pawn.loc) && !ismob(living_pawn.loc) && !istype(living_pawn.loc, /obj/item/mob_holder)) - var/atom/contained_in = living_pawn.loc - var/attack_effective = FALSE - if (!pacifist) - if (isbasicmob(living_pawn)) // Currently this literally only works for basic mobs because it's hard to check for anyone else but it's ok because only they use this subtree - var/mob/living/basic/basic_pawn = living_pawn - attack_effective = basic_pawn.obj_damage > contained_in.damage_deflection - if (attack_effective) - controller.queue_behavior(/datum/ai_behavior/break_out_of_object, contained_in) - else - controller.queue_behavior(/datum/ai_behavior/resist) - return SUBTREE_RETURN_FINISH_PLANNING - - var/mob/puller = living_pawn.pulledby - if (puller && puller.grab_state > GRAB_PASSIVE) - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - var/friends_list = controller.blackboard[BB_FRIENDS_LIST] || list() - // Only resist grabs from mobs that aren't in our faction - if (targeting_strategy?.can_attack(living_pawn, puller) && !(puller in friends_list)) - controller.queue_behavior(/datum/ai_behavior/resist) - return SUBTREE_RETURN_FINISH_PLANNING - - if (HAS_TRAIT(living_pawn, TRAIT_RESTRAINED)) - controller.queue_behavior(/datum/ai_behavior/resist) - return SUBTREE_RETURN_FINISH_PLANNING - -/// Keep attacking an object while it is our loc or while we are buckled to it -/datum/ai_behavior/break_out_of_object - action_cooldown = 0.2 SECONDS - -/datum/ai_behavior/break_out_of_object/setup(datum/ai_controller/controller, atom/target) - if (!should_attack_target(controller, target)) - return FALSE - return TRUE - -/datum/ai_behavior/break_out_of_object/perform(seconds_per_tick, datum/ai_controller/controller, atom/target_atom) - if (!should_attack_target(controller, target_atom)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - controller.ai_interact(target = target_atom, combat_mode = TRUE) - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/break_out_of_object/proc/should_attack_target(datum/ai_controller/controller, atom/target) - if (QDELETED(target)) - return FALSE - var/mob/living/pawn = controller.pawn - if (!target.IsReachableBy(pawn)) - return FALSE - return pawn.loc == target || pawn.buckled == target - -/datum/ai_planning_subtree/escape_captivity/pacifist - pacifist = TRUE +/// Pacifist variant: never attacks objects, only resists. +/datum/bt_node/subtree/escape_captivity/pacifist + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.json new file mode 100644 index 000000000000..59684c19d86d --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.json @@ -0,0 +1,50 @@ +{ + "dm_type": "/datum/bt_node/subtree/escape_captivity/pacifist", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_contained_in_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm b/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm deleted file mode 100644 index 4d7a3e7ad306..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm +++ /dev/null @@ -1,44 +0,0 @@ -#define HIGH_HAPPINESS_THRESHOLD 0.7 -#define MODERATE_HAPPINESS_THRESHOLD 0.5 - -/datum/ai_planning_subtree/express_happiness - operational_datums = list(/datum/component/happiness) - ///the key storing our happiness value - var/happiness_key = BB_BASIC_HAPPINESS - ///list of emotions we relay when happy - var/static/list/happy_emotions = list( - "celebrates happily!", - "dances around in excitement!", - ) - ///our moderate emotions - var/static/list/moderate_emotions = list( - "looks satisfied.", - "trots around.", - ) - ///emotions we display when we are sad - var/static/list/depressed_emotions = list( - "looks depressed...", - "turns its back and sulks...", - "looks towards the floor in dissapointment...", - ) - -/datum/ai_planning_subtree/express_happiness/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(5, seconds_per_tick)) - return - var/happiness_value = controller.blackboard[happiness_key] - if(isnull(happiness_value)) - return - var/list/final_list - switch(happiness_value) - if(HIGH_HAPPINESS_THRESHOLD to INFINITY) - final_list = controller.blackboard[BB_HAPPY_EMOTIONS] || happy_emotions - if(MODERATE_HAPPINESS_THRESHOLD to HIGH_HAPPINESS_THRESHOLD) - final_list = controller.blackboard[BB_MODERATE_EMOTIONS] || moderate_emotions - else - final_list = controller.blackboard[BB_SAD_EMOTIONS] || depressed_emotions - if(!length(final_list)) - return - controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(final_list)) - -#undef HIGH_HAPPINESS_THRESHOLD -#undef MODERATE_HAPPINESS_THRESHOLD diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm deleted file mode 100644 index 9b3e2883290e..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm +++ /dev/null @@ -1,43 +0,0 @@ -/// similar to finding a target but looks for food types in the // the what? -/datum/ai_planning_subtree/find_food - ///behavior we use to find the food - var/datum/ai_behavior/finding_behavior = /datum/ai_behavior/find_and_set/in_list - ///key of foods list - var/food_list_key = BB_BASIC_FOODS - ///key where we store our food - var/found_food_key = BB_TARGET_FOOD - ///key holding any emotes we play after eating food - var/emotes_blackboard_list = BB_EAT_EMOTES - ///key where we store our search range - var/search_range = BB_SEARCH_RANGE - -/datum/ai_planning_subtree/find_food/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/list/foods_list = controller.blackboard[food_list_key] - if(!length(foods_list)) - CRASH("the types of food has not been supplied in the [food_list_key] key!") - if(controller.blackboard[BB_NEXT_FOOD_EAT] > world.time) - return - - if(!controller.blackboard_key_exists(found_food_key)) - controller.queue_behavior(finding_behavior, found_food_key, foods_list, controller.blackboard[BB_SEARCH_RANGE]) - return - - controller.queue_behavior(/datum/ai_behavior/interact_with_target/eat_food, found_food_key, emotes_blackboard_list) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/interact_with_target/eat_food - ///default list of actions we take after eating - var/list/food_actions = list( - "eats up happily!", - "chomps with glee!", - ) - -/datum/ai_behavior/interact_with_target/eat_food/perform(seconds_per_tick, datum/ai_controller/controller, target_key, emotes_blackboard_list) - . = ..() - if(. & AI_BEHAVIOR_FAILED) - return - var/list/emotes_to_pick = controller.blackboard[emotes_blackboard_list] || food_actions - if(!length(emotes_to_pick)) - return - var/mob/living/living_pawn = controller.pawn - living_pawn.manual_emote(pick(emotes_to_pick)) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.json new file mode 100644 index 000000000000..d6b902983f93 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.json @@ -0,0 +1,73 @@ +{ + "dm_type": "/datum/bt_node/subtree/find_paper_and_write", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/write_on_paper", + "vars": { + "paper_key": "BB_SIMPLE_CARRY_ITEM", + "writing_list_key": "BB_WRITING_LIST" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_FOUND_PAPER" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "time_between_perform": "0.4 SECONDS", + "target_key": "BB_FOUND_PAPER", + "can_attack_turfs": true, + "can_attack_dense_objects": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_PAPER", + "required_dist": 0 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up_item_virtual", + "vars": { + "target_key": "BB_FOUND_PAPER", + "storage_key": "BB_SIMPLE_CARRY_ITEM" + } + } + ] + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm index 0d2d0c3e3b44..947fbec7b8d9 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm @@ -1,22 +1,3 @@ -/datum/ai_planning_subtree/find_paper_and_write - -/datum/ai_planning_subtree/find_paper_and_write/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/wizard = controller.pawn - - if(controller.blackboard_key_exists(BB_SIMPLE_CARRY_ITEM)) - controller.queue_behavior(/datum/ai_behavior/write_on_paper, BB_SIMPLE_CARRY_ITEM, BB_WRITING_LIST) - return SUBTREE_RETURN_FINISH_PLANNING - - var/obj/item/paper/target = controller.blackboard[BB_FOUND_PAPER] - - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/empty_paper, BB_FOUND_PAPER, /obj/item/paper) - return - - if(get_turf(wizard) != get_turf(target)) - controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_FOUND_PAPER) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!(target in wizard.contents)) - controller.queue_behavior(/datum/ai_behavior/pick_up_item, BB_FOUND_PAPER, BB_SIMPLE_CARRY_ITEM) - return SUBTREE_RETURN_FINISH_PLANNING +/// Idle behaviour: hunt down a nearby blank paper, fetch it, scrawl a threat on it and drop it. +/datum/bt_node/subtree/find_paper_and_write + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_parent.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_parent.dm deleted file mode 100644 index 7b35053ff386..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_parent.dm +++ /dev/null @@ -1,31 +0,0 @@ -/datum/ai_planning_subtree/look_for_adult - ///how far we must be from the mom - var/minimum_distance = 1 - -/datum/ai_planning_subtree/look_for_adult/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/target = controller.blackboard[BB_FOUND_MOM] - var/mob/baby = controller.pawn - - if(QDELETED(target)) - find_mom(controller) - return - - if(get_dist(target, baby) > minimum_distance) - controller.queue_behavior(/datum/ai_behavior/travel_towards/stop_on_arrival, BB_FOUND_MOM) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!SPT_PROB(15, seconds_per_tick)) - return - - if(target.stat == DEAD) - controller.queue_behavior(/datum/ai_behavior/perform_emote, "cries for their parent!") - else - controller.queue_behavior(/datum/ai_behavior/perform_emote, "dances around their parent!") - - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_planning_subtree/look_for_adult/proc/find_mom(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/find_mom, BB_FIND_MOM_TYPES, BB_IGNORE_MOM_TYPES, BB_FOUND_MOM) - -/datum/ai_planning_subtree/look_for_adult/raptor/find_mom(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/find_mom/raptor, BB_FIND_MOM_TYPES, BB_FOUND_MOM) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_targets_prioritize_traits.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_targets_prioritize_traits.dm deleted file mode 100644 index 6c83469960ad..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_targets_prioritize_traits.dm +++ /dev/null @@ -1,6 +0,0 @@ -/// Find something with a specific trait to run from -/datum/ai_planning_subtree/find_target_prioritize_traits - -/datum/ai_planning_subtree/find_target_prioritize_traits/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/prioritize_trait, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION, BB_TARGET_PRIORITY_TRAIT) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/fishing.dm b/code/datums/ai/basic_mobs/basic_subtrees/fishing.dm deleted file mode 100644 index e17f50740fa6..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/fishing.dm +++ /dev/null @@ -1,44 +0,0 @@ -#define FISHING_COOLDOWN 45 SECONDS - -///subtree for fishing and eating food! -/datum/ai_planning_subtree/fish - ///behavior we use to find fishable objects - var/datum/ai_behavior/find_fishable_behavior = /datum/ai_behavior/find_and_set/in_list - ///behavior we use to fish! - var/datum/ai_behavior/fishing_behavior = /datum/ai_behavior/interact_with_target/fishing - ///blackboard key storing things we can fish from - var/fishable_list_key = BB_FISHABLE_LIST - ///key where we store found fishable items - var/fishing_target_key = BB_FISHING_TARGET - ///key where we store our fishing cooldown - var/fishing_cooldown_key = BB_FISHING_COOLDOWN - ///our fishing range - var/fishing_range = 5 - -/datum/ai_planning_subtree/fish/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_ONLY_FISH_WHILE_HUNGRY] && controller.blackboard[BB_NEXT_FOOD_EAT] > world.time) - return - if(controller.blackboard[BB_FISHING_TIMER] > world.time) - return - if(!controller.blackboard_key_exists(fishing_target_key)) - controller.queue_behavior(find_fishable_behavior, fishing_target_key, controller.blackboard[fishable_list_key], fishing_range) - return - controller.queue_behavior(/datum/ai_behavior/interact_with_target/fishing, fishing_target_key, fishing_cooldown_key) - return SUBTREE_RETURN_FINISH_PLANNING - -///less expensive fishing behavior! -/datum/ai_planning_subtree/fish/fish_from_turfs - find_fishable_behavior = /datum/ai_behavior/find_and_set/in_list/closest_turf - -/datum/ai_behavior/interact_with_target/fishing - clear_target = FALSE - combat_mode = FALSE - -/datum/ai_behavior/interact_with_target/fishing/finish_action(datum/ai_controller/controller, succeeded, fishing_target_key, fishing_cooldown_key) - . = ..() - if(!succeeded) - return - var/cooldown = controller.blackboard[fishing_cooldown_key] || FISHING_COOLDOWN - controller.set_blackboard_key(BB_FISHING_TIMER, world.time + cooldown) - -#undef FISHING_COOLDOWN diff --git a/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm deleted file mode 100644 index 3ed8b2df2b26..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm +++ /dev/null @@ -1,39 +0,0 @@ -/// Try to escape from your current target, without performing any other actions. -/datum/ai_planning_subtree/flee_target - /// Behaviour to execute in order to flee - var/flee_behaviour = /datum/ai_behavior/run_away_from_target - /// Blackboard key in which to store selected target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// Blackboard key in which to store selected target's hiding place - var/hiding_place_key = BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION - -/datum/ai_planning_subtree/flee_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/atom/flee_from = controller.blackboard[target_key] - if(!should_flee(controller, flee_from)) - return - var/flee_distance = controller.blackboard[BB_BASIC_MOB_FLEE_DISTANCE] || DEFAULT_BASIC_FLEE_DISTANCE - if (get_dist(controller.pawn, flee_from) >= flee_distance) - return - - controller.queue_behavior(flee_behaviour, target_key, hiding_place_key) - return SUBTREE_RETURN_FINISH_PLANNING //we gotta get out of here. - -/datum/ai_planning_subtree/flee_target/proc/should_flee(datum/ai_controller/controller, atom/flee_from) - if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING] || QDELETED(flee_from)) - return FALSE - return TRUE - -/// Try to escape from your current target, without performing any other actions. -/// Reads from some fleeing-specific targeting keys rather than the current mob target. -/datum/ai_planning_subtree/flee_target/from_flee_key - target_key = BB_BASIC_MOB_FLEE_TARGET - hiding_place_key = BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION - -/// A subtype that forces the mob to flee from targets with the scary fisherman trait anyway. -/datum/ai_planning_subtree/flee_target/from_fisherman - -/datum/ai_planning_subtree/flee_target/from_fisherman/should_flee(datum/ai_controller/controller, atom/flee_from) - if (!QDELETED(flee_from) && HAS_TRAIT(flee_from, TRAIT_SCARY_FISHERMAN)) - return TRUE - return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/generic_hunger.dm b/code/datums/ai/basic_mobs/basic_subtrees/generic_hunger.dm new file mode 100644 index 000000000000..38ceaf78659a --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/generic_hunger.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/generic_hunger + behavior_tree_json = "code/datums/ai/generic_hunger.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/generic_play_instrument.dm b/code/datums/ai/basic_mobs/basic_subtrees/generic_play_instrument.dm new file mode 100644 index 000000000000..7b7e3d7a8ee6 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/generic_play_instrument.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/generic_play_instrument + behavior_tree_json = "code/datums/ai/generic_play_instrument.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.json new file mode 100644 index 000000000000..1524aad1a2d5 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.json @@ -0,0 +1,70 @@ +{ + "dm_type": "/datum/bt_node/subtree/go_for_swim", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SWIM_ALTERNATE_TURF" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SWIM_ALTERNATE_TURF", + "required_dist": 0, + "finish_on_arrival": true + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_KEY_SWIMMER_COOLDOWN", + "cooldown_duration": "30 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": "30 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SWIM_ALTERNATE_TURF", + "targeting_strategy": "/datum/targeting_strategy/walkable_turf", + "target_source": "/datum/target_source/oview_land_turfs" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SWIM_ALTERNATE_TURF", + "required_dist": 0, + "finish_on_arrival": true + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/swim_splash" + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.dm b/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.dm index ba50023567fe..89eb9829dcb1 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.dm @@ -1,59 +1,15 @@ -#define DEFAULT_TIME_SWIMMER 30 SECONDS +/// Wander between water and land, splashing about now and then. +/datum/bt_node/subtree/go_for_swim + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.json" -///subtree to go and swim! -/datum/ai_planning_subtree/go_for_swim +/// Splashes about while standing in water. +/datum/bt_node/ai_behavior/swim_splash -/datum/ai_planning_subtree/go_for_swim/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_SWIM_ALTERNATE_TURF)) - controller.queue_behavior(/datum/ai_behavior/travel_towards/swimming, BB_SWIM_ALTERNATE_TURF) - - if(isnull(controller.blackboard[BB_KEY_SWIM_TIME])) - controller.set_blackboard_key(BB_KEY_SWIM_TIME, DEFAULT_TIME_SWIMMER) - - var/mob/living/living_pawn = controller.pawn - var/turf/our_turf = get_turf(living_pawn) - - // we have been taken out of water! - controller.set_blackboard_key(BB_CURRENTLY_SWIMMING, iswaterturf(our_turf)) - - if(controller.blackboard[BB_KEY_SWIM_TIME] < world.time) - controller.queue_behavior(/datum/ai_behavior/find_and_set/swim_alternate, BB_SWIM_ALTERNATE_TURF, /turf/open) - return - - // have some fun in the water - if(controller.blackboard[BB_CURRENTLY_SWIMMING] && SPT_PROB(5, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/perform_emote, "splashes water all around!") - - -///find land if its time to get out of water, otherwise find water -/datum/ai_behavior/find_and_set/swim_alternate - -/datum/ai_behavior/find_and_set/swim_alternate/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) +/datum/bt_node/ai_behavior/swim_splash/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn - if(QDELETED(living_pawn)) - return null - var/look_for_land = controller.blackboard[BB_CURRENTLY_SWIMMING] - var/list/possible_turfs = list() - for(var/turf/possible_turf in oview(search_range, living_pawn)) - if(isclosedturf(possible_turf) || is_space_or_openspace(possible_turf)) - continue - if(possible_turf.is_blocked_turf()) - continue - if(look_for_land == iswaterturf(possible_turf)) - continue - possible_turfs += possible_turf - - if(!length(possible_turfs)) - return null - - return(pick(possible_turfs)) - -/datum/ai_behavior/travel_towards/swimming - clear_target = TRUE - -/datum/ai_behavior/travel_towards/swimming/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - var/time_to_add = controller.blackboard[BB_KEY_SWIMMER_COOLDOWN] ? controller.blackboard[BB_KEY_SWIMMER_COOLDOWN] : DEFAULT_TIME_SWIMMER - controller.set_blackboard_key(BB_KEY_SWIM_TIME, world.time + time_to_add ) - -#undef DEFAULT_TIME_SWIMMER + if(!istype(living_pawn) || !iswaterturf(get_turf(living_pawn))) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!SPT_PROB(5, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + living_pawn.manual_emote("splashes water all around!") + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm b/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm deleted file mode 100644 index 8b8b0d0bf6fc..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm +++ /dev/null @@ -1,119 +0,0 @@ -/// Step away if too close, or towards if too far -/datum/ai_planning_subtree/maintain_distance - /// Blackboard key holding atom we want to stay away from - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// How far do we look for our target? - var/view_distance = 10 - /// the run away behavior we will use - var/run_away_behavior = /datum/ai_behavior/step_away - -/datum/ai_planning_subtree/maintain_distance/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - - var/mob/living/living_pawn = controller.pawn - if(LAZYLEN(living_pawn.do_afters)) - return - - var/atom/target = controller.blackboard[target_key] - if (!isliving(target) || !can_see(controller.pawn, target, view_distance)) - return // Don't run away from cucumbers, they're not snakes - var/range = get_dist(controller.pawn, target) - - var/minimum_distance = controller.blackboard[BB_RANGED_SKIRMISH_MIN_DISTANCE] || 4 - var/maximum_distance = controller.blackboard[BB_RANGED_SKIRMISH_MAX_DISTANCE] || 6 - - if (range < minimum_distance) - controller.queue_behavior(run_away_behavior, target_key, minimum_distance) - return - if (range > maximum_distance) - controller.queue_behavior(/datum/ai_behavior/pursue_to_range, target_key, maximum_distance) - return - -/datum/ai_planning_subtree/maintain_distance/cover_minimum_distance - run_away_behavior = /datum/ai_behavior/cover_minimum_distance - -/// Take one step away -/datum/ai_behavior/step_away - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 0 - action_cooldown = 0.2 SECONDS - -/datum/ai_behavior/step_away/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/current_target = controller.blackboard[target_key] - if (QDELETED(current_target)) - return FALSE - - var/mob/living/our_pawn = controller.pawn - our_pawn.face_atom(current_target) - - var/turf/next_step = get_step_away(controller.pawn, current_target) - if (!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) - set_movement_target(controller, target = next_step, new_movement = /datum/ai_movement/basic_avoidance/backstep) - return TRUE - - var/list/all_dirs = GLOB.alldirs.Copy() - all_dirs -= get_dir(controller.pawn, next_step) - all_dirs -= get_dir(controller.pawn, current_target) - shuffle_inplace(all_dirs) - - for (var/dir in all_dirs) - next_step = get_step(controller.pawn, dir) - if (!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) - set_movement_target(controller, target = next_step, new_movement = /datum/ai_movement/basic_avoidance/backstep) - return TRUE - return FALSE - -/datum/ai_behavior/step_away/perform(seconds_per_tick, datum/ai_controller/controller) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/step_away/finish_action(datum/ai_controller/controller, succeeded) - . = ..() - controller.change_ai_movement_type(initial(controller.ai_movement)) - -/// Pursue a target until we are within a provided range -/datum/ai_behavior/pursue_to_range - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_MOVE_AND_PERFORM - -/datum/ai_behavior/pursue_to_range/setup(datum/ai_controller/controller, target_key, range) - . = ..() - var/atom/current_target = controller.blackboard[target_key] - if (QDELETED(current_target)) - return FALSE - if (get_dist(controller.pawn, current_target) <= range) - return FALSE - set_movement_target(controller, current_target) - -/datum/ai_behavior/pursue_to_range/perform(seconds_per_tick, datum/ai_controller/controller, target_key, range) - var/atom/current_target = controller.blackboard[target_key] - if (!QDELETED(current_target) && get_dist(controller.pawn, current_target) > range) - return AI_BEHAVIOR_INSTANT - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -///instead of taking a single step, we cover the entire distance -/datum/ai_behavior/cover_minimum_distance - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 0 - action_cooldown = 0.2 SECONDS - -/datum/ai_behavior/cover_minimum_distance/setup(datum/ai_controller/controller, target_key, minimum_distance) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - var/required_distance = minimum_distance - get_dist(controller.pawn, target) //the distance we need to move - var/distance = 0 - var/turf/chosen_turf - for(var/turf/open/potential_turf in oview(required_distance, controller.pawn)) - var/new_distance_from_target = get_dist(potential_turf, target) - if(potential_turf.is_blocked_turf()) - continue - if(new_distance_from_target > distance) - chosen_turf = potential_turf - distance = new_distance_from_target - if(isnull(chosen_turf)) - return FALSE - set_movement_target(controller, target = chosen_turf) - -/datum/ai_behavior/cover_minimum_distance/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm b/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm deleted file mode 100644 index 6edc631c7a15..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm +++ /dev/null @@ -1,68 +0,0 @@ -//behavior to find mineable mineral walls - -/datum/ai_planning_subtree/mine_walls - var/find_wall_behavior = /datum/ai_behavior/find_mineral_wall - -/datum/ai_planning_subtree/mine_walls/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_TARGET_MINERAL_WALL)) - controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_TARGET_MINERAL_WALL) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(find_wall_behavior, BB_TARGET_MINERAL_WALL) - -/datum/ai_behavior/mine_wall - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = 15 SECONDS - -/datum/ai_behavior/mine_wall/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/mine_wall/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/basic/living_pawn = controller.pawn - var/turf/closed/mineral/target = controller.blackboard[target_key] - var/is_gibtonite_turf = istype(target, /turf/closed/mineral/gibtonite) - if(!controller.ai_interact(target = target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(is_gibtonite_turf) - living_pawn.manual_emote("sighs...") //accept whats about to happen to us - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/mine_wall/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/find_mineral_wall - -/datum/ai_behavior/find_mineral_wall/perform(seconds_per_tick, datum/ai_controller/controller, found_wall_key) - var/mob/living_pawn = controller.pawn - - for(var/turf/closed/mineral/potential_wall in oview(9, living_pawn)) - if(!check_if_mineable(controller, potential_wall)) //check if its surrounded by walls - continue - controller.set_blackboard_key(found_wall_key, potential_wall) //closest wall first! - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/find_mineral_wall/proc/check_if_mineable(datum/ai_controller/controller, turf/target_wall) - var/mob/living/source = controller.pawn - var/direction_to_turf = get_dir(target_wall, source) - if(!ISDIAGONALDIR(direction_to_turf)) - return TRUE - var/list/directions_to_check = list() - for(var/direction_check in GLOB.cardinals) - if(direction_check & direction_to_turf) - directions_to_check += direction_check - - for(var/direction in directions_to_check) - var/turf/test_turf = get_step(target_wall, direction) - if(isnull(test_turf)) - continue - if(!test_turf.is_blocked_turf(ignore_atoms = list(source))) - return TRUE - return FALSE diff --git a/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm b/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm index bcf5c8290251..c36fe040fcff 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm @@ -1,69 +1,64 @@ -/// Try to line up with a cardinal direction of your target -/datum/ai_planning_subtree/move_to_cardinal - /// Behaviour to execute to line ourselves up - var/move_behaviour = /datum/ai_behavior/move_to_cardinal - /// Blackboard key in which to store selected target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - -/datum/ai_planning_subtree/move_to_cardinal/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(target_key)) - return - controller.queue_behavior(move_behaviour, target_key) - -/// Try to line up with a cardinal direction of your target -/datum/ai_behavior/move_to_cardinal - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// How close to our target is too close? +/// Moves to line up with the target along a cardinal direction, so a directional ability can fire down the lane. +/// Reports SUCCESS once lined up and within range, FAILURE when the target is gone, too far, or pathing gives up. +/datum/bt_node/ai_behavior/move_to_cardinal + time_between_perform = 0 + /// Blackboard key holding the atom to line up with. + var/target_key = BB_CURRENT_TARGET + /// How close to our target is too close. var/minimum_distance = 1 - /// How far away is too far? + /// How far away is too far. var/maximum_distance = 9 + /// The cardinal tile of our target we are currently moving toward. + var/atom/destination + /// Set by on_movement_failed() when the movement system gives up pathing. + var/movement_failed = FALSE -/datum/ai_behavior/move_to_cardinal/setup(datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/move_to_cardinal/setup(datum/ai_controller/controller) var/atom/target = controller.blackboard[target_key] if(QDELETED(target)) return FALSE - target_nearest_cardinal(controller, target) + RegisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED, PROC_REF(on_movement_failed)) + move_towards_nearest_cardinal(controller, target) return TRUE -/// Set our movement target to the closest cardinal space to our target -/datum/ai_behavior/move_to_cardinal/proc/target_nearest_cardinal(datum/ai_controller/controller, atom/target) +/datum/bt_node/ai_behavior/move_to_cardinal/proc/on_movement_failed(atom/source) + SIGNAL_HANDLER + movement_failed = TRUE + +/// Begin moving toward the closest unblocked cardinal tile of our target. +/datum/bt_node/ai_behavior/move_to_cardinal/proc/move_towards_nearest_cardinal(datum/ai_controller/controller, atom/target) var/atom/move_target var/closest = INFINITY - - for (var/dir in GLOB.cardinals) + for(var/dir in GLOB.cardinals) var/turf/cardinal_turf = get_ranged_target_turf(target, dir, minimum_distance) - if (cardinal_turf.is_blocked_turf()) + if(cardinal_turf.is_blocked_turf()) continue var/distance_to = get_dist(controller.pawn, cardinal_turf) - if (distance_to >= closest) + if(distance_to >= closest) continue closest = distance_to move_target = cardinal_turf - - if (isnull(move_target)) + if(isnull(move_target)) move_target = target - if (controller.current_movement_target == move_target) - return - set_movement_target(controller, move_target) + controller.ai_movement.start_moving_towards(controller, move_target, 0) + destination = move_target -/datum/ai_behavior/move_to_cardinal/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/move_to_cardinal/perform(seconds_per_tick, datum/ai_controller/controller) + if(movement_failed) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/atom/target = controller.blackboard[target_key] - if (QDELETED(target)) + if(QDELETED(target)) return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - if (!(get_dir(controller.pawn, target) in GLOB.cardinals)) - target_nearest_cardinal(controller, target) - return AI_BEHAVIOR_INSTANT var/distance_to_target = get_dist(controller.pawn, target) - if (distance_to_target < minimum_distance) - target_nearest_cardinal(controller, target) - return AI_BEHAVIOR_INSTANT - if (distance_to_target > maximum_distance) + if(distance_to_target > maximum_distance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!(get_dir(controller.pawn, target) in GLOB.cardinals) || distance_to_target < minimum_distance) + move_towards_nearest_cardinal(controller, target) return AI_BEHAVIOR_INSTANT return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/move_to_cardinal/finish_action(datum/ai_controller/controller, succeeded, target_key) - if (!succeeded) - controller.clear_blackboard_key(target_key) +/datum/bt_node/ai_behavior/move_to_cardinal/finish_action(datum/ai_controller/controller, succeeded) + UnregisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED) + movement_failed = FALSE + controller.ai_movement.stop_moving_towards(controller) return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm b/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm deleted file mode 100644 index 240272d1ef48..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm +++ /dev/null @@ -1,20 +0,0 @@ -/// Opportunistically searches for and hides/scurries through vents. -/datum/ai_planning_subtree/opportunistic_ventcrawler - -/datum/ai_planning_subtree/opportunistic_ventcrawler/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(HAS_TRAIT(controller.pawn, TRAIT_MOVE_VENTCRAWLING)) - return SUBTREE_RETURN_FINISH_PLANNING // hold on let me cook - - var/obj/machinery/atmospherics/components/unary/vent_pump/target = controller.blackboard[BB_ENTRY_VENT_TARGET] - - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_ENTRY_VENT_TARGET, /obj/machinery/atmospherics/components/unary/vent_pump) // keep looking otherwise they KILL US AND WE DIE - return - - if(get_turf(controller.pawn) != get_turf(target)) - controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_ENTRY_VENT_TARGET) - return - - controller.set_blackboard_key(BB_CURRENTLY_TARGETING_VENT, TRUE) - controller.queue_behavior(/datum/ai_behavior/crawl_through_vents, BB_ENTRY_VENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING // we are going into this vent... no distractions diff --git a/code/datums/ai/basic_mobs/basic_subtrees/play_with_owners.dm b/code/datums/ai/basic_mobs/basic_subtrees/play_with_owners.dm deleted file mode 100644 index f78574a110ad..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/play_with_owners.dm +++ /dev/null @@ -1,21 +0,0 @@ -/datum/ai_planning_subtree/find_and_hunt_target/play_with_owner - target_key = BB_OWNER_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/play_with_owner - finding_behavior = /datum/ai_behavior/find_hunt_target/find_owner - hunt_targets = list(/mob/living) - hunt_chance = 80 - hunt_range = 9 - -/datum/ai_behavior/find_hunt_target/find_owner - action_cooldown = 1 MINUTES - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_hunt_target/find_owner/valid_dinner(mob/living/source, atom/friend, radius, datum/ai_controller/controller, seconds_per_tick) - return (friend != source) && (source.has_ally(friend)) && can_see(source, friend, radius) - -/datum/ai_behavior/hunt_target/play_with_owner - -/datum/ai_behavior/hunt_target/play_with_owner/target_caught(mob/living/hunter, atom/hunted) - var/list/interactions_list = hunter.ai_controller.blackboard[BB_INTERACTIONS_WITH_OWNER] - var/interaction_message = length(interactions_list) ? pick(interactions_list) : "Plays with" - hunter.manual_emote("[interaction_message] [hunted]!") diff --git a/code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm b/code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm deleted file mode 100644 index 2718ca630ff7..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm +++ /dev/null @@ -1,23 +0,0 @@ - -///Subtree that checks if we are on the target atom's tile, and sets it as a travel target if not -///The target is taken from the blackboard. This one always requires a specific implementation. -/datum/ai_planning_subtree/prepare_travel_to_destination - var/target_key - var/travel_destination_key = BB_TRAVEL_DESTINATION - -/datum/ai_planning_subtree/prepare_travel_to_destination/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[target_key] - - //Target is deleted, or we are already standing on it - if(QDELETED(target) || (isturf(target) && controller.pawn.loc == target) || (target.loc == controller.pawn.loc)) - return - - //Already set with this value, return - if(controller.blackboard[target_key] == controller.blackboard[travel_destination_key]) - return - - controller.queue_behavior(/datum/ai_behavior/set_travel_destination, target_key, travel_destination_key) - return //continue planning regardless of success - -/datum/ai_planning_subtree/prepare_travel_to_destination/trader - target_key = BB_SHOP_SPOT diff --git a/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.json new file mode 100644 index 000000000000..57d616c63bd5 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": { + "label": "speech_behavior", + "default": "/datum/bt_node/ai_behavior/random_speech_blackboard" + } + }, + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "$bqdqne64" + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.dm b/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.dm new file mode 100644 index 000000000000..2a3db065a1ef --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/random_speech_loop + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm deleted file mode 100644 index 3004199a70f5..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm +++ /dev/null @@ -1,50 +0,0 @@ -/// Fire a ranged attack without interrupting movement. -/datum/ai_planning_subtree/ranged_skirmish - operational_datums = list(/datum/component/ranged_attacks) - /// Blackboard key holding target atom - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// What AI behaviour do we actually run? - var/attack_behavior = /datum/ai_behavior/ranged_skirmish - /// If target is further away than this we don't fire - var/max_range = 9 - /// If target is closer than this we don't fire - var/min_range = 2 - -/datum/ai_planning_subtree/ranged_skirmish/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(target_key)) - return - controller.queue_behavior(attack_behavior, target_key, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION, max_range, min_range) - -/// How often will we try to perform our ranged attack? -/datum/ai_behavior/ranged_skirmish - action_cooldown = 0.5 SECONDS - -/datum/ai_behavior/ranged_skirmish/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, max_range, min_range) - . = ..() - var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] - return !QDELETED(target) - -/datum/ai_behavior/ranged_skirmish/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, max_range, min_range) - var/atom/target = controller.blackboard[target_key] - if (QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targeting_strategy.can_attack(controller.pawn, target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/hiding_target = targeting_strategy.find_hidden_mobs(controller.pawn, target) - controller.set_blackboard_key(hiding_location_key, hiding_target) - - target = hiding_target || target - - var/distance = get_dist(controller.pawn, target) - if (distance > max_range || distance < min_range) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target = target, combat_mode = TRUE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_planning_subtree/ranged_skirmish/no_minimum - min_range = 0 diff --git a/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.json new file mode 100644 index 000000000000..e3bbd798047f --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.json @@ -0,0 +1,50 @@ +{ + "dm_type": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": { + "label": "target_key", + "default": "BB_CURRENT_TARGET" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_STOP_FLEEING", + "invert": true, + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_flee_location", + "vars": { + "target_key": "$byk9gqj4", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "destination_key": "BB_FLEE_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FLEE_LOCATION", + "required_dist": 0, + "finish_on_arrival": true + } + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.dm new file mode 100644 index 000000000000..bfdd95157363 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.dm @@ -0,0 +1,8 @@ +/// Flee from BB_CURRENT_TARGET, gated by BB_BASIC_MOB_STOP_FLEEING. +/// Computes a flee waypoint each loop via find_flee_location then moves to it. +/datum/bt_node/subtree/run_away_from_target + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.json" + +/// Flee variant that fires ranged attacks at the current target while moving. +/datum/bt_node/subtree/run_away_from_target/run_and_shoot + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.json new file mode 100644 index 000000000000..124239b50608 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.json @@ -0,0 +1,69 @@ +{ + "dm_type": "/datum/bt_node/subtree/run_away_from_target/run_and_shoot", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_STOP_FLEEING", + "invert": true, + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_flee_location", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "destination_key": "BB_FLEE_LOCATION" + } + } + ] + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FLEE_LOCATION", + "required_dist": 0, + "finish_on_arrival": true + } + } + ] + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm b/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm deleted file mode 100644 index c0c0da96584a..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm +++ /dev/null @@ -1,31 +0,0 @@ -/// Intermittently run an emote -/datum/ai_planning_subtree/run_emote - var/emote_key = BB_EMOTE_KEY - var/emote_chance_key = BB_EMOTE_CHANCE - -/datum/ai_planning_subtree/run_emote/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/emote_chance = controller.blackboard[emote_chance_key] || 0 - if (!SPT_PROB(emote_chance, seconds_per_tick)) - return - controller.queue_behavior(/datum/ai_behavior/run_emote, emote_key) - -/// Emote from a blackboard key -/datum/ai_behavior/run_emote - -/datum/ai_behavior/run_emote/perform(seconds_per_tick, datum/ai_controller/controller, emote_key) - var/mob/living/living_pawn = controller.pawn - if (!isliving(living_pawn)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/list/emote_list = controller.blackboard[emote_key] - var/emote - if (islist(emote_list)) - emote = length(emote_list) ? pick(emote_list) : null - else - emote = emote_list - - if(isnull(emote)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - living_pawn.emote(emote) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm b/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm deleted file mode 100644 index ff01eb804ff7..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm +++ /dev/null @@ -1,41 +0,0 @@ -/// Shapeshift when we have no target, until someone has been nearby for long enough -/datum/ai_planning_subtree/shapechange_ambush - operational_datums = list(/datum/component/ai_target_timer) - /// Key where we keep our ability - var/ability_key = BB_SHAPESHIFT_ACTION - /// Key where we keep our target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// How long to lull our target into a false sense of security - var/minimum_target_time = 8 SECONDS - -/datum/ai_planning_subtree/shapechange_ambush/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/is_shifted = ismob(living_pawn.loc) - var/has_target = controller.blackboard_key_exists(target_key) - var/datum/action/cooldown/using_action = controller.blackboard[ability_key] - - if (!is_shifted) - if (has_target) - return // We're busy - - if (using_action?.IsAvailable()) - controller.queue_behavior(/datum/ai_behavior/use_mob_ability/shapeshift, BB_SHAPESHIFT_ACTION) // Shift - return SUBTREE_RETURN_FINISH_PLANNING - - if (!has_target || !using_action?.IsAvailable()) - return SUBTREE_RETURN_FINISH_PLANNING // Lie in wait - var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 - if (time_on_target < minimum_target_time) - return // Wait a bit longer - controller.queue_behavior(/datum/ai_behavior/use_mob_ability/shapeshift, BB_SHAPESHIFT_ACTION) // Surprise! - -/// Selects a random shapeshift ability before shifting -/datum/ai_behavior/use_mob_ability/shapeshift - -/datum/ai_behavior/use_mob_ability/shapeshift/setup(datum/ai_controller/controller, ability_key) - var/datum/action/cooldown/spell/shapeshift/using_action = controller.blackboard[ability_key] - if (!using_action?.IsAvailable()) - return FALSE - if (isnull(using_action.shapeshift_type)) // If we don't have a shape then pick one, AI can't use context wheels - using_action.shapeshift_type = pick(using_action.possible_shapes) - return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm deleted file mode 100644 index f764568d4ba7..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm +++ /dev/null @@ -1,33 +0,0 @@ -/datum/ai_planning_subtree/basic_melee_attack_subtree - /// What do we do in order to attack someone? - var/datum/ai_behavior/basic_melee_attack/melee_attack_behavior = /datum/ai_behavior/basic_melee_attack - /// Is this the last thing we do? (if we set a movement target, this will usually be yes) - var/end_planning = TRUE - -/datum/ai_planning_subtree/basic_melee_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - if (end_planning) - return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. - -/datum/ai_planning_subtree/basic_ranged_attack_subtree - operational_datums = list(/datum/component/ranged_attacks) - var/datum/ai_behavior/basic_ranged_attack/ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. - -/datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman - -/datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) - return ..() - if(!HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm deleted file mode 100644 index 83e514f32702..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm +++ /dev/null @@ -1,25 +0,0 @@ -/// Find the nearest thing which we assume is hostile and set it as the flee target -/datum/ai_planning_subtree/simple_find_nearest_target_to_flee - -/datum/ai_planning_subtree/simple_find_nearest_target_to_flee/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/nearest, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - -/// Find the nearest thing on our list of 'things which have done damage to me' and set it as the flee target -/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee - ///the targeting strategy we use - var/targeting_key = BB_TARGETING_STRATEGY - ///what key should we set the target as - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - -/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return - controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list/nearest, BB_BASIC_MOB_RETALIATE_LIST, target_key, targeting_key, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - -/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/from_flee_key - target_key = BB_BASIC_MOB_FLEE_TARGET - targeting_key = BB_FLEE_TARGETING_STRATEGY diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm deleted file mode 100644 index 57fb22b4a956..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm +++ /dev/null @@ -1,26 +0,0 @@ -/datum/ai_planning_subtree/simple_find_target - /// Variable to store target in - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// Targeting strategy key to use - var/strategy_key = BB_TARGETING_STRATEGY - /// Behavior to use to find targets - var/target_behavior = /datum/ai_behavior/find_potential_targets - -/datum/ai_planning_subtree/simple_find_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(target_behavior, target_key, strategy_key, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - -// Prevents finding a target if a human is nearby -/datum/ai_planning_subtree/simple_find_target/not_while_observed - -/datum/ai_planning_subtree/simple_find_target/not_while_observed/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - for(var/mob/living/carbon/human/watcher in hearers(7, controller.pawn)) - if(watcher.stat != DEAD) - return - return ..() - -/datum/ai_planning_subtree/simple_find_target/to_flee - target_key = BB_BASIC_MOB_FLEE_TARGET - -/datum/ai_planning_subtree/simple_find_target/hunt - strategy_key = BB_HUNT_TARGETING_STRATEGY diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm deleted file mode 100644 index 7a230014d9ab..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm +++ /dev/null @@ -1,6 +0,0 @@ -/// Selects the most wounded potential target that we can see -/datum/ai_planning_subtree/simple_find_wounded_target - -/datum/ai_planning_subtree/simple_find_wounded_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/most_wounded, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.json new file mode 100644 index 000000000000..bb2b997b7729 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.json @@ -0,0 +1,88 @@ +{ + "dm_type": "/datum/bt_node/subtree/skittish_brawler_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": "DEFAULT_BASIC_FLEE_DISTANCE" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.dm b/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.dm new file mode 100644 index 000000000000..3ea474d2ded3 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.dm @@ -0,0 +1,4 @@ +/// Cowardly brawler combat: keep our distance from attackers, fleeing if they get close but +/// turning to bite if they hang back just out of reach. +/datum/bt_node/subtree/skittish_brawler_combat + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm deleted file mode 100644 index 649a45d4cc7a..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm +++ /dev/null @@ -1,35 +0,0 @@ -/// Disables AI after a certain amount of time spent with no target, you will have to enable the AI again somewhere else -/datum/ai_planning_subtree/sleep_with_no_target - /// Behaviour to execute when sleeping - var/sleep_behaviour = /datum/ai_behavior/sleep_after_targetless_time - /// Target key to interrogate - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - -/datum/ai_planning_subtree/sleep_with_no_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(sleep_behaviour, BB_BASIC_MOB_CURRENT_TARGET) - -/// Disables AI after a certain amount of time spent with no target, you will have to enable the AI again somewhere else -/datum/ai_behavior/sleep_after_targetless_time - /// Turn off AI if we spend this many seconds without a target - var/time_to_wait = 10 SECONDS - -/datum/ai_behavior/sleep_after_targetless_time/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - return (controller.blackboard_key_exists(target_key)) ? ( AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED) : ( AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED) - -/datum/ai_behavior/sleep_after_targetless_time/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if (!succeeded) - controller.clear_blackboard_key(BB_TARGETLESS_TIME) - return - - if (isnull(controller.blackboard[BB_TARGETLESS_TIME])) - controller.set_blackboard_key(BB_TARGETLESS_TIME, world.time + time_to_wait) - - if (controller.blackboard[BB_TARGETLESS_TIME] < world.time) - enter_sleep(controller) - controller.clear_blackboard_key(BB_TARGETLESS_TIME) - -/// Disables AI, override to do additional things or something else -/datum/ai_behavior/sleep_after_targetless_time/proc/enter_sleep(datum/ai_controller/controller) - controller.set_ai_status(AI_STATUS_OFF) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm deleted file mode 100644 index 2a4a0d336e20..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm +++ /dev/null @@ -1,242 +0,0 @@ -/datum/ai_planning_subtree/random_speech - //The chance of an emote occurring each second - var/speech_chance = 0 - ///Hearable emotes - var/list/emote_hear - ///Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps - var/list/emote_see - ///Possible lines of speech the AI can have - var/list/speak - ///The sound effects associated with this speech, if any - var/list/sound - -/datum/ai_planning_subtree/random_speech/New() - . = ..() - if(LAZYLEN(speak)) - speak = string_list(speak) - if(LAZYLEN(sound)) - sound = string_list(sound) - if(LAZYLEN(emote_hear)) - emote_hear = string_list(emote_hear) - if(LAZYLEN(emote_see)) - emote_see = string_list(emote_see) - -/datum/ai_planning_subtree/random_speech/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(speech_chance, seconds_per_tick)) - return - speak(controller) - -/// Actually perform an action -/datum/ai_planning_subtree/random_speech/proc/speak(datum/ai_controller/controller) - var/audible_emotes_length = emote_hear?.len - var/non_audible_emotes_length = emote_see?.len - var/speak_lines_length = speak?.len - - var/total_choices_length = audible_emotes_length + non_audible_emotes_length + speak_lines_length - - if (total_choices_length == 0) - return - - var/random_number_in_range = rand(1, total_choices_length) - var/sound_to_play = length(sound) > 0 ? pick(sound) : null - - if(random_number_in_range <= audible_emotes_length) - controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_hear), sound_to_play) - else if(random_number_in_range <= (audible_emotes_length + non_audible_emotes_length)) - controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_see)) - else - controller.queue_behavior(/datum/ai_behavior/perform_speech, pick(speak), sound_to_play) - -/datum/ai_planning_subtree/random_speech/insect - speech_chance = 5 - sound = list('sound/mobs/non-humanoids/insect/chitter.ogg') - emote_hear = list("chitters.") - -/datum/ai_planning_subtree/random_speech/mothroach - speech_chance = 15 - emote_hear = list("flutters.") - -/datum/ai_planning_subtree/random_speech/mouse - speech_chance = 1 - speak = list("Squeak!", "SQUEAK!", "Squeak?") - sound = list('sound/mobs/non-humanoids/mouse/mousesqueek.ogg') - emote_hear = list("squeaks.") - emote_see = list("runs in a circle.", "shakes.") - -/datum/ai_planning_subtree/random_speech/frog - speech_chance = 3 - emote_see = list("jumps in a circle.", "shakes.") - -/datum/ai_planning_subtree/random_speech/lizard // all of these have to be three words long or i'm killing you. you're dead. - speech_chance = 3 - emote_hear = list("stamps around some.", "hisses a bit.") - emote_see = list("blehs the tongue.", "tilts the head.", "does a spin.") - -/datum/ai_planning_subtree/random_speech/sheep - speech_chance = 5 - speak = list("baaa","baaaAAAAAH!","baaah") - sound = list('sound/mobs/non-humanoids/sheep/sheep1.ogg', 'sound/mobs/non-humanoids/sheep/sheep2.ogg', 'sound/mobs/non-humanoids/sheep/sheep3.ogg') - emote_hear = list("bleats.") - emote_see = list("shakes her head.", "stares into the distance.") - -/datum/ai_planning_subtree/random_speech/rabbit - speech_chance = 10 - speak = list("Mrrp.", "CHIRP!", "Mrrp?") // rabbits make some weird noises dude i don't know what to tell you - emote_hear = list("hops.") - emote_see = list("hops around.", "bounces up and down.") - -/// For the easter subvariant of rabbits, these ones actually speak catchphrases. -/datum/ai_planning_subtree/random_speech/rabbit/easter - speak = list( - "Hop into Easter!", - "Come get your eggs!", - "Prizes for everyone!", - ) - -/// These ones have a space mask on, so their catchphrases are muffled. -/datum/ai_planning_subtree/random_speech/rabbit/easter/space - speak = list( - "Hmph mmph mmmph!", - "Mmphe mmphe mmphe!", - "Hmm mmm mmm!", - ) - -/datum/ai_planning_subtree/random_speech/chicken - speech_chance = 15 // really talkative ladies - speak = list("Cluck!", "BWAAAAARK BWAK BWAK BWAK!", "Bwaak bwak.") - sound = list('sound/mobs/non-humanoids/chicken/clucks.ogg', 'sound/mobs/non-humanoids/chicken/bagawk.ogg') - emote_hear = list("clucks.", "croons.") - emote_see = list("pecks at the ground.","flaps her wings viciously.") - -/datum/ai_planning_subtree/random_speech/chick - speech_chance = 4 - speak = list("Cherp.", "Cherp?", "Chirrup.", "Cheep!") - sound = list('sound/mobs/non-humanoids/chicken/chick_peep.ogg') - emote_hear = list("cheeps.") - emote_see = list("pecks at the ground.","flaps her tiny wings.") - -/datum/ai_planning_subtree/random_speech/cow - speech_chance = 1 - speak = list("moo?","moo","MOOOOOO") - sound = list('sound/mobs/non-humanoids/cow/cow.ogg') - emote_hear = list("brays.") - emote_see = list("shakes her head.") - -///unlike normal cows, wisdom cows speak of wisdom and won't shut the fuck up -/datum/ai_planning_subtree/random_speech/cow/wisdom - speech_chance = 15 - -/datum/ai_planning_subtree/random_speech/cow/wisdom/New() - . = ..() - speak = GLOB.wisdoms //Done here so it's setup properly - sound = list() - -/datum/ai_planning_subtree/random_speech/deer - speech_chance = 1 - speak = list("Weeeeeeee?", "Weeee", "WEOOOOOOOOOO") - emote_hear = list("brays.") - emote_see = list("shakes her head.") - -/datum/ai_planning_subtree/random_speech/dog - speech_chance = 1 - -/datum/ai_planning_subtree/random_speech/dog/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!isdog(controller.pawn)) - return - - // Stay in sync with dog fashion. - var/mob/living/basic/pet/dog/dog_pawn = controller.pawn - dog_pawn.update_dog_speech(src) - - return ..() - -/datum/ai_planning_subtree/random_speech/faithless - speech_chance = 1 - emote_see = list("wails.") - -/datum/ai_planning_subtree/random_speech/garden_gnome - speech_chance = 5 - speak = list("Gnot a gnelf!", "Gnot a gnoblin!", "Howdy chum!") - emote_hear = list("snores.", "burps.") - emote_see = list("blinks.") - -/datum/ai_planning_subtree/random_speech/tree - speech_chance = 3 - emote_see = list("photosynthesizes angrily.") - -/datum/ai_planning_subtree/random_speech/pig - speech_chance = 3 - speak = list("oink?","oink","snurf") - sound = list('sound/mobs/non-humanoids/pig/pig1.ogg', 'sound/mobs/non-humanoids/pig/pig2.ogg') - emote_hear = list("snorts.") - emote_see = list("sniffs around.") - -/datum/ai_planning_subtree/random_speech/pony - speech_chance = 3 - sound = list('sound/mobs/non-humanoids/pony/whinny01.ogg', 'sound/mobs/non-humanoids/pony/whinny02.ogg', 'sound/mobs/non-humanoids/pony/whinny03.ogg') - emote_hear = list("whinnies!") - emote_see = list("horses around.") - -/datum/ai_planning_subtree/random_speech/pony/tamed - speech_chance = 3 - sound = list('sound/mobs/non-humanoids/pony/snort.ogg') - emote_hear = list("snorts.") - emote_see = list("snorts.") - -/datum/ai_planning_subtree/random_speech/killer_tomato - speech_chance = 3 - emote_hear = list("gnashes.", "growls lowly.", "snarls.") - emote_see = list("salivates.") - -/datum/ai_planning_subtree/random_speech/ant - speech_chance = 1 - speak = list("BZZZZT!", "CHTCHTCHT!", "Bzzz", "ChtChtCht") - sound = list('sound/mobs/non-humanoids/insect/chitter.ogg') - emote_hear = list("buzzes.", "clacks.") - emote_see = list("shakes their head.", "twitches their antennae.") - -/datum/ai_planning_subtree/random_speech/fox - speech_chance = 1 - speak = list("Ack-Ack", "Ack-Ack-Ack-Ackawoooo", "Geckers", "Awoo", "Tchoff") - emote_hear = list("howls.", "barks.", "screams.") - emote_see = list("shakes their head.", "shivers.") - -/datum/ai_planning_subtree/random_speech/crab - speech_chance = 1 - sound = list('sound/mobs/non-humanoids/crab/claw_click.ogg') - emote_hear = list("clicks.") - emote_see = list("clacks.") - -/datum/ai_planning_subtree/random_speech/penguin - speech_chance = 5 - speak = list("Gah Gah!", "NOOT NOOT!", "NOOT!", "Noot", "noot", "Prah!", "Grah!") - emote_hear = list("squawks", "gakkers") - -/datum/ai_planning_subtree/random_speech/bear - speech_chance = 5 - emote_hear = list("rawrs.","grumbles.","grawls.", "stomps!") - emote_see = list("stares ferociously.") - -/datum/ai_planning_subtree/random_speech/cats - speech_chance = 10 - sound = list(SFX_CAT_MEOW) - emote_hear = list("meows.") - emote_see = list("meows.") - -/datum/ai_planning_subtree/random_speech/blackboard //literal tower of babel, subtree form - speech_chance = 1 - -/datum/ai_planning_subtree/random_speech/blackboard/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/list/speech_lines = controller.blackboard[BB_BASIC_MOB_SPEAK_LINES] - if(isnull(speech_lines)) - return ..() - - // Note to future developers: this behaviour a singleton so this probably doesn't work as you would expect - // The whole speech tree really needs to be refactored because this isn't how we use AI data these days - speak = speech_lines[BB_EMOTE_SAY] || list() - emote_see = speech_lines[BB_EMOTE_SEE] || list() - emote_hear = speech_lines[BB_EMOTE_HEAR] || list() - sound = speech_lines[BB_EMOTE_SOUND] || list() - speech_chance = speech_lines[BB_SPEAK_CHANCE] ? speech_lines[BB_SPEAK_CHANCE] : initial(speech_chance) - - return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm b/code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm deleted file mode 100644 index 5263c8f82c6d..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm +++ /dev/null @@ -1,12 +0,0 @@ -/// Locate a thing (practically any atom) to stop and stare at. -/datum/ai_planning_subtree/stare_at_thing - -/datum/ai_planning_subtree/stare_at_thing/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_STATIONARY_CAUSE] - - if(isnull(target)) // No target? Time to locate one using the list we set in this mob's blackboard. - var/list/potential_scares = controller.blackboard[BB_STATIONARY_TARGETS] - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_STATIONARY_CAUSE, potential_scares) - return - - controller.queue_behavior(/datum/ai_behavior/stop_and_stare, BB_STATIONARY_CAUSE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm deleted file mode 100644 index c86994054613..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm +++ /dev/null @@ -1,94 +0,0 @@ -/// Sets the BB target to a mob which you can see and who has recently attacked you -/datum/ai_planning_subtree/target_retaliate - operational_datums = list(/datum/element/ai_retaliate, /datum/component/ai_retaliate_advanced) - /// Blackboard key which tells us how to select valid targets - var/targeting_strategy_key = BB_TARGETING_STRATEGY - /// Blackboard key in which to store selected target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// Blackboard key in which to store selected target's hiding place - var/hiding_place_key = BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION - /// do we check for faction? - var/check_faction = FALSE - /// Behavior to use to select our target - var/target_behavior = /datum/ai_behavior/target_from_retaliate_list - -/datum/ai_planning_subtree/target_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - controller.queue_behavior(target_behavior, BB_BASIC_MOB_RETALIATE_LIST, target_key, targeting_strategy_key, hiding_place_key, check_faction) - -/datum/ai_planning_subtree/target_retaliate/check_faction - check_faction = TRUE - -/// Places a mob which you can see and who has recently attacked you into some 'run away from this' AI keys -/// Can use a different targeting strategy than you use to select attack targets -/// Not required if fleeing is the only target behaviour or uses the same target datum -/datum/ai_planning_subtree/target_retaliate/to_flee - targeting_strategy_key = BB_FLEE_TARGETING_STRATEGY - target_key = BB_BASIC_MOB_FLEE_TARGET - hiding_place_key = BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION - -/** - * Picks a target from a provided list of atoms who have been pissing you off - * You will probably need /datum/element/ai_retaliate to take advantage of this unless you're populating the blackboard yourself - */ -/datum/ai_behavior/target_from_retaliate_list - action_cooldown = 2 SECONDS - /// How far can we see stuff? - var/vision_range = 9 - -/datum/ai_behavior/target_from_retaliate_list/perform(seconds_per_tick, datum/ai_controller/controller, shitlist_key, target_key, targeting_strategy_key, hiding_location_key, check_faction) - var/mob/living/living_mob = controller.pawn - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targeting_strategy) - . = AI_BEHAVIOR_DELAY - CRASH("No target datum was supplied in the blackboard for [controller.pawn]") - - var/list/shitlist = controller.blackboard[shitlist_key] - var/atom/existing_target = controller.blackboard[target_key] - - var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[BB_TARGET_PRIORITY_STRATEGY]) - var/existing_priority = 0 - // If we have an existing target and its priority is higher than our new target's, don't switch focus - if (priority_strategy && existing_target) - existing_priority = priority_strategy.get_target_priority(controller, existing_target) - - if (!check_faction) - controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) - - if (!QDELETED(existing_target) && targeting_strategy.can_attack(living_mob, existing_target, vision_range)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - var/list/enemies_list = list() - for(var/mob/living/potential_target as anything in shitlist) - if(!targeting_strategy.can_attack(living_mob, potential_target, vision_range)) - continue - // Strict comparasion because priority strategies might not care about retaliation, so this makes existing targets not override potential retaliates - if (priority_strategy && priority_strategy.get_target_priority(controller, potential_target) < existing_priority) - continue - enemies_list += potential_target - - if(!length(enemies_list)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/atom/new_target = pick_final_target(controller, enemies_list) - controller.set_blackboard_key(target_key, new_target) - - var/atom/potential_hiding_location = targeting_strategy.find_hidden_mobs(living_mob, new_target) - - if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially. - controller.set_blackboard_key(hiding_location_key, potential_hiding_location) - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/// Returns the desired final target from the filtered list of enemies -/datum/ai_behavior/target_from_retaliate_list/proc/pick_final_target(datum/ai_controller/controller, list/enemies_list) - var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[BB_TARGET_PRIORITY_STRATEGY]) - if (!priority_strategy) - return pick(enemies_list) - return priority_strategy.select_target(controller, enemies_list) - -/datum/ai_behavior/target_from_retaliate_list/finish_action(datum/ai_controller/controller, succeeded, shitlist_key, target_key, targeting_strategy_key, hiding_location_key, check_faction) - . = ..() - if (succeeded || check_faction) - return - var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE - controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm deleted file mode 100644 index 010dd04a425e..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm +++ /dev/null @@ -1,34 +0,0 @@ -/// Attempts to use a mob ability on a target -/datum/ai_planning_subtree/targeted_mob_ability - /// Blackboard key for the ability - var/ability_key = BB_TARGETED_ACTION - /// Blackboard key for where the target ref is stored - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// Behaviour to perform using ability - var/use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability - /// If true we terminate planning after trying to use the ability. - var/finish_planning = TRUE - -/datum/ai_planning_subtree/targeted_mob_ability/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!ability_key) - CRASH("You forgot to tell this mob where to find its ability") - - if (!controller.blackboard_key_exists(target_key)) - return - - var/datum/action/cooldown/using_action = controller.blackboard[ability_key] - if (!using_action?.IsAvailable()) - return - if (!additional_ability_checks(controller, using_action)) - return - - controller.queue_behavior(use_ability_behaviour, ability_key, target_key) - if (finish_planning) - return SUBTREE_RETURN_FINISH_PLANNING - -/// Any additional checks before we queue the behaviour -/datum/ai_planning_subtree/targeted_mob_ability/proc/additional_ability_checks(datum/ai_controller/controller, datum/action/cooldown/using_action) - return TRUE - -/datum/ai_planning_subtree/targeted_mob_ability/continue_planning - finish_planning = FALSE diff --git a/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm deleted file mode 100644 index 25f0e4a42496..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm +++ /dev/null @@ -1,55 +0,0 @@ -///behavior to activate ability to escape from target -/datum/ai_planning_subtree/teleport_away_from_target - ///minimum distance away from the target before we execute behavior - var/minimum_distance = 2 - ///the ability we will execute - var/ability_key - -/datum/ai_planning_subtree/teleport_away_from_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - var/distance_from_target = get_dist(target, controller.pawn) - if(distance_from_target >= minimum_distance) - controller.clear_blackboard_key(BB_ESCAPE_DESTINATION) - return - var/datum/action/cooldown/ability = controller.blackboard[ability_key] - if(!ability?.IsAvailable()) - return - var/turf/location_turf = controller.blackboard[BB_ESCAPE_DESTINATION] - - if(isnull(location_turf)) - controller.queue_behavior(/datum/ai_behavior/find_furthest_turf_from_target, BB_BASIC_MOB_CURRENT_TARGET, BB_ESCAPE_DESTINATION, minimum_distance) - return SUBTREE_RETURN_FINISH_PLANNING - - if(get_dist(location_turf, target) < minimum_distance || !can_see(controller.pawn, location_turf)) //target moved close too close or we moved too far since finding the target turf - controller.clear_blackboard_key(BB_ESCAPE_DESTINATION) - return - - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target, ability_key, BB_ESCAPE_DESTINATION) - -///find furtherst turf target so we may teleport to it -/datum/ai_behavior/find_furthest_turf_from_target - -/datum/ai_behavior/find_furthest_turf_from_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, set_key, range) - var/mob/living/living_target = controller.blackboard[target_key] - if(QDELETED(living_target)) - return AI_BEHAVIOR_INSTANT - - var/distance = 0 - var/turf/chosen_turf - for(var/turf/open/potential_destination in oview(range, living_target)) - if(potential_destination.is_blocked_turf()) - continue - var/new_distance_to_target = get_dist(potential_destination, living_target) - if(new_distance_to_target > distance) - chosen_turf = potential_destination - distance = new_distance_to_target - if(distance == range) - break //we have already found the max distance - - if(isnull(chosen_turf)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(set_key, chosen_turf) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.json new file mode 100644 index 000000000000..862d917bde0b --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.json @@ -0,0 +1,13 @@ +{ + "dm_type": "/datum/bt_node/subtree/tip_reaction", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_BASIC_MOB_TIP_REACTING", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/tipped_reaction" + } +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/tipped_subtree.dm b/code/datums/ai/basic_mobs/basic_subtrees/tipped_subtree.dm deleted file mode 100644 index b502860a6beb..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/tipped_subtree.dm +++ /dev/null @@ -1,10 +0,0 @@ -///used by cows -/datum/ai_planning_subtree/tip_reaction - -/datum/ai_planning_subtree/tip_reaction/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/tip_reacting = controller.blackboard[BB_BASIC_MOB_TIP_REACTING] - if(!tip_reacting) - return - controller.queue_behavior(/datum/ai_behavior/tipped_reaction, BB_BASIC_MOB_TIPPER, BB_BASIC_MOB_TIP_REACTING) - return SUBTREE_RETURN_FINISH_PLANNING //no point in trying, boy. you're TIPPED. diff --git a/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm b/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm deleted file mode 100644 index 0b5e5d4776f9..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm +++ /dev/null @@ -1,21 +0,0 @@ -/// Simply walk to a location -/datum/ai_planning_subtree/travel_to_point - /// Blackboard key where we travel a place we walk to - var/location_key = BB_TRAVEL_DESTINATION - /// What do we do in order to travel - var/travel_behaviour = /datum/ai_behavior/travel_towards - -/datum/ai_planning_subtree/travel_to_point/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/atom/target = controller.blackboard[location_key] - if (QDELETED(target)) - return - controller.queue_behavior(travel_behaviour, location_key) - return SUBTREE_RETURN_FINISH_PLANNING - - -/datum/ai_planning_subtree/travel_to_point/and_clear_target - travel_behaviour = /datum/ai_behavior/travel_towards/stop_on_arrival - -/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce - location_key = BB_BASIC_MOB_REINFORCEMENT_TARGET diff --git a/code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm b/code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm deleted file mode 100644 index 4c794a73d8ad..000000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Simple behaviours which simply try to use an ability whenever it is available. - * For something which wants a target try `targeted_mob_ability`. - */ -/datum/ai_planning_subtree/use_mob_ability - /// Blackboard key for the ability - var/ability_key = BB_GENERIC_ACTION - /// Behaviour to perform using ability - var/use_ability_behaviour = /datum/ai_behavior/use_mob_ability - /// If true we terminate planning after trying to use the ability. - var/finish_planning = FALSE - -/datum/ai_planning_subtree/use_mob_ability/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!ability_key) - CRASH("You forgot to tell this mob where to find its ability") - - var/datum/action/using_action = controller.blackboard[ability_key] - if (!using_action?.IsAvailable()) - return - - controller.queue_behavior(use_ability_behaviour, ability_key) - if (finish_planning) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/use_mob_ability - -/datum/ai_behavior/use_mob_ability/perform(seconds_per_tick, datum/ai_controller/controller, ability_key) - var/datum/action/using_action = controller.blackboard[ability_key] - if (QDELETED(using_action)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - if(using_action.Trigger()) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED diff --git a/code/datums/ai/basic_mobs/generic_controllers.dm b/code/datums/ai/basic_mobs/generic_controllers.dm index b6581a5cb8b9..0a0661f5a8d7 100644 --- a/code/datums/ai/basic_mobs/generic_controllers.dm +++ b/code/datums/ai/basic_mobs/generic_controllers.dm @@ -5,138 +5,121 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk + behavior_tree_json = ABSTRACT_AI_CLASS + + +/datum/bt_node/subtree/simple_hostile_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile_combat.bt.json" + -/// The most basic AI tree which just finds a guy and then runs at them to click them /datum/ai_controller/basic_controller/simple/simple_hostile - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile.bt.json" + + +/datum/bt_node/subtree/simple_ranged_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ranged_combat.bt.json" + +/datum/bt_node/subtree/simple_ranged_retaliate_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.json" + + +/datum/bt_node/subtree/simple_skirmisher_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_skirmisher_combat.bt.json" + +/datum/bt_node/subtree/simple_ability_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_combat.bt.json" + +/datum/bt_node/subtree/simple_ability_retaliate_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.json" + +/datum/bt_node/subtree/simple_ability_melee_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_melee_combat.bt.json" + +/datum/bt_node/subtree/simple_ability_ranged_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.json" + + +/datum/bt_node/subtree/simple_retaliate_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_retaliate_combat.bt.json" + +/datum/bt_node/subtree/simple_capricious_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_capricious_combat.bt.json" + +/datum/bt_node/subtree/simple_fearful_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_fearful_combat.bt.json" + +/datum/bt_node/subtree/simple_skittish_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_skittish_combat.bt.json" + +/datum/bt_node/subtree/simple_hostile_obstacles_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.json" + + /// Find a target, walk at target, attack intervening obstacles /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile_obstacles.bt.json" -/// Find a target, walk at target, attack intervening obstacles +/// Find a target, maintain distance, shoot them /datum/ai_controller/basic_controller/simple/simple_ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/ranged_skirmish, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ranged.bt.json" /datum/ai_controller/basic_controller/simple/simple_ranged_retaliate - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/ranged_skirmish, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ranged_retaliate.bt.json" /// Find a target, walk towards it AND shoot it /datum/ai_controller/basic_controller/simple/simple_skirmisher - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_skirmisher.bt.json" /// Use an ability on target on cooldown /datum/ai_controller/basic_controller/simple/simple_ability - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/targeted_mob_ability, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability.bt.json" /datum/ai_controller/basic_controller/simple/simple_ability_retaliate - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/targeted_mob_ability, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_retaliate.bt.json" /// Use an ability on target on cooldown, then try to punch them /datum/ai_controller/basic_controller/simple/simple_ability_melee - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_melee.bt.json" /// Use an ability on target on cooldown, then try to shoot them /datum/ai_controller/basic_controller/simple/simple_ability_ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/targeted_mob_ability, - /datum/ai_planning_subtree/ranged_skirmish, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_ranged.bt.json" /// Fight back if attacked /datum/ai_controller/basic_controller/simple/simple_retaliate + behavior_tree_json = "code/datums/ai/basic_mobs/simple_retaliate.bt.json" ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Get pissed at random people for no reason /datum/ai_controller/basic_controller/simple/simple_capricious + behavior_tree_json = "code/datums/ai/basic_mobs/simple_capricious.bt.json" ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/capricious_retaliate, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Runs away from anyone it sees /datum/ai_controller/basic_controller/simple/simple_fearful + behavior_tree_json = "code/datums/ai/basic_mobs/simple_fearful.bt.json" ai_traits = PASSIVE_AI_FLAGS - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/flee_target, - ) /// Runs away when attacked /datum/ai_controller/basic_controller/simple/simple_skittish + behavior_tree_json = "code/datums/ai/basic_mobs/simple_skittish.bt.json" ai_traits = PASSIVE_AI_FLAGS - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - ) /// Does what it is told and protects da boss +/// TODO: port pet command system to BT so pet_planning functions correctly /datum/ai_controller/basic_controller/simple/simple_goon + behavior_tree_json = "code/datums/ai/basic_mobs/simple_goon.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - ) -/// Literally does nothing except random speedh +/// Literally does nothing except random speech /datum/ai_controller/basic_controller/talk - idle_behavior = null - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/blackboard, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/talk.bt.json" + + +/datum/bt_node/subtree/simple_hostile_combat_with_retaliate + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.json" diff --git a/code/datums/ai/basic_mobs/pet_commands/fetch.dm b/code/datums/ai/basic_mobs/pet_commands/fetch.dm deleted file mode 100644 index 5ff208560d2a..000000000000 --- a/code/datums/ai/basic_mobs/pet_commands/fetch.dm +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Traverse to a target with the intention of picking it up. - * If we can't do that, add it to a list of ignored items. - */ -/datum/ai_behavior/fetch_seek - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT|AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/fetch_seek/setup(datum/ai_controller/controller, target_key, delivery_key) - . = ..() - var/obj/item/fetch_thing = controller.blackboard[target_key] - // It stopped existing - if (QDELETED(fetch_thing)) - return FALSE - set_movement_target(controller, fetch_thing) - -/datum/ai_behavior/fetch_seek/perform(seconds_per_tick, datum/ai_controller/controller, target_key, delivery_key) - var/obj/item/fetch_thing = controller.blackboard[target_key] - - // It stopped existing - if (QDELETED(fetch_thing)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - // We can't pick this up - if (fetch_thing.anchored) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/fetch_seek/finish_action(datum/ai_controller/controller, success, target_key, delivery_key) - . = ..() - if (success) - return - // Blacklist item if we failed - var/obj/item/target = controller.blackboard[target_key] - if (target) - controller.set_blackboard_key_assoc_lazylist(BB_FETCH_IGNORE_LIST, target, TRUE) - controller.clear_blackboard_key(target_key) - controller.clear_blackboard_key(delivery_key) - -/** - * The second half of fetching, deliver the item to a target. - */ -/datum/ai_behavior/deliver_fetched_item - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT|AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/deliver_fetched_item/setup(datum/ai_controller/controller, delivery_key, storage_key) - . = ..() - var/mob/living/return_target = controller.blackboard[delivery_key] - if(QDELETED(return_target)) // Guess it's mine now - return FALSE - set_movement_target(controller, return_target) - -/datum/ai_behavior/deliver_fetched_item/perform(seconds_per_tick, datum/ai_controller/controller, delivery_key, storage_key) - var/mob/living/return_target = controller.blackboard[delivery_key] - if(QDELETED(return_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(!deliver_item(controller, return_target, storage_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/deliver_fetched_item/finish_action(datum/ai_controller/controller, success, delivery_key) - . = ..() - controller.clear_blackboard_key(delivery_key) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - -/// Actually deliver the fetched item to the target, if we still have it -/// Returns TRUE if we succeeded, FALSE if we failed -/datum/ai_behavior/deliver_fetched_item/proc/deliver_item(datum/ai_controller/controller, return_target, storage_key) - var/mob/pawn = controller.pawn - var/obj/item/carried_item = controller.blackboard[storage_key] - if(QDELETED(carried_item) || carried_item.loc != pawn) - pawn.visible_message(span_notice("[pawn] looks around as if [pawn.p_they()] [pawn.p_have()] lost something.")) - return FALSE - - pawn.visible_message(span_notice("[pawn] delivers [carried_item] to [return_target].")) - carried_item.forceMove(get_turf(return_target)) - controller.clear_blackboard_key(storage_key) - return TRUE - -/** - * The alternate second half of fetching, attack the item if we can eat it. - * Or make pleading eyes at someone who has picked it up. - * - * Unfortunately this doesn't work because food can't currently be eaten by mobs. - */ -/datum/ai_behavior/eat_fetched_snack - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - action_cooldown = 0.8 SECONDS - -/datum/ai_behavior/eat_fetched_snack/setup(datum/ai_controller/controller, target_key, delivery_key) - . = ..() - var/obj/item/snack = controller.blackboard[target_key] - if(!istype(snack) || !IS_EDIBLE(snack) || !(isturf(snack.loc) || ishuman(snack.loc))) - return FALSE // This isn't food at all! - set_movement_target(controller, snack) - -/datum/ai_behavior/eat_fetched_snack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, delivery_key) - var/obj/item/snack = controller.blackboard[target_key] - var/is_living_loc = isliving(snack.loc) - if(QDELETED(snack) || (!isturf(snack.loc) && !is_living_loc)) - // Where did it go? - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/basic/basic_pawn = controller.pawn - if(is_living_loc) - if(SPT_PROB(10, seconds_per_tick)) - basic_pawn.manual_emote("Stares at [snack.loc]'s [snack.name] intently.") - return AI_BEHAVIOR_DELAY - - if(!basic_pawn.Adjacent(snack)) - return AI_BEHAVIOR_DELAY - - controller.ai_interact(target = snack) - - if(QDELETED(snack)) // we ate it! - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/eat_fetched_snack/finish_action(datum/ai_controller/controller, succeeded, target_key, delivery_key) - . = ..() - controller.clear_blackboard_key(target_key) - controller.clear_blackboard_key(delivery_key) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - -/** - * Clear our failed fetch list every so often - */ -/datum/ai_behavior/forget_failed_fetches - /// How long to wait between resetting the list - var/cooldown_duration = AI_FETCH_IGNORE_DURATION - /// Time until we should forget things we failed to pick up - COOLDOWN_DECLARE(reset_ignore_cooldown) - -/datum/ai_behavior/forget_failed_fetches/setup(datum/ai_controller/controller, ...) - . = ..() - if (!COOLDOWN_FINISHED(src, reset_ignore_cooldown)) - return FALSE - if (!length(controller.blackboard[BB_FETCH_IGNORE_LIST])) - return - -/datum/ai_behavior/forget_failed_fetches/perform(seconds_per_tick, datum/ai_controller/controller) - COOLDOWN_START(src, reset_ignore_cooldown, cooldown_duration) - controller.clear_blackboard_key(BB_FETCH_IGNORE_LIST) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.json new file mode 100644 index 000000000000..ca4446c07f76 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.json @@ -0,0 +1,48 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/attack", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.json new file mode 100644 index 000000000000..c8aa95fd4528 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.json @@ -0,0 +1,48 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/attack/dog", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack/dog", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.json new file mode 100644 index 000000000000..c1c6ab6b77a6 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.json @@ -0,0 +1,48 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/attack/minebot", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/minebot", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.json new file mode 100644 index 000000000000..dd8ff0b694a4 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.json @@ -0,0 +1,46 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/attack/ranged/glockroach", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.json new file mode 100644 index 000000000000..d5344983e85a --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.json @@ -0,0 +1,23 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/beehive", + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_HOME", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enter_exit_hive" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.json new file mode 100644 index 000000000000..18d2ef34057d --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.json @@ -0,0 +1,64 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/breed", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_in_typelist", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "typelist_key": "BB_BABIES_PARTNER_TYPES" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/keys_different_gender", + "vars": { + "invert": true, + "key_a": "BB_MY_PAWN", + "key_b": "BB_CURRENT_PET_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "Seems confused" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "combat_mode": false + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] + } + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_bt.dm b/code/datums/ai/basic_mobs/pet_commands/pet_command_bt.dm new file mode 100644 index 000000000000..2654e04ada84 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_bt.dm @@ -0,0 +1,128 @@ +// Pet-command-specific BT behaviors and override subtrees. +// Generic leaf behaviors (wait, play_dead, pick_up_item_virtual, pass_item_virtual, ai_interact) live in basic_ai_behaviors/. + +/// Validates a protect_owner target; clears command + target if invalid. +/datum/bt_node/ai_behavior/protect_owner_check + +/datum/bt_node/ai_behavior/protect_owner_check/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/victim = controller.blackboard[BB_CURRENT_PET_TARGET] + if(QDELETED(victim)) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(controller.blackboard[BB_PET_TARGETING_STRATEGY]) + if(!targeter?.is_valid_target(controller.pawn, victim)) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/minimum_stat = controller.blackboard[BB_TARGET_MINIMUM_STAT] + if((!isnull(minimum_stat) && victim.stat > minimum_stat) || victim == controller.pawn) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Validates a fetch item at target_key; adds to ignore list and clears keys on failure. +/datum/bt_node/ai_behavior/fetch_seek + var/target_key + +/datum/bt_node/ai_behavior/fetch_seek/setup(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/fetch_seek/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/fetch_thing = controller.blackboard[target_key] + if(QDELETED(fetch_thing)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(fetch_thing.anchored) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/fetch_seek/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(succeeded) + return + var/obj/item/target = controller.blackboard[target_key] + if(target) + controller.set_blackboard_key_assoc_lazylist(BB_FETCH_IGNORE_LIST, target, TRUE) + controller.clear_blackboard_key(target_key) + controller.clear_blackboard_key(BB_FETCH_DELIVER_TO) + +/// Clears the fetch ignore list at most once per AI_FETCH_IGNORE_DURATION. Always succeeds. +/datum/bt_node/ai_behavior/forget_failed_fetches + COOLDOWN_DECLARE(clear_cooldown) + +/datum/bt_node/ai_behavior/forget_failed_fetches/perform(seconds_per_tick, datum/ai_controller/controller) + if(COOLDOWN_FINISHED(src, clear_cooldown) && LAZYLEN(controller.blackboard[BB_FETCH_IGNORE_LIST])) + COOLDOWN_START(src, clear_cooldown, AI_FETCH_IGNORE_DURATION) + controller.clear_blackboard_key(BB_FETCH_IGNORE_LIST) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Clears BB_ACTIVE_PET_COMMAND and removes the SUBPLAN_ID_PET_COMMAND override. +/datum/bt_node/ai_behavior/clear_pet_command + +/datum/bt_node/ai_behavior/clear_pet_command/perform(seconds_per_tick, datum/ai_controller/controller) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, null) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +/// Waits forever; blocks normal AI while stay/idle is active. +/datum/bt_node/subtree/pet_command/stay + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.json" + +/// Loops move_to_target toward BB_CURRENT_PET_TARGET until the key is cleared. +/datum/bt_node/subtree/pet_command/follow + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.json" + +/// Plays dead (10%/tick to get up). Clears command on revival. +/datum/bt_node/subtree/pet_command/play_dead + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.json" + +/// Attacks BB_CURRENT_PET_TARGET in a looping melee combat parallel. +/datum/bt_node/subtree/pet_command/attack + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.json" + +/// Protect owner: loops a validity check then melee attack. Clears command if target invalid. +/datum/bt_node/subtree/pet_command/protect_owner + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.json" + +/// Travels to BB_CURRENT_PET_TARGET, clears command on arrival. +/datum/bt_node/subtree/pet_command/move_to + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.json" + +/// Moves to BB_CURRENT_PET_TARGET and fishes there on a loop. +/datum/bt_node/subtree/pet_command/fish + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.json" + +/// Moves to BB_CURRENT_PET_TARGET and breeds once. Clears command on completion. +/datum/bt_node/subtree/pet_command/breed + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.json" + +/// Moves to BB_CURRENT_PET_TARGET and fires BB_TARGETED_ACTION on it once. +/datum/bt_node/subtree/pet_command/targeted_ability + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.json" + +/// Fires the ability stored in BB_PET_ACTIVE_ABILITY once (untargeted). +/datum/bt_node/subtree/pet_command/untargeted_ability + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.json" + +/// Fetch: seek > pick up > deliver. Falls back to clear_pet_command if nothing to do. +/datum/bt_node/subtree/pet_command/fetch + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.json" + +/// Attacks BB_CURRENT_PET_TARGET using the dog's melee behavior (paws if BB_DOG_HARASS_HARM is false, bites otherwise). +/datum/bt_node/subtree/pet_command/attack/dog + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.json" + +/// Attacks BB_CURRENT_PET_TARGET with glockroach ranged attack (1s cooldown). +/datum/bt_node/subtree/pet_command/attack/ranged/glockroach + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.json" + +/// Attacks BB_CURRENT_PET_TARGET with minebot ranged attack (avoids friendly fire). +/datum/bt_node/subtree/pet_command/attack/minebot + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.json" + +/// Protect owner: loops validity check then glockroach ranged attack. Clears command if target invalid. +/datum/bt_node/subtree/pet_command/protect_owner/ranged/glockroach + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.json" diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.json new file mode 100644 index 000000000000..c6ef1ab9177a --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.json @@ -0,0 +1,107 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/fetch", + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/forget_failed_fetches" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/fetch_seek", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up_item_virtual", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "storage_key": "BB_SIMPLE_CARRY_ITEM" + } + } + ] + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SIMPLE_CARRY_ITEM", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FETCH_DELIVER_TO", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FETCH_DELIVER_TO", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pass_item_virtual", + "vars": { + "delivery_key": "BB_FETCH_DELIVER_TO", + "storage_key": "BB_SIMPLE_CARRY_ITEM" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": true, + "key": "BB_CURRENT_PET_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.json new file mode 100644 index 000000000000..62e0d24abe7a --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.json @@ -0,0 +1,38 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/fish", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "combat_mode": false + } + } + ] + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.json new file mode 100644 index 000000000000..9a70605a7aed --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.json @@ -0,0 +1,25 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/follow", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.json new file mode 100644 index 000000000000..f7a76f638f67 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.json @@ -0,0 +1,46 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/mine_walls", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/mine_wall", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mineral_wall", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.json new file mode 100644 index 000000000000..98fd4fd007e3 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.json @@ -0,0 +1,27 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/move_to", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 0, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_planning.dm b/code/datums/ai/basic_mobs/pet_commands/pet_command_planning.dm deleted file mode 100644 index bf4885b817b6..000000000000 --- a/code/datums/ai/basic_mobs/pet_commands/pet_command_planning.dm +++ /dev/null @@ -1,14 +0,0 @@ -/** - * # Pet Planning - * Perform behaviour based on what pet commands you have received. This is delegated to the pet command datum. - * When a command is set, we blackboard a key to our currently active command. - * The blackboard also has a weak reference to every command datum available to us. - * We use the key to figure out which datum to run, then ask it to figure out how to execute its action. - */ -/datum/ai_planning_subtree/pet_planning - -/datum/ai_planning_subtree/pet_planning/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/datum/pet_command/command = controller.blackboard[BB_ACTIVE_PET_COMMAND] - if (!command) - return // Do something else - return command.execute_action(controller) diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.json new file mode 100644 index 000000000000..c7a182918b30 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/play_dead", + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/play_dead" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.json new file mode 100644 index 000000000000..3c9f545eb663 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.json @@ -0,0 +1,57 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/protect_owner", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/protect_owner_check" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.json new file mode 100644 index 000000000000..b8cdd00f02f1 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.json @@ -0,0 +1,55 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/protect_owner/ranged/glockroach", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/protect_owner_check" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.json new file mode 100644 index 000000000000..d3d3517b2b77 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.json @@ -0,0 +1,17 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/scatter", + "type": "sequence", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_CURRENT_PET_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.json new file mode 100644 index 000000000000..d374016e90cb --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/stay", + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": 0 + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.json new file mode 100644 index 000000000000..baf9fc2e7d4d --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.json @@ -0,0 +1,81 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/swirl", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SWARM_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/swirl_around_target" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SWIRL_TURF", + "required_dist": 0, + "finish_on_arrival": true + } + } + ] + } + ] + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.json new file mode 100644 index 000000000000..aa3b6b5b9360 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.json @@ -0,0 +1,51 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/targeted_ability", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_PET_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.json new file mode 100644 index 000000000000..a993a27f8e50 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.json @@ -0,0 +1,17 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/untargeted_ability", + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_PET_ACTIVE_ABILITY" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_follow_friend.dm b/code/datums/ai/basic_mobs/pet_commands/pet_follow_friend.dm deleted file mode 100644 index 38a6939c9018..000000000000 --- a/code/datums/ai/basic_mobs/pet_commands/pet_follow_friend.dm +++ /dev/null @@ -1,16 +0,0 @@ -/// Just keep following the target until the command is interrupted -/datum/ai_behavior/pet_follow_friend - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - -/datum/ai_behavior/pet_follow_friend/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if (QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/pet_follow_friend/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - if (QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - return AI_BEHAVIOR_DELAY diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_use_targeted_ability.dm b/code/datums/ai/basic_mobs/pet_commands/pet_use_targeted_ability.dm index c90ffa785900..e69de29bb2d1 100644 --- a/code/datums/ai/basic_mobs/pet_commands/pet_use_targeted_ability.dm +++ b/code/datums/ai/basic_mobs/pet_commands/pet_use_targeted_ability.dm @@ -1,39 +0,0 @@ -/// Pet owners can't see their pet's ability cooldowns so we keep attempting to use an ability until we succeed -/datum/ai_behavior/pet_use_ability - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - -/datum/ai_behavior/pet_use_ability/setup(datum/ai_controller/controller, ability_key, target_key) - . = ..() - var/mob/living/target = controller.blackboard[target_key] - if (QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/pet_use_ability/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) - var/datum/action/cooldown/mob_cooldown/ability = controller.blackboard[ability_key] - var/mob/living/target = controller.blackboard[target_key] - if (QDELETED(ability) || QDELETED(target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - var/mob/pawn = controller.pawn - if(QDELETED(pawn) || ability.InterceptClickOn(pawn, null, target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_INSTANT - -/datum/ai_behavior/pet_use_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/pet_use_ability/then_attack - -/datum/ai_behavior/pet_use_ability/then_attack/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) - . = ..() - if(succeeded) - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack, target_key, BB_PET_TARGETING_STRATEGY) - -/datum/ai_behavior/pet_use_ability/then_attack/short_ranged - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - required_distance = 4 - -/datum/ai_behavior/pet_use_ability/then_attack/long_ranged - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - required_distance = 8 diff --git a/code/datums/ai/basic_mobs/pet_commands/play_dead.dm b/code/datums/ai/basic_mobs/pet_commands/play_dead.dm deleted file mode 100644 index 5a7a09430260..000000000000 --- a/code/datums/ai/basic_mobs/pet_commands/play_dead.dm +++ /dev/null @@ -1,26 +0,0 @@ -/// Pretend to be dead -/datum/ai_behavior/play_dead - -/datum/ai_behavior/play_dead/setup(datum/ai_controller/controller) - . = ..() - var/mob/living/basic/basic_pawn = controller.pawn - if(!istype(basic_pawn) || basic_pawn.stat) // Can't act dead if you're dead - return - basic_pawn.emote("deathgasp", intentional=FALSE) - ADD_TRAIT(basic_pawn, TRAIT_FAKEDEATH, BASIC_MOB_DEATH_TRAIT) - basic_pawn.look_dead() - -/datum/ai_behavior/play_dead/perform(seconds_per_tick, datum/ai_controller/controller) - if(SPT_PROB(10, seconds_per_tick)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/play_dead/finish_action(datum/ai_controller/controller, succeeded) - . = ..() - var/mob/living/basic/basic_pawn = controller.pawn - if(QDELETED(basic_pawn) || basic_pawn.stat) // imagine actually dying while playing dead. hell, imagine being the kid waiting for your pup to get back up :( - return - basic_pawn.visible_message(span_notice("[basic_pawn] miraculously springs back to life!")) - REMOVE_TRAIT(basic_pawn, TRAIT_FAKEDEATH, BASIC_MOB_DEATH_TRAIT) - basic_pawn.look_alive() - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) diff --git a/code/datums/ai/basic_mobs/simple_ability.bt.json b/code/datums/ai/basic_mobs/simple_ability.bt.json new file mode 100644 index 000000000000..3a741326a407 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ability", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_combat.bt.json b/code/datums/ai/basic_mobs/simple_ability_combat.bt.json new file mode 100644 index 000000000000..7415f3130c3a --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_combat.bt.json @@ -0,0 +1,101 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ability_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": false, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_WIZARD_SPELL_COOLDOWN", + "cooldown_duration": "WIZARD_SPELL_COOLDOWN" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_TARGETED_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_SECONDARY_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_BLINK_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 3, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_melee.bt.json b/code/datums/ai/basic_mobs/simple_ability_melee.bt.json new file mode 100644 index 000000000000..7bb2f68b2876 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_melee.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ability_melee", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_melee_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_melee_combat.bt.json b/code/datums/ai/basic_mobs/simple_ability_melee_combat.bt.json new file mode 100644 index 000000000000..4da9075ce8a2 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_melee_combat.bt.json @@ -0,0 +1,91 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ability_melee_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_ranged.bt.json b/code/datums/ai/basic_mobs/simple_ability_ranged.bt.json new file mode 100644 index 000000000000..971a7059245c --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_ranged.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ability_ranged", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_ranged_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.json b/code/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.json new file mode 100644 index 000000000000..78d229fec93d --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.json @@ -0,0 +1,82 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ability_ranged_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_retaliate.bt.json b/code/datums/ai/basic_mobs/simple_ability_retaliate.bt.json new file mode 100644 index 000000000000..546fb2ed02ad --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_retaliate.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ability_retaliate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_retaliate_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.json b/code/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.json new file mode 100644 index 000000000000..9f1548488534 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.json @@ -0,0 +1,70 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ability_retaliate_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_BASIC_MOB_RETALIATE_LIST" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 3, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_capricious.bt.json b/code/datums/ai/basic_mobs/simple_capricious.bt.json new file mode 100644 index 000000000000..70ba4d20a29d --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_capricious.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_capricious", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_capricious_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_capricious_combat.bt.json b/code/datums/ai/basic_mobs/simple_capricious_combat.bt.json new file mode 100644 index 000000000000..265658831d87 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_capricious_combat.bt.json @@ -0,0 +1,66 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_capricious_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_BASIC_MOB_RETALIATE_LIST" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/capricious_pick_target" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_fearful.bt.json b/code/datums/ai/basic_mobs/simple_fearful.bt.json new file mode 100644 index 000000000000..d4f2d92caebd --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_fearful.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_fearful", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_fearful_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_fearful_combat.bt.json b/code/datums/ai/basic_mobs/simple_fearful_combat.bt.json new file mode 100644 index 000000000000..e4bca3a86142 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_fearful_combat.bt.json @@ -0,0 +1,41 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_fearful_combat", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_goon.bt.json b/code/datums/ai/basic_mobs/simple_goon.bt.json new file mode 100644 index 000000000000..2add30ed0878 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_goon.bt.json @@ -0,0 +1,15 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_goon", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_hostile.bt.json b/code/datums/ai/basic_mobs/simple_hostile.bt.json new file mode 100644 index 000000000000..9e4077fd8b98 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_hostile.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_hostile", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_hostile_combat.bt.json b/code/datums/ai/basic_mobs/simple_hostile_combat.bt.json new file mode 100644 index 000000000000..537387307936 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_hostile_combat.bt.json @@ -0,0 +1,98 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_hostile_combat", + "bindings": { + "bbrbyj7y": { + "label": "combat_speech_behavior", + "default": "" + }, + "bp3p5vvb": { + "label": "walk_chance", + "default": "25" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + }, + { + "type": "leaf", + "behavior": "$bbrbyj7y" + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": "$bp3p5vvb" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.json b/code/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.json new file mode 100644 index 000000000000..b5a026e01a6a --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.json @@ -0,0 +1,107 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_hostile_combat_with_retaliate", + "bindings": { + "bhvqd9fh": { + "label": "speech loop subtree", + "default": "/datum/bt_node/subtree" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "subtree", + "subtype": "$bhvqd9fh" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_hostile_obstacles.bt.json b/code/datums/ai/basic_mobs/simple_hostile_obstacles.bt.json new file mode 100644 index 000000000000..3ce7b8cac9b8 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_hostile_obstacles.bt.json @@ -0,0 +1,49 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack" + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pick_retaliate_target" + } + ] + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.json b/code/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.json new file mode 100644 index 000000000000..846e921552a1 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.json @@ -0,0 +1,109 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_hostile_obstacles_combat", + "bindings": { + "b95z0f0c": { + "label": "while_attacking_subtree", + "default": "" + }, + "by235tlw": { + "label": "cant_attack_subtree", + "default": "" + }, + "bqul7l8t": { + "label": "speech_subtree", + "default": "/datum/bt_node/subtree" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "subtree", + "subtype": "$b95z0f0c" + } + ] + } + }, + { + "type": "subtree", + "subtype": "$by235tlw" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "$bqul7l8t" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ranged.bt.json b/code/datums/ai/basic_mobs/simple_ranged.bt.json new file mode 100644 index 000000000000..cc8e48db271d --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ranged.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ranged", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ranged_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ranged_combat.bt.json b/code/datums/ai/basic_mobs/simple_ranged_combat.bt.json new file mode 100644 index 000000000000..6da0fd0be51f --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ranged_combat.bt.json @@ -0,0 +1,77 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ranged_combat", + "bindings": { + "bsjjwub2": { + "label": "idle_behavior", + "default": "/datum/bt_node/subtree/random_walk" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "time_between_perform": "0.6 SECONDS", + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "approach_movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "$bsjjwub2" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ranged_retaliate.bt.json b/code/datums/ai/basic_mobs/simple_ranged_retaliate.bt.json new file mode 100644 index 000000000000..92e64b045f15 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ranged_retaliate.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ranged_retaliate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ranged_retaliate_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.json b/code/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.json new file mode 100644 index 000000000000..06747f4e9e8d --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.json @@ -0,0 +1,69 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ranged_retaliate_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_retaliate.bt.json b/code/datums/ai/basic_mobs/simple_retaliate.bt.json new file mode 100644 index 000000000000..1155f2378688 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_retaliate.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_retaliate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_retaliate_combat.bt.json b/code/datums/ai/basic_mobs/simple_retaliate_combat.bt.json new file mode 100644 index 000000000000..105e0ef8b7a1 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_retaliate_combat.bt.json @@ -0,0 +1,87 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_retaliate_combat", + "bindings": { + "b2jvnm5d": { + "label": "check_faction", + "default": "FALSE" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": "$b2jvnm5d" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_skirmisher.bt.json b/code/datums/ai/basic_mobs/simple_skirmisher.bt.json new file mode 100644 index 000000000000..3a671abba85b --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_skirmisher.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_skirmisher", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_skirmisher_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_skirmisher_combat.bt.json b/code/datums/ai/basic_mobs/simple_skirmisher_combat.bt.json new file mode 100644 index 000000000000..6c5d60b593f2 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_skirmisher_combat.bt.json @@ -0,0 +1,92 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_skirmisher_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_skittish.bt.json b/code/datums/ai/basic_mobs/simple_skittish.bt.json new file mode 100644 index 000000000000..36828f42ae53 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_skittish.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_skittish", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_skittish_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_skittish_combat.bt.json b/code/datums/ai/basic_mobs/simple_skittish_combat.bt.json new file mode 100644 index 000000000000..6db93fc782db --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_skittish_combat.bt.json @@ -0,0 +1,41 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_skittish_combat", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] +} diff --git a/code/datums/ai/basic_mobs/talk.bt.json b/code/datums/ai/basic_mobs/talk.bt.json new file mode 100644 index 000000000000..20584cd412b7 --- /dev/null +++ b/code/datums/ai/basic_mobs/talk.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/talk", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" +} diff --git a/code/datums/ai/basic_mobs/target_sources/_target_source.dm b/code/datums/ai/basic_mobs/target_sources/_target_source.dm new file mode 100644 index 000000000000..0a501c5ca131 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/_target_source.dm @@ -0,0 +1,106 @@ +/// Singleton datum that defines how an acquire_target behavior gathers candidates. +/// Subtype collect_candidates() to change what atoms are considered. +/// Subtype with a typecache var to pre-filter by type before targeting_strategy validation. +/datum/target_source + +/// Returns a list of candidate atoms for the behavior to filter and select from. +/datum/target_source/proc/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + return list() + +/// Gathers nearby atoms via oview(). No type pre-filtering. +/datum/target_source/oview + +/datum/target_source/oview/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = oview(range, pawn) + return candidates + +/// Gathers nearby atoms via oview(), pre-filtered by a typecache +/datum/target_source/oview_typed + /// Optional typecache for pre-filtering candidates; null means no type pre-filter. + var/list/typecache + +/datum/target_source/oview_typed/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + if(isnull(typecache)) + CRASH("[pawn] using [controller] ran [src] with no typecache.") + var/list/candidates = typecache_filter_list(oview(range, pawn), typecache) + return candidates + +/// Gathers nearby atoms via oview(), pre-filtered by a typecache stored in a blackboard key. +/// Use this when the typecache varies per mob species (e.g. BB_BASIC_FOODS). +/datum/target_source/oview_typed/from_bb_key + /// Blackboard key whose value is the typecache list to filter by. + var/typecache_key + +/datum/target_source/oview_typed/from_bb_key/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/bb_typecache = controller.blackboard[typecache_key] + var/list/candidates + if(isnull(bb_typecache)) + candidates = oview(range, pawn) + else + candidates = typecache_filter_list(oview(range, pawn), bb_typecache) + return candidates + +/// Gathers nearby atoms via hearers() plus any hostile machines on the same z-level. (I should probably split out the hostile machine part but oh well) +/datum/target_source/hearers + +/datum/target_source/hearers/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = hearers(range, get_turf(pawn)) - pawn + var/turf/mob_turf = get_turf(pawn) + if(mob_turf?.z) + for(var/atom/hostile_machine as anything in GLOB.hostile_machines_by_z[mob_turf.z]) + if(can_see(pawn, hostile_machine, range)) + candidates += hostile_machine + + return candidates + +/// Gathers turfs in range via RANGE_TURFS(). +/datum/target_source/range_turfs + +/datum/target_source/range_turfs/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + return RANGE_TURFS(range, pawn) + +/// Gathers items currently held in the pawn's hands. +/datum/target_source/held_items + +/datum/target_source/held_items/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + return pawn.held_items || list() + +/// Reads a typecache from BB_BASIC_FOODS and filters oview candidates by it. +/// For mobs whose food list varies by species (set in Initialize via set_blackboard_key). +/datum/target_source/oview_typed/from_bb_key/basic_foods + typecache_key = BB_BASIC_FOODS + +/// Reads a typecache from BB_HUNTABLE_PREY and filters oview candidates by it. +/datum/target_source/oview_typed/from_bb_key/huntable_prey + typecache_key = BB_HUNTABLE_PREY + +/// Gathers items the pawn is carrying that match the prey typecache in BB_HUNTABLE_PREY. +/datum/target_source/carried_huntable_prey + +/datum/target_source/carried_huntable_prey/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/prey = controller.blackboard[BB_HUNTABLE_PREY] + if(isnull(prey)) + return list() + return typecache_filter_list(pawn.contents, prey) + +/// Reads candidates directly from a blackboard list. No spatial filtering; range is ignored. +/datum/target_source/from_bb_list + var/list_key + +/datum/target_source/from_bb_list/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + return controller.blackboard[list_key] || list() + +/// Reads from BB_BASIC_MOB_RETALIATE_LIST. +/datum/target_source/from_bb_list/retaliate_list + list_key = BB_BASIC_MOB_RETALIATE_LIST + + +/datum/target_source/oview_items + +/datum/target_source/oview_items/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/obj/item/item_candidate in oview(range, pawn)) + candidates += item_candidate + + return candidates diff --git a/code/datums/ai/basic_mobs/target_sources/held_items_then_oview.dm b/code/datums/ai/basic_mobs/target_sources/held_items_then_oview.dm new file mode 100644 index 000000000000..721a7c09545a --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/held_items_then_oview.dm @@ -0,0 +1,11 @@ +/// Returns the pawn's held items first, then nearby atoms via oview(). Used for food searches that prefer held items. +/datum/target_source/held_items_then_oview + +/datum/target_source/held_items_then_oview/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = list() + for(var/obj/item/candidate_item as anything in pawn.held_items) + if(isnull(candidate_item)) + continue + candidates += candidate_item + candidates += oview(range, pawn) + return candidates diff --git a/code/datums/ai/basic_mobs/target_sources/held_items_typed.dm b/code/datums/ai/basic_mobs/target_sources/held_items_typed.dm new file mode 100644 index 000000000000..3f12cfa3957d --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/held_items_typed.dm @@ -0,0 +1,13 @@ +/// Gathers held items matching a fixed typepath. +/datum/target_source/held_items_typed + var/locate_typepath + +/datum/target_source/held_items_typed/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = list() + for(var/atom/candidate as anything in pawn.held_items) + if(istype(candidate, locate_typepath)) + candidates += candidate + return candidates + +/datum/target_source/held_items_typed/instrument + locate_typepath = /obj/item/instrument diff --git a/code/datums/ai/basic_mobs/target_sources/mobs_in_oview.dm b/code/datums/ai/basic_mobs/target_sources/mobs_in_oview.dm new file mode 100644 index 000000000000..e0d0e34e3f9d --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/mobs_in_oview.dm @@ -0,0 +1,20 @@ +/datum/target_source/oview_living + +/datum/target_source/oview_living/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/mob/living/living_candidate in oview(range, pawn)) + candidates += living_candidate + + return candidates + + +/datum/target_source/oview_raptor_babies + +/datum/target_source/oview_raptor_babies/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/mob/living/basic/raptor/candidate in oview(range, pawn)) + candidates += candidate + + return candidates diff --git a/code/datums/ai/basic_mobs/target_sources/near_village_humans.dm b/code/datums/ai/basic_mobs/target_sources/near_village_humans.dm new file mode 100644 index 000000000000..893fc25a4617 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/near_village_humans.dm @@ -0,0 +1,12 @@ +/// Gathers nearby carbon humans within BB_MAXIMUM_DISTANCE_TO_VILLAGE of the BB_HOME_VILLAGE anchor. +/datum/target_source/near_village_humans + +/datum/target_source/near_village_humans/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/atom/anchor = controller.blackboard[BB_HOME_VILLAGE] + var/max_dist = controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE] || range + var/list/candidates = list() + for(var/mob/living/carbon/human/candidate in oview(max_dist, pawn)) + if(!isnull(anchor) && get_dist(candidate, anchor) > max_dist) + continue + candidates += candidate + return candidates diff --git a/code/datums/ai/basic_mobs/target_sources/oview_single_type.dm b/code/datums/ai/basic_mobs/target_sources/oview_single_type.dm new file mode 100644 index 000000000000..0c8835332a41 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/oview_single_type.dm @@ -0,0 +1,58 @@ +/** + * Gathers all targets of a type in the area. This is a define so we can do cheap loops over oview with astype. Yeah it sucks. + * You can do /datum/target_source/oview_single_type/[sub_type] to use it + */ +#define OVIEW_TARGET_SOURCE(subtype, path) \ +/datum/target_source/oview_single_type/##subtype/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) { \ + var/list/candidates = list(); \ + for(var/##path/candidate in oview(range, pawn)) { \ + candidates += candidate; \ + } \ + return candidates; \ +} + +/// Abstract parent for target sources that return every nearby atom of a single fixed type. +/// Subtypes are generated with the OVIEW_TARGET_SOURCE() macro above. +/datum/target_source/oview_single_type + +OVIEW_TARGET_SOURCE(carbon_mob, mob/living/carbon) +OVIEW_TARGET_SOURCE(human_mob, mob/living/carbon/human) +OVIEW_TARGET_SOURCE(living_mob, mob/living) +OVIEW_TARGET_SOURCE(disposal_unit, obj/machinery/disposal) +OVIEW_TARGET_SOURCE(paper, obj/item/paper) +OVIEW_TARGET_SOURCE(watering_can, obj/item/reagent_containers/cup/watering_can) +OVIEW_TARGET_SOURCE(ore_stand, obj/structure/ore_container/material_stand) +OVIEW_TARGET_SOURCE(flora_tree, obj/structure/flora/tree) +OVIEW_TARGET_SOURCE(vent_pump, obj/machinery/atmospherics/components/unary/vent_pump) +OVIEW_TARGET_SOURCE(tribal_chief, mob/living/basic/mining/mook/worker/tribal_chief) +OVIEW_TARGET_SOURCE(apc, obj/machinery/power/apc) +OVIEW_TARGET_SOURCE(machine, obj/machinery) +OVIEW_TARGET_SOURCE(beehive, obj/structure/beebox) +OVIEW_TARGET_SOURCE(penguin_egg, obj/item/food/egg/penguin_egg) +OVIEW_TARGET_SOURCE(raptor, mob/living/basic/raptor) +OVIEW_TARGET_SOURCE(raptor_trough, obj/structure/ore_container/food_trough/raptor_trough) +OVIEW_TARGET_SOURCE(gutlunch_trough, obj/structure/ore_container/food_trough/gutlunch_trough) +OVIEW_TARGET_SOURCE(mouse, mob/living/basic/mouse) +OVIEW_TARGET_SOURCE(oven, obj/machinery/oven/range) +OVIEW_TARGET_SOURCE(cable, obj/structure/cable) +OVIEW_TARGET_SOURCE(donut, obj/item/food/donut) +OVIEW_TARGET_SOURCE(hydroponics, obj/machinery/hydroponics) +OVIEW_TARGET_SOURCE(cheese, obj/item/food/cheese) +OVIEW_TARGET_SOURCE(piano_synth, obj/item/instrument/piano_synth) +OVIEW_TARGET_SOURCE(orbie, mob/living/basic/orbie) +OVIEW_TARGET_SOURCE(ore_vent, obj/structure/ore_vent) +OVIEW_TARGET_SOURCE(mushroom_food, obj/item/food/grown/mushroom) +OVIEW_TARGET_SOURCE(ore, obj/item/stack/ore) +OVIEW_TARGET_SOURCE(minebot_target, obj/effect/temp_visual/minebot_target) +OVIEW_TARGET_SOURCE(node_drone, mob/living/basic/node_drone) +OVIEW_TARGET_SOURCE(icy_rock, obj/structure/flora/rock/icy) +OVIEW_TARGET_SOURCE(ice_whelp, mob/living/basic/mining/ice_whelp) +OVIEW_TARGET_SOURCE(hivebot, mob/living/basic/hivebot) +OVIEW_TARGET_SOURCE(carrot, obj/item/food/grown/carrotlike/carrot) +OVIEW_TARGET_SOURCE(ants, obj/effect/decal/cleanable/ants) +OVIEW_TARGET_SOURCE(cat_house, obj/structure/cat_house) +OVIEW_TARGET_SOURCE(honeycomb, obj/item/food/honeycomb) +OVIEW_TARGET_SOURCE(kitten, mob/living/basic/pet/cat/kitten) +OVIEW_TARGET_SOURCE(deer_animals, mob/living/basic/deer) + +#undef OVIEW_TARGET_SOURCE diff --git a/code/datums/ai/basic_mobs/target_sources/oview_typed_from_bb_key.dm b/code/datums/ai/basic_mobs/target_sources/oview_typed_from_bb_key.dm new file mode 100644 index 000000000000..49c526fd74cc --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/oview_typed_from_bb_key.dm @@ -0,0 +1,19 @@ +/// Subtypes of oview_typed/from_bb_key for interaction searches. + +/datum/target_source/oview_typed/from_bb_key/stationary_targets + typecache_key = BB_STATIONARY_TARGETS + +/datum/target_source/oview_typed/from_bb_key/mook_heal_targets + typecache_key = BB_MOOK_HEAL_TARGETS + +/datum/target_source/oview_typed/from_bb_key/bonfire_targets + typecache_key = BB_BONFIRE_TARGETS + +/datum/target_source/oview_typed/from_bb_key/hunt_target_list + typecache_key = BB_HUNT_TARGET_LIST + +/datum/target_source/oview_typed/from_bb_key/turtle_headbutt_types + typecache_key = BB_TURTLE_HEADBUTT_TYPES + +/datum/target_source/oview_typed/from_bb_key/turtle_flora_types + typecache_key = BB_TURTLE_FLORA_TYPES diff --git a/code/datums/ai/basic_mobs/target_sources/range_turfs_typecache_visible.dm b/code/datums/ai/basic_mobs/target_sources/range_turfs_typecache_visible.dm new file mode 100644 index 000000000000..d8b2617c2c14 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/range_turfs_typecache_visible.dm @@ -0,0 +1,34 @@ +/// Gathers visible turfs in range matching a typecache. Shuffled for variety. +/datum/target_source/range_turfs/typecache_visible + var/list/typecache + +/datum/target_source/range_turfs/typecache_visible/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/found = RANGE_TURFS(range, pawn) + var/list/valid = list() + for(var/turf/candidate as anything in found) + if(!is_type_in_typecache(candidate, typecache)) + continue + if(can_see(pawn, candidate, range)) + valid += candidate + return valid + +/datum/target_source/range_turfs/typecache_visible/deer_grass + typecache = list(/turf/open/floor/grass, /turf/open/misc/grass) + +/datum/target_source/range_turfs/typecache_visible/deer_grass/New() + . = ..() + typecache = typecacheof(typecache) + +/datum/target_source/range_turfs/typecache_visible/deer_water + typecache = list(/turf/open/water) + +/datum/target_source/range_turfs/typecache_visible/deer_water/New() + . = ..() + typecache = typecacheof(typecache) + +/datum/target_source/range_turfs/typecache_visible/ice + typecache = list(/turf/open/misc/ice) + +/datum/target_source/range_turfs/typecache_visible/ice/New() + . = ..() + typecache = typecacheof(typecache) diff --git a/code/datums/ai/basic_mobs/target_sources/slime_source.dm b/code/datums/ai/basic_mobs/target_sources/slime_source.dm new file mode 100644 index 000000000000..f4597cedde39 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/slime_source.dm @@ -0,0 +1,11 @@ +/datum/target_source/oview_living_no_slimes + +/datum/target_source/oview_living_no_slimes/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/mob/living/living_candidate in oview(range, pawn)) + if(isslime(living_candidate)) + continue + candidates += living_candidate + + return candidates diff --git a/code/datums/ai/basic_mobs/target_sources/turfs_in_oview.dm b/code/datums/ai/basic_mobs/target_sources/turfs_in_oview.dm new file mode 100644 index 000000000000..5dbf4f8ba91e --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/turfs_in_oview.dm @@ -0,0 +1,23 @@ + +/datum/target_source/oview_water_turfs + +/datum/target_source/oview_water_turfs/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/turf/open/water/water_turf in oview(range, pawn)) + candidates += water_turf + + return candidates + + +/datum/target_source/oview_land_turfs + +/datum/target_source/oview_water_turfs/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/turf/open/potential_floor in oview(range, pawn)) + if(iswaterturf(potential_floor)) + continue + candidates += potential_floor + + return candidates diff --git a/code/datums/ai/basic_mobs/targeting_strategies/_targeting_strategy.dm b/code/datums/ai/basic_mobs/targeting_strategies/_targeting_strategy.dm index 5720602c6fc7..9e6e61cab28c 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/_targeting_strategy.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/_targeting_strategy.dm @@ -2,25 +2,41 @@ ///Global, just like ai_behaviors /datum/targeting_strategy -///Returns true or false depending on if the target can be attacked by the mob -/datum/targeting_strategy/proc/can_attack(mob/living/living_mob, atom/target, vision_range) - return +///Returns true or false depending on if the target can be attacked by the mob. +///Base proc checks if target is within vision_range distance. +/datum/targeting_strategy/proc/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + if(QDELETED(target)) + return FALSE -///Returns something the target might be hiding inside of + if(!vision_range) + return TRUE + + return get_dist(living_mob, target) <= vision_range + +/// Returns an atom the target might be hiding inside of, or null if none. /datum/targeting_strategy/proc/find_hidden_mobs(mob/living/living_mob, atom/target) - var/atom/target_hiding_location - if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper)) - target_hiding_location = target.loc - return target_hiding_location + return null + +/// Returns TRUE if we should keep tracking an existing target when no new candidates are visible. +/// Called with the loss range (typically larger than vision_range). +/// Default delegates to is_valid_target so all normal checks still apply. +/datum/targeting_strategy/proc/can_keep_target(mob/living/living_mob, atom/target, range, datum/ai_controller/controller = null) + return is_valid_target(living_mob, target, range, controller) /// Simply always returns true if you have a target, so only use this if you're pre-checking the targets somewhere else /datum/targeting_strategy/anything -/datum/targeting_strategy/anything/can_attack(mob/living/living_mob, atom/target, vision_range) - return !!target +/datum/targeting_strategy/anything/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + return TRUE ///A very simple targeting strategy that checks that the target is a valid fishing spot. /datum/targeting_strategy/fishing -/datum/targeting_strategy/fishing/can_attack(mob/living/living_mob, atom/target, vision_range) +/datum/targeting_strategy/fishing/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE return HAS_TRAIT(target, TRAIT_FISHING_SPOT) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/accessible_cable.dm b/code/datums/ai/basic_mobs/targeting_strategies/accessible_cable.dm new file mode 100644 index 000000000000..7a62b9d4c02b --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/accessible_cable.dm @@ -0,0 +1,14 @@ +/// Accepts visible cables sitting on accessible open floor tiles. +/datum/targeting_strategy/accessible_cable + +/datum/targeting_strategy/accessible_cable/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/structure/cable/candidate = target + if(!istype(candidate) || !can_see(living_mob, candidate, vision_range)) + return FALSE + var/turf/open/floor/below_the_cable = get_turf(candidate) + if(!istype(below_the_cable)) + return FALSE + return below_the_cable.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/ally_mob.dm b/code/datums/ai/basic_mobs/targeting_strategies/ally_mob.dm new file mode 100644 index 000000000000..e881ccafd5d3 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/ally_mob.dm @@ -0,0 +1,10 @@ +/// Accepts visible mobs that are allies of the pawn (and not the pawn itself). +/datum/targeting_strategy/ally_mob + +/datum/targeting_strategy/ally_mob/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(target == living_mob || !living_mob.has_ally(target)) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/baby_raptor.dm b/code/datums/ai/basic_mobs/targeting_strategies/baby_raptor.dm new file mode 100644 index 000000000000..bcdcdce2fdf4 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/baby_raptor.dm @@ -0,0 +1,11 @@ +/// Accepts visible, living baby raptors. ONLY USE ON BABY RAPTORS WE ASSUME IT IS ONE +/datum/targeting_strategy/healthy_raptor_baby + +/datum/targeting_strategy/healthy_raptor_baby/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/raptor/candidate = target + if(candidate.stat == DEAD || candidate.growth_stage != RAPTOR_BABY) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm index be1d1484225c..a75a9c79b4c4 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm @@ -1,20 +1,37 @@ /datum/targeting_strategy/basic + ///Whether we ignore faction checks + var/ignore_target_status = FALSE /// When we do our basic faction check, do we look for exact faction matches? var/check_factions_exactly = FALSE /// Whether we care for seeing the target or not var/ignore_sight = FALSE + /// Whether we skip faction checks entirely, never rejecting a target over factions + var/ignore_faction = FALSE + /// If TRUE the faction check is inverted, making us only target mobs that DO share a faction with us + var/invert_faction_check = FALSE + /// Set to TRUE on subtypes that override faction_check() with custom logic, routing the check through the proc instead of the inlined macro + var/custom_faction_check = FALSE /// Blackboard key containing the minimum stat of a living mob to target var/minimum_stat_key = BB_TARGET_MINIMUM_STAT /// If this blackboard key is TRUE, makes us only target wounded mobs var/target_wounded_key -/datum/targeting_strategy/basic/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) + // checks are ordered cheapest first so invalid targets are rejected before the expensive sight check + if(isturf(the_target) || isnull(the_target)) // bail out on invalids + return FALSE + var/datum/ai_controller/basic_controller/our_controller = living_mob.ai_controller if(isnull(our_controller)) return FALSE - if(isturf(the_target) || isnull(the_target)) // bail out on invalids + if(living_mob.see_invisible < the_target.invisibility) //Target's invisible to us, forget it + return FALSE + + if(!isturf(living_mob.loc)) + return FALSE + if(isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting return FALSE if(isobj(the_target.loc)) @@ -31,35 +48,27 @@ if (vision_range && get_dist(living_mob, the_target) > vision_range) return FALSE - if(!ignore_sight && !can_see(living_mob, the_target, vision_range)) //Target has moved behind cover and we have lost line of sight to it - return FALSE - - if(living_mob.see_invisible < the_target.invisibility) //Target's invisible to us, forget it - return FALSE - - if(!isturf(living_mob.loc)) - return FALSE - if(isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting - return FALSE - if(isliving(the_target)) //Targeting vs living mobs - var/mob/living/living_target = the_target - if(faction_check(our_controller, living_mob, living_target)) - return FALSE - if(living_target.stat > our_controller.blackboard[minimum_stat_key]) - return FALSE - if(target_wounded_key && our_controller.blackboard[target_wounded_key] && living_target.health == living_target.maxHealth) - return FALSE - - return TRUE - - if(ismecha(the_target)) //Targeting vs mechas + if(!ignore_target_status) + var/mob/living/living_target = the_target + if(custom_faction_check ? faction_check(our_controller, living_mob, living_target) : TARGETING_FACTION_CHECK(src, our_controller, living_mob, living_target)) + return FALSE + if(living_target.stat > our_controller.blackboard[minimum_stat_key]) + return FALSE + if(target_wounded_key && our_controller.blackboard[target_wounded_key] && living_target.health == living_target.maxHealth) + return FALSE + + else if(ismecha(the_target)) //Targeting vs mechas var/obj/vehicle/sealed/mecha/M = the_target + var/valid_occupant = FALSE for(var/occupant in M.occupants) - if(can_attack(living_mob, occupant)) //Can we attack any of the occupants? - return TRUE + if(is_valid_target(living_mob, occupant)) //Can we attack any of the occupants? + valid_occupant = TRUE + break + if(!valid_occupant) + return FALSE - if(istype(the_target, /obj/machinery/porta_turret)) //Cringe turret! kill it! + else if(istype(the_target, /obj/machinery/porta_turret)) //Cringe turret! kill it! var/obj/machinery/porta_turret/P = the_target if(P.in_faction(living_mob)) //Don't attack if the turret is in the same faction return FALSE @@ -67,21 +76,33 @@ return FALSE if(P.machine_stat & BROKEN) //Or turrets that are already broken return FALSE - return TRUE - return FALSE + else //Not a type of thing we can target + return FALSE -/// Returns true if the mob and target share factions -/datum/targeting_strategy/basic/proc/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) - if (controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || controller.blackboard[BB_TEMPORARILY_IGNORE_FACTION]) + if(!ignore_sight && !can_see(living_mob, the_target, vision_range)) //Sight check goes last, it's by far the most expensive return FALSE - return living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly) + return TRUE + +/datum/targeting_strategy/basic/find_hidden_mobs(mob/living/living_mob, atom/target) + . = ..() + if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper)) + return target.loc + return null + +/datum/targeting_strategy/basic/can_keep_target(mob/living/living_mob, atom/target, range) + return can_see(living_mob, target, range) + +/// Returns true if the mob and target share factions. +/// Slow path for subtypes with custom_faction_check set; everything else uses TARGETING_FACTION_CHECK directly +/datum/targeting_strategy/basic/proc/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) + return TARGETING_FACTION_CHECK(src, controller, living_mob, the_target) /// Subtype more forgiving for items. /// Careful, this can go wrong and keep a mob hyper-focused on an item it can't lose aggro on /datum/targeting_strategy/basic/allow_items -/datum/targeting_strategy/basic/allow_items/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/allow_items/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) . = ..() if(isitem(the_target)) // trust fall exercise @@ -89,7 +110,7 @@ /datum/targeting_strategy/basic/require_traits -/datum/targeting_strategy/basic/require_traits/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/require_traits/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) . = ..() if (!.) return FALSE @@ -109,7 +130,7 @@ /// If true, we will return mobs which are the same size as us. var/inclusive = TRUE -/datum/targeting_strategy/basic/of_size/can_attack(mob/living/owner, atom/target, vision_range) +/datum/targeting_strategy/basic/of_size/is_valid_target(mob/living/owner, atom/target, vision_range, datum/ai_controller/controller = null) if(!isliving(target)) return FALSE . = ..() @@ -136,13 +157,11 @@ /// Makes the mob only attack their own faction. Useful mostly if their attacks do something helpful (e.g. healing touch). /datum/targeting_strategy/basic/same_faction - -/datum/targeting_strategy/basic/same_faction/faction_check(mob/living/living_mob, mob/living/the_target) - return !..() // inverts logic to ONLY target mobs that share a faction + invert_faction_check = TRUE /datum/targeting_strategy/basic/allow_turfs -/datum/targeting_strategy/basic/allow_turfs/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/allow_turfs/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) if(isturf(the_target)) return TRUE return ..() @@ -150,7 +169,7 @@ /// Subtype which searches for mobs that havent been gutted by megafauna /datum/targeting_strategy/basic/no_gutted_mobs -/datum/targeting_strategy/basic/no_gutted_mobs/can_attack(mob/living/owner, mob/living/target, vision_range) +/datum/targeting_strategy/basic/no_gutted_mobs/is_valid_target(mob/living/owner, mob/living/target, vision_range, datum/ai_controller/controller = null) if(!istype(target) || target.has_status_effect(/datum/status_effect/gutted)) return FALSE return ..() @@ -160,7 +179,7 @@ /datum/targeting_strategy/basic/exact_match/ignore_friends -/datum/targeting_strategy/basic/exact_match/ignore_friends/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/exact_match/ignore_friends/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) . = ..() if (!.) return FALSE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/beamable_hydro.dm b/code/datums/ai/basic_mobs/targeting_strategies/beamable_hydro.dm new file mode 100644 index 000000000000..f86c5e65c29e --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/beamable_hydro.dm @@ -0,0 +1,11 @@ +/// Accepts hydroponics trays whose plant health is below the seed's endurance threshold. +/datum/targeting_strategy/beamable_hydro + +/datum/targeting_strategy/beamable_hydro/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/hydroponics/hydro = target + if(!istype(hydro) || isnull(hydro.myseed)) + return FALSE + return hydro.plant_health < hydro.myseed.endurance diff --git a/code/datums/ai/basic_mobs/targeting_strategies/befriendable_cultist.dm b/code/datums/ai/basic_mobs/targeting_strategies/befriendable_cultist.dm new file mode 100644 index 000000000000..8b0f1dafb636 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/befriendable_cultist.dm @@ -0,0 +1,13 @@ +/// Accepts carbon cultists that the pawn has not already befriended. +/datum/targeting_strategy/befriendable_cultist + +/datum/targeting_strategy/befriendable_cultist/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!iscarbon(target)) + return FALSE + var/mob/living/carbon/carbon_target = target + if(!IS_CULTIST(carbon_target)) + return FALSE + return !living_mob.has_ally(carbon_target) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/cat_food.dm b/code/datums/ai/basic_mobs/targeting_strategies/cat_food.dm new file mode 100644 index 000000000000..f5e1e98feecf --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/cat_food.dm @@ -0,0 +1,12 @@ +/// Accepts visible food that has no kitten (other than the pawn) nearby. +/// Pair with a food typecache source (e.g. oview_typed/from_bb_key/basic_foods). +/datum/targeting_strategy/cat_food + +/datum/targeting_strategy/cat_food/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/nearby_kitten = locate(/mob/living/basic/pet/cat/kitten) in oview(2, target) + if(nearby_kitten && nearby_kitten != living_mob) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/chargeable_apc.dm b/code/datums/ai/basic_mobs/targeting_strategies/chargeable_apc.dm new file mode 100644 index 000000000000..601e790c95db --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/chargeable_apc.dm @@ -0,0 +1,14 @@ +/// Accepts APCs that have a cell which is not at full charge, and are visible to the pawn. +/datum/targeting_strategy/chargeable_apc + +/datum/targeting_strategy/chargeable_apc/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/power/apc/candidate = target + if(!istype(candidate) || !candidate.cell) + return FALSE + var/obj/item/stock_parts/power_store/cell/apc_cell = candidate.cell + if(apc_cell.charge == apc_cell.maxcharge) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/conscious_human.dm b/code/datums/ai/basic_mobs/targeting_strategies/conscious_human.dm new file mode 100644 index 000000000000..e9154e85cd90 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/conscious_human.dm @@ -0,0 +1,13 @@ +/// Targets conscious human carbons with a mind. Used for interaction targets (traders, etc.). +/datum/targeting_strategy/conscious_human + +/datum/targeting_strategy/conscious_human/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!istype(the_target, /mob/living/carbon/human)) + return FALSE + var/mob/living/carbon/human/human_target = the_target + if(IS_DEAD_OR_INCAP(human_target) || !human_target.mind) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/conscious_mob.dm b/code/datums/ai/basic_mobs/targeting_strategies/conscious_mob.dm new file mode 100644 index 000000000000..17bac1f3dbc2 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/conscious_mob.dm @@ -0,0 +1,11 @@ +/// Accepts living mobs that are not dead or incapacitated, and visible to the pawn. +/datum/targeting_strategy/conscious_mob + +/datum/targeting_strategy/conscious_mob/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate) || IS_DEAD_OR_INCAP(candidate)) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/conscious_snail.dm b/code/datums/ai/basic_mobs/targeting_strategies/conscious_snail.dm new file mode 100644 index 000000000000..37924ca37fc2 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/conscious_snail.dm @@ -0,0 +1,13 @@ +/// Accepts visible, conscious carbon mobs of the snail species. +/datum/targeting_strategy/conscious_snail + +/datum/targeting_strategy/conscious_snail/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/carbon/candidate = target + if(!istype(candidate) || candidate.stat != CONSCIOUS) + return FALSE + if(!is_species(candidate, /datum/species/snail)) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/damaged_eyes.dm b/code/datums/ai/basic_mobs/targeting_strategies/damaged_eyes.dm new file mode 100644 index 000000000000..3b10ad9579fc --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/damaged_eyes.dm @@ -0,0 +1,17 @@ +/// Accepts carbon mobs whose eyes are damaged at or beyond the controller's BB_EYE_DAMAGE_THRESHOLD. +/datum/targeting_strategy/damaged_eyes + +/datum/targeting_strategy/damaged_eyes/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!iscarbon(target)) + return FALSE + var/threshold = controller?.blackboard[BB_EYE_DAMAGE_THRESHOLD] + if(!threshold) + return FALSE + var/mob/living/carbon/carbon_target = target + var/obj/item/organ/eyes/eyes = carbon_target.get_organ_slot(ORGAN_SLOT_EYES) + if(isnull(eyes) || eyes.damage < threshold) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/damaged_machine.dm b/code/datums/ai/basic_mobs/targeting_strategies/damaged_machine.dm new file mode 100644 index 000000000000..459733c7e885 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/damaged_machine.dm @@ -0,0 +1,11 @@ +/// Accepts machinery below max integrity that is visible to the pawn. +/datum/targeting_strategy/damaged_machine + +/datum/targeting_strategy/damaged_machine/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/candidate = target + if(!istype(candidate) || candidate.get_integrity() >= candidate.max_integrity) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/dead_mob.dm b/code/datums/ai/basic_mobs/targeting_strategies/dead_mob.dm new file mode 100644 index 000000000000..0cce3346d4b0 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/dead_mob.dm @@ -0,0 +1,20 @@ +/// Accepts dead living mobs that are visible to the pawn. +/datum/targeting_strategy/dead_mob + +/datum/targeting_strategy/dead_mob/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate) || candidate.stat != DEAD) + return FALSE + return TRUE + +/// As dead_mob, but rejects corpses already being dragged by something. +/datum/targeting_strategy/dead_mob/not_pulled + +/datum/targeting_strategy/dead_mob/not_pulled/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + var/mob/living/candidate = target + if(isliving(candidate) && candidate.pulledby) + return FALSE + return ..() diff --git a/code/datums/ai/basic_mobs/targeting_strategies/decorated_donut.dm b/code/datums/ai/basic_mobs/targeting_strategies/decorated_donut.dm new file mode 100644 index 000000000000..b6bb107bb7f9 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/decorated_donut.dm @@ -0,0 +1,11 @@ +/// Accepts decorated donuts that are visible to the pawn. +/datum/targeting_strategy/decorated_donut + +/datum/targeting_strategy/decorated_donut/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/item/food/donut/candidate = target + if(!istype(candidate) || !candidate.is_decorated) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm b/code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm index 268b4ca88428..e13d02fdccee 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm @@ -1,5 +1,6 @@ /// Don't target an atom in our friends list (or turfs), anything else is fair game /datum/targeting_strategy/basic/not_friends + ignore_faction = TRUE //friends dont care about factions /// Stop regarding someone as a valid target once they pass this stat level, setting it to DEAD means you will happily attack corpses var/attack_until_past_stat = HARD_CRIT /// If we can try to closed turfs or not @@ -8,7 +9,7 @@ var/attack_obj = FALSE ///Returns true or false depending on if the target can be attacked by the mob -/datum/targeting_strategy/basic/not_friends/can_attack(mob/living/living_mob, atom/target, vision_range) +/datum/targeting_strategy/basic/not_friends/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) if(attack_closed_turf && isclosedturf(target)) return TRUE @@ -21,10 +22,6 @@ return ..() -///friends dont care about factions -/datum/targeting_strategy/basic/not_friends/faction_check(mob/living/living_mob, mob/living/the_target) - return FALSE - /datum/targeting_strategy/basic/not_friends/attack_closed_turfs attack_closed_turf = TRUE @@ -40,7 +37,7 @@ /// Subtype that allows us to target items while deftly avoiding attacking our allies. Be careful when it comes to targeting items as an AI could get trapped targeting something it can't destroy. /datum/targeting_strategy/basic/not_friends/allow_items -/datum/targeting_strategy/basic/not_friends/allow_items/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/not_friends/allow_items/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) . = ..() if(isitem(the_target)) // trust fall exercise diff --git a/code/datums/ai/basic_mobs/targeting_strategies/drillable_ice.dm b/code/datums/ai/basic_mobs/targeting_strategies/drillable_ice.dm new file mode 100644 index 000000000000..1f092ce20d60 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/drillable_ice.dm @@ -0,0 +1,12 @@ +/// Accepts ice turfs that can have a hole made in them and are visible to the pawn. +/// Pair with a range_turfs source. +/datum/targeting_strategy/drillable_ice + +/datum/targeting_strategy/drillable_ice/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/turf/open/misc/ice/candidate = target + if(!istype(candidate) || !candidate.can_make_hole) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/empty_paper.dm b/code/datums/ai/basic_mobs/targeting_strategies/empty_paper.dm new file mode 100644 index 000000000000..8c0145c6f815 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/empty_paper.dm @@ -0,0 +1,11 @@ +/// Accepts pieces of paper that have nothing written on them yet. +/datum/targeting_strategy/empty_paper + +/datum/targeting_strategy/empty_paper/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/item/paper/candidate = target + if(!istype(candidate) || !candidate.is_empty()) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/finished_stove.dm b/code/datums/ai/basic_mobs/targeting_strategies/finished_stove.dm new file mode 100644 index 000000000000..c8d49aa68a67 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/finished_stove.dm @@ -0,0 +1,14 @@ +/// Accepts closed ovens whose tray holds finished (no longer bakeable) goods. +/datum/targeting_strategy/finished_stove + +/datum/targeting_strategy/finished_stove/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/oven/range/candidate = target + if(!istype(candidate) || candidate.open || !length(candidate.used_tray?.contents)) + return FALSE + for(var/atom/baking as anything in candidate.used_tray) + if(HAS_TRAIT(baking, TRAIT_BAKEABLE)) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/food_or_drink.dm b/code/datums/ai/basic_mobs/targeting_strategies/food_or_drink.dm new file mode 100644 index 000000000000..6233824fdc0b --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/food_or_drink.dm @@ -0,0 +1,30 @@ +/// Accepts items that are edible or consumable from a bowl, or drinks when allowed by the controller blackboard. +/datum/targeting_strategy/pickup_item/food_or_drink + +/datum/targeting_strategy/pickup_item/food_or_drink/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/find_drinks = controller?.blackboard[BB_IGNORE_DRINKS] + return _is_food(target) || (find_drinks && _is_drink(target)) + +/datum/targeting_strategy/pickup_item/food_or_drink/proc/_is_food(obj/item/thing) + if(IS_EDIBLE(thing)) + return TRUE + if(istype(thing, /obj/item/reagent_containers/cup/bowl)) + return thing.reagents.total_volume > 0 + return FALSE + +/datum/targeting_strategy/pickup_item/food_or_drink/proc/_is_drink(obj/item/thing) + if(istype(thing, /obj/item/reagent_containers/cup/glass)) + return thing.reagents.total_volume > 0 + return FALSE + +/// Like food_or_drink but always accepts drinks regardless of BB_IGNORE_DRINKS. +/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks + +/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + return _is_food(target) || _is_drink(target) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/goliath_diggable_turf.dm b/code/datums/ai/basic_mobs/targeting_strategies/goliath_diggable_turf.dm new file mode 100644 index 000000000000..cc7d01b4a8e6 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/goliath_diggable_turf.dm @@ -0,0 +1,11 @@ +/// Accepts undig asteroid turfs. Pair with a range_turfs source. +/datum/targeting_strategy/goliath_diggable_turf + +/datum/targeting_strategy/goliath_diggable_turf/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/turf/open/misc/asteroid/candidate = target + if(!istype(candidate) || candidate.dug) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/goose_edible.dm b/code/datums/ai/basic_mobs/targeting_strategies/goose_edible.dm new file mode 100644 index 000000000000..b2c52be2d368 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/goose_edible.dm @@ -0,0 +1,11 @@ +/// Accepts items that are edible or made of plastic. Used by goose eating. +/datum/targeting_strategy/goose_edible + +/datum/targeting_strategy/goose_edible/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/item/thing = target + if(!isitem(thing)) + return FALSE + return IS_EDIBLE(thing) || thing.has_material_type(/datum/material/plastic) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/huntable.dm b/code/datums/ai/basic_mobs/targeting_strategies/huntable.dm new file mode 100644 index 000000000000..e7ae75556d03 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/huntable.dm @@ -0,0 +1,12 @@ +/// Accepts any visible target that isn't a dead mob. Mirrors the legacy hunt finder's validity check. +/datum/targeting_strategy/huntable + +/datum/targeting_strategy/huntable/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(isliving(target)) + var/mob/living/living_target = target + if(living_target.stat == DEAD) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/huntable_mouse.dm b/code/datums/ai/basic_mobs/targeting_strategies/huntable_mouse.dm new file mode 100644 index 000000000000..ee688815b796 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/huntable_mouse.dm @@ -0,0 +1,11 @@ +/// Accepts living, mindless mice that are visible to the pawn. +/datum/targeting_strategy/huntable_mouse + +/datum/targeting_strategy/huntable_mouse/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate) || candidate.stat == DEAD || candidate.mind) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/injured_mob.dm b/code/datums/ai/basic_mobs/targeting_strategies/injured_mob.dm new file mode 100644 index 000000000000..6afb807c2d11 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/injured_mob.dm @@ -0,0 +1,19 @@ +/// Accepts mobs below their maximum health (no line-of-sight requirement). +/datum/targeting_strategy/injured_mob + +/datum/targeting_strategy/injured_mob/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate)) + return FALSE + return candidate.health < candidate.maxHealth + +/// As injured_mob, but never accepts the searching pawn itself. +/datum/targeting_strategy/injured_mob/not_self + +/datum/targeting_strategy/injured_mob/not_self/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + if(target == living_mob) + return FALSE + return ..() diff --git a/code/datums/ai/basic_mobs/targeting_strategies/injured_raptor.dm b/code/datums/ai/basic_mobs/targeting_strategies/injured_raptor.dm new file mode 100644 index 000000000000..36c7a2b8cae9 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/injured_raptor.dm @@ -0,0 +1,6 @@ +/datum/targeting_strategy/injured_mob/not_self/injured_raptor + +/datum/targeting_strategy/injured_mob/not_self/injured_raptor/is_valid_target(mob/living/living_mob, mob/living/basic/raptor/target, vision_range, datum/ai_controller/controller = null) + if(!istype(target)) + return FALSE + return ..() diff --git a/code/datums/ai/basic_mobs/targeting_strategies/legged_conscious_human.dm b/code/datums/ai/basic_mobs/targeting_strategies/legged_conscious_human.dm new file mode 100644 index 000000000000..97294b667701 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/legged_conscious_human.dm @@ -0,0 +1,13 @@ +/// Accepts conscious humans who have at least one leg and are visible to the pawn. +/datum/targeting_strategy/legged_conscious_human + +/datum/targeting_strategy/legged_conscious_human/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/carbon/human/candidate = target + if(!istype(candidate) || candidate.stat != CONSCIOUS) + return FALSE + if(isnull(candidate.get_bodypart(BODY_ZONE_R_LEG)) && isnull(candidate.get_bodypart(BODY_ZONE_L_LEG))) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/living_not_dead.dm b/code/datums/ai/basic_mobs/targeting_strategies/living_not_dead.dm new file mode 100644 index 000000000000..e712d70f3889 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/living_not_dead.dm @@ -0,0 +1,12 @@ +/// Accepts living mobs that are not dead and visible to the pawn. Skips faction checks, +/// so it's suitable for finding allies (e.g. a hivebot looking for another hivebot). +/datum/targeting_strategy/living_not_dead + +/datum/targeting_strategy/living_not_dead/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate) || candidate.stat == DEAD) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/non_stump_tree.dm b/code/datums/ai/basic_mobs/targeting_strategies/non_stump_tree.dm new file mode 100644 index 000000000000..c969e8d24122 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/non_stump_tree.dm @@ -0,0 +1,10 @@ +/// Accepts trees that are not stumps and are visible to the pawn. +/datum/targetingUI_strategy/non_stump_tree + +/datum/targeting_strategy/non_stump_tree/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(istype(target, /obj/structure/flora/tree/stump)) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/pickup_item.dm b/code/datums/ai/basic_mobs/targeting_strategies/pickup_item.dm new file mode 100644 index 000000000000..dc7efb60626d --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/pickup_item.dm @@ -0,0 +1,10 @@ +///Ensures the item is actually on a turf, else we can't go there to pick it up! +/datum/targeting_strategy/pickup_item + +/datum/targeting_strategy/pickup_item/is_valid_target(mob/living/living_mob, obj/item/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!isturf(target.loc) && !living_mob.is_holding(target)) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/playable_deer.dm b/code/datums/ai/basic_mobs/targeting_strategies/playable_deer.dm new file mode 100644 index 000000000000..e140b8ee2fd9 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/playable_deer.dm @@ -0,0 +1,11 @@ +/// Accepts living (non-dead) deer that are visible to the pawn. +/datum/targeting_strategy/playable_deer + +/datum/targeting_strategy/playable_deer/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate) || candidate.stat == DEAD) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/playable_synthesizer.dm b/code/datums/ai/basic_mobs/targeting_strategies/playable_synthesizer.dm new file mode 100644 index 000000000000..3c83efdd4f60 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/playable_synthesizer.dm @@ -0,0 +1,12 @@ +/// Accepts a visible piano synthesizer sitting on the floor (not a wearable headphones variant). +/datum/targeting_strategy/playable_synthesizer + +/datum/targeting_strategy/playable_synthesizer/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(target.type != /obj/item/instrument/piano_synth) + return FALSE + if(!isturf(target.loc)) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/pollinatable_hydro.dm b/code/datums/ai/basic_mobs/targeting_strategies/pollinatable_hydro.dm new file mode 100644 index 000000000000..44b785485034 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/pollinatable_hydro.dm @@ -0,0 +1,11 @@ +/// Accepts visible hydroponics trays a bee can currently pollinate. +/datum/targeting_strategy/pollinatable_hydro + +/datum/targeting_strategy/pollinatable_hydro/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/hydroponics/candidate = target + if(!istype(candidate) || !candidate.can_bee_pollinate()) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/raptor_trough.dm b/code/datums/ai/basic_mobs/targeting_strategies/raptor_trough.dm new file mode 100644 index 000000000000..5f51cf840086 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/raptor_trough.dm @@ -0,0 +1,10 @@ +/// Looks for filled troughs raptors can eat from +/datum/targeting_strategy/raptor_trough + +/datum/targeting_strategy/raptor_trough/is_valid_target(mob/living/living_mob, obj/structure/ore_container/food_trough/raptor_trough/trough, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!can_see(living_mob, trough, vision_range)) + return FALSE + return !!(locate(/obj/item/stack/ore) in trough.contents) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/slime_food.dm b/code/datums/ai/basic_mobs/targeting_strategies/slime_food.dm new file mode 100644 index 000000000000..8f63df2315c7 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/slime_food.dm @@ -0,0 +1,44 @@ +/// Accepts edible targets for slimes based on hunger level, faction, and species. +/// Requires the controller (reads slime hunger/rabid/current-target blackboard keys). +/datum/targeting_strategy/slime_food + +/datum/targeting_strategy/slime_food/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!controller) + return FALSE + // Anything that attacked us is a valid target even if we can't eat it. + if(target in controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST]) + var/datum/targeting_strategy/retaliate_strategy = GET_TARGETING_STRATEGY(/datum/targeting_strategy/basic/not_friends) + return retaliate_strategy.is_valid_target(living_mob, target, vision_range) + + var/mob/living/basic/slime/hunter = living_mob + var/mob/living/candidate = target + if(!isliving(candidate)) + return FALSE + + // We're already latched onto them and feeding; don't invalidate our own meal. + if(hunter.buckled == candidate) + return TRUE + + var/static/list/slime_faction + if(isnull(slime_faction)) + slime_faction = string_list(list(FACTION_SLIME)) + + if(FAST_FACTION_CHECK(slime_faction, candidate.get_faction(), hunter.allies, candidate.allies, FALSE)) + return FALSE + + if(!hunter.can_feed_on(candidate, check_adjacent = FALSE)) + return FALSE + + if(candidate == controller.blackboard[BB_CURRENT_TARGET]) + return can_see(hunter, candidate, vision_range) + + if(controller.blackboard[BB_SLIME_HUNGER_LEVEL] == SLIME_HUNGER_STARVING && controller.blackboard[BB_SLIME_RABID]) + return can_see(hunter, candidate, vision_range) + + if(islarva(candidate) || ismonkey(candidate) || ishuman(candidate) || isalienadult(candidate)) + return can_see(hunter, candidate, vision_range) + + return FALSE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/sniffable_hydro.dm b/code/datums/ai/basic_mobs/targeting_strategies/sniffable_hydro.dm new file mode 100644 index 000000000000..61ab84289f18 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/sniffable_hydro.dm @@ -0,0 +1,15 @@ +/// Accepts hydroponics trays with a growing seed that are not too weedy or pest-ridden, and visible. +/datum/targeting_strategy/sniffable_hydro + +/datum/targeting_strategy/sniffable_hydro/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/hydroponics/candidate = target + if(!istype(candidate)) + return FALSE + if(isnull(candidate.myseed)) + return FALSE + if(candidate.weedlevel > 5 || candidate.pestlevel > 5) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/stealable_item.dm b/code/datums/ai/basic_mobs/targeting_strategies/stealable_item.dm new file mode 100644 index 000000000000..15c0128be6c2 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/stealable_item.dm @@ -0,0 +1,11 @@ +/// Accepts visible items that aren't anchored or already being pulled. +/datum/targeting_strategy/pickup_item/stealable_item + +/datum/targeting_strategy/pickup_item/stealable_item/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/item/candidate = target + if(!isitem(candidate) || candidate.anchored || candidate.pulledby) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/stocked_beehive.dm b/code/datums/ai/basic_mobs/targeting_strategies/stocked_beehive.dm new file mode 100644 index 000000000000..7b21dc743943 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/stocked_beehive.dm @@ -0,0 +1,11 @@ +/// Accepts beehives that contain at least one honeycomb and are visible to the pawn. +/datum/targeting_strategy/stocked_beehive + +/datum/targeting_strategy/stocked_beehive/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/structure/beebox/candidate = target + if(!istype(candidate) || !length(candidate.honeycombs)) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/treatable_hydro.dm b/code/datums/ai/basic_mobs/targeting_strategies/treatable_hydro.dm new file mode 100644 index 000000000000..79f922bfb3c3 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/treatable_hydro.dm @@ -0,0 +1,19 @@ +/// Accepts hydroponics trays that need watering (if pawn holds a watering can) or weeding/dead plant removal. +/// Reads thresholds from the controller blackboard. +/datum/targeting_strategy/treatable_hydro + +/datum/targeting_strategy/treatable_hydro/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/hydroponics/hydro = target + if(!istype(hydro) || isnull(hydro.myseed)) + return FALSE + if(!controller) + return FALSE + var/waterlevel_threshold = controller.blackboard[BB_WATERLEVEL_THRESHOLD] + var/weedlevel_threshold = controller.blackboard[BB_WEEDLEVEL_THRESHOLD] + if(hydro.waterlevel < waterlevel_threshold) + if(locate(/obj/item/reagent_containers/cup/watering_can) in living_mob) + return TRUE + return hydro.weedlevel > weedlevel_threshold || hydro.plant_status == HYDROTRAY_PLANT_DEAD diff --git a/code/datums/ai/basic_mobs/targeting_strategies/trough_with_ore.dm b/code/datums/ai/basic_mobs/targeting_strategies/trough_with_ore.dm new file mode 100644 index 000000000000..10a2781bd980 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/trough_with_ore.dm @@ -0,0 +1,10 @@ +/// Accepts containers (e.g. troughs) that hold at least one ore item. +/datum/targeting_strategy/trough_with_ore + +/datum/targeting_strategy/trough_with_ore/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!ismovable(target)) + return FALSE + return !!(locate(/obj/item/stack/ore) in target.contents) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/unbroken_light.dm b/code/datums/ai/basic_mobs/targeting_strategies/unbroken_light.dm new file mode 100644 index 000000000000..9289e0de4faf --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/unbroken_light.dm @@ -0,0 +1,11 @@ +/// Accepts light fixtures that are not already broken and are visible to the pawn. +/datum/targeting_strategy/unbroken_light + +/datum/targeting_strategy/unbroken_light/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/light/candidate = target + if(!istype(candidate) || candidate.status == LIGHT_BROKEN) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/uncarried_egg.dm b/code/datums/ai/basic_mobs/targeting_strategies/uncarried_egg.dm new file mode 100644 index 000000000000..4cf778ac7103 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/uncarried_egg.dm @@ -0,0 +1,10 @@ +/// Accepts visible penguin eggs the pawn is not already carrying. +/datum/targeting_strategy/uncarried_egg + +/datum/targeting_strategy/uncarried_egg/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(target in living_mob.contents) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/unlit_bonfire.dm b/code/datums/ai/basic_mobs/targeting_strategies/unlit_bonfire.dm new file mode 100644 index 000000000000..f9cf2e6dbb18 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/unlit_bonfire.dm @@ -0,0 +1,11 @@ +/// Accepts bonfires that are not currently burning and are visible to the pawn. +/datum/targeting_strategy/unlit_bonfire + +/datum/targeting_strategy/unlit_bonfire/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/structure/bonfire/candidate = target + if(!istype(candidate) || candidate.burning) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/valid_cat_home.dm b/code/datums/ai/basic_mobs/targeting_strategies/valid_cat_home.dm new file mode 100644 index 000000000000..7ce512001522 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/valid_cat_home.dm @@ -0,0 +1,11 @@ +/// Accepts cat houses that don't already have a resident cat. +/datum/targeting_strategy/valid_cat_home + +/datum/targeting_strategy/valid_cat_home/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/structure/cat_house/home = target + if(!istype(home) || home.resident_cat) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/valid_kitten.dm b/code/datums/ai/basic_mobs/targeting_strategies/valid_kitten.dm new file mode 100644 index 000000000000..55056a0b5b46 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/valid_kitten.dm @@ -0,0 +1,15 @@ +/// Accepts living kittens that don't already have huntable food sitting next to them. +/// Reads the prey typecache from BB_HUNTABLE_PREY on the searching controller. +/datum/targeting_strategy/valid_kitten + +/datum/targeting_strategy/valid_kitten/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/kitten = target + if(!isliving(kitten) || kitten.stat == DEAD) + return FALSE + var/list/prey = controller?.blackboard[BB_HUNTABLE_PREY] + if(prey && length(typecache_filter_list(oview(2, kitten), prey))) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/walkable_turf.dm b/code/datums/ai/basic_mobs/targeting_strategies/walkable_turf.dm new file mode 100644 index 000000000000..bfb6618236b1 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/walkable_turf.dm @@ -0,0 +1,9 @@ +/// Base strategy for checking if a turf is walkable +/datum/targeting_strategy/walkable_turf + +/datum/targeting_strategy/walkable_turf/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/turf/candidate = target + return !candidate.is_blocked_turf() diff --git a/code/datums/ai/basic_mobs/targeting_strategies/water_dispenser.dm b/code/datums/ai/basic_mobs/targeting_strategies/water_dispenser.dm new file mode 100644 index 000000000000..3de583899d4e --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/water_dispenser.dm @@ -0,0 +1,13 @@ +/// Accepts sinks or reagent dispensers that contain water and are visible to the pawn. +/datum/targeting_strategy/water_dispenser + +/datum/targeting_strategy/water_dispenser/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/atom/movable/dispenser = target + if(!istype(dispenser, /obj/structure/sink) && !istype(dispenser, /obj/structure/reagent_dispensers)) + return FALSE + if(!dispenser.reagents || !(locate(/datum/reagent/water) in dispenser.reagents.reagent_list)) + return FALSE + return can_see(living_mob, dispenser, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm b/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm index 7cc76d3010c9..e255eef33647 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm @@ -8,11 +8,13 @@ /datum/targeting_strategy/basic/holding_object /// BB key that holds the target typepath to use var/target_item_key = BB_TARGET_HELD_ITEM + ///We dont care that they're dead, if they got tongs theyre bad + ignore_target_status = TRUE ///Returns true or false depending on if the target can be attacked by the mob -/datum/targeting_strategy/basic/holding_object/can_attack(mob/living/living_mob, atom/target, vision_range) - var/datum/ai_controller/controller = living_mob.ai_controller - var/object_type_path = controller.blackboard[target_item_key] +/datum/targeting_strategy/basic/holding_object/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + var/datum/ai_controller/our_controller = living_mob.ai_controller + var/object_type_path = our_controller.blackboard[target_item_key] if (object_type_path == null) return FALSE // no op @@ -22,8 +24,8 @@ // Look at me, type casting like a grown up var/mob/targetmob = target // Check if our parent behaviour agrees we can attack this target (we ignore faction by default) - var/can_attack = ..() - if(can_attack && targetmob.is_holding_item_of_type(object_type_path)) + var/is_valid_target = ..() + if(is_valid_target && targetmob.is_holding_item_of_type(object_type_path)) return TRUE // they have the item // No valid target return FALSE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/working_machine.dm b/code/datums/ai/basic_mobs/targeting_strategies/working_machine.dm new file mode 100644 index 000000000000..9f982d1bdf68 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/working_machine.dm @@ -0,0 +1,11 @@ +/// Accepts machinery that is not broken and is visible to the pawn. +/datum/targeting_strategy/working_machine + +/datum/targeting_strategy/working_machine/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/candidate = target + if(!istype(candidate) || (candidate.machine_stat & BROKEN)) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/bots/bot_decorators.dm b/code/datums/ai/bots/bot_decorators.dm new file mode 100644 index 000000000000..e36668a3a029 --- /dev/null +++ b/code/datums/ai/bots/bot_decorators.dm @@ -0,0 +1,45 @@ +/// Gates child on pawn being emagged. Use invert = TRUE for the opposite. Checked each tick. +/datum/bt_node/decorator/bot_is_emagged + +/datum/bt_node/decorator/bot_is_emagged/check_condition(datum/ai_controller/controller) + var/mob/living/basic/bot/bot_pawn = controller.pawn + return !!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) + +/// Gates child on pawn having the specified bot_mode_flag. Observes COMSIG_BOT_MODE_FLAGS_SET. +/datum/bt_node/decorator/bot_mode_flag + var/flag + +/datum/bt_node/decorator/bot_mode_flag/register_observe_signals(atom/pawn) + RegisterSignal(pawn, COMSIG_BOT_MODE_FLAGS_SET, PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/bot_mode_flag/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, COMSIG_BOT_MODE_FLAGS_SET) + +/datum/bt_node/decorator/bot_mode_flag/check_condition(datum/ai_controller/controller) + var/mob/living/basic/bot/bot_pawn = controller.pawn + return !!(bot_pawn.bot_mode_flags & flag) + +/// Gates child on the pawn's current mode matching `mode` (e.g. BOT_DELIVER). Use invert = TRUE for the opposite. Checked each tick. +/datum/bt_node/decorator/bot_mode + var/mode + +/datum/bt_node/decorator/bot_mode/check_condition(datum/ai_controller/controller) + var/mob/living/basic/bot/bot_pawn = controller.pawn + return bot_pawn.mode == mode + +/// Gates child on the pawn's `wire` being cut. Use invert = TRUE to gate on the wire being intact. Checked each tick. +/datum/bt_node/decorator/bot_wire_cut + var/wire + +/datum/bt_node/decorator/bot_wire_cut/check_condition(datum/ai_controller/controller) + var/mob/living/basic/bot/bot_pawn = controller.pawn + return !!(bot_pawn.wires?.is_cut(wire)) + +/// Gates child when pawn has the specified medical mode flag. Use invert = TRUE for the opposite. Checked each tick. +/datum/bt_node/decorator/bot_medical_flag + var/flag + +/datum/bt_node/decorator/bot_medical_flag/check_condition(datum/ai_controller/controller) + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn + return !!(bot_pawn.medical_mode_flags & flag) diff --git a/code/datums/ai/bots/bot_patrol.bt.json b/code/datums/ai/bots/bot_patrol.bt.json new file mode 100644 index 000000000000..9d6f1f84067a --- /dev/null +++ b/code/datums/ai/bots/bot_patrol.bt.json @@ -0,0 +1,88 @@ +{ + "dm_type": "/datum/bt_node/subtree/bot_patrol", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BOT_BEACON_COOLDOWN" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_mode_flag", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "flag": "BOT_MODE_AUTOPATROL" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_PREVIOUS_BEACON_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_next_beacon_target", + "vars": { + "target_key": "BB_BEACON_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_first_beacon_target", + "vars": { + "target_key": "BB_BEACON_TARGET" + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BEACON_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BEACON_TARGET", + "required_dist": 0, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/arrive_at_beacon", + "vars": { + "target_key": "BB_BEACON_TARGET" + } + } + ] + } + } + ] + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": "1 SECONDS" + } + } + ] +} diff --git a/code/datums/ai/bots/bot_respond_to_summon.bt.json b/code/datums/ai/bots/bot_respond_to_summon.bt.json new file mode 100644 index 000000000000..7d0661fa7e6a --- /dev/null +++ b/code/datums/ai/bots/bot_respond_to_summon.bt.json @@ -0,0 +1,29 @@ +{ + "dm_type": "/datum/bt_node/subtree/bot_respond_to_summon", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BOT_SUMMON_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BOT_SUMMON_TARGET", + "required_dist": 0, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/complete_summon_travel", + "vars": { + "target_key": "BB_BOT_SUMMON_TARGET" + } + } + ] + } +} diff --git a/code/datums/ai/bots/bot_salute_authority.bt.json b/code/datums/ai/bots/bot_salute_authority.bt.json new file mode 100644 index 000000000000..b7a6609f6378 --- /dev/null +++ b/code/datums/ai/bots/bot_salute_authority.bt.json @@ -0,0 +1,29 @@ +{ + "dm_type": "/datum/bt_node/subtree/bot_salute_authority", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_SALUTE_COOLDOWN", + "cooldown_duration": "60 SECONDS" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_valid_authority", + "vars": { + "target_key": "BB_SALUTE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/salute_authority", + "vars": { + "target_key": "BB_SALUTE_TARGET", + "salute_keys": "BB_SALUTE_MESSAGES" + } + } + ] + } +} diff --git a/code/datums/ai/bots/bot_subtrees.dm b/code/datums/ai/bots/bot_subtrees.dm new file mode 100644 index 000000000000..efabf9984da7 --- /dev/null +++ b/code/datums/ai/bots/bot_subtrees.dm @@ -0,0 +1,281 @@ +#define BOT_NO_BEACON_PATH_PENALTY 30 SECONDS + +/** + * Searches for a valid target in oview and sets a blackboard key when found. + * Subtypes override valid_target() to refine selection criteria. + * looking_for is an optional typecache pre-filter; pass null to check all atoms via valid_target(). + */ +/datum/bt_node/ai_behavior/bot_search + var/target_key + var/looking_for = null + var/radius = 5 + var/pathing_distance = 10 + var/bypass_add_blacklist = FALSE + var/turf_search = FALSE + /// How close the path must get to the target (0 = onto/adjacent). Repairbot raises this so it stops next to the walls/girders it repairs. + var/minimum_distance = 0 + time_between_perform = 2 SECONDS + /// Stashed candidate list between perform() and the async worker (not a blackboard value). + VAR_PRIVATE/list/candidate_stash + +/datum/bt_node/ai_behavior/bot_search/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + if(!istype(controller)) + stack_trace("attempted to give [controller.pawn] the bot search behavior!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/async_flags = handle_async() + if(async_flags) + return async_flags + + if(isnull(looking_for)) + looking_for = get_looking_for_typecache() + + // Build candidate list synchronously (no sleeping), then hand off to async. + var/mob/living/living_pawn = controller.pawn + var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] + var/list/candidates = list() + for(var/atom/potential_target as anything in (turf_search ? RANGE_TURFS(radius, controller.pawn) : oview(radius, controller.pawn))) + if(!isnull(looking_for) && !is_type_in_typecache(potential_target, looking_for)) + continue + if(LAZYACCESS(ignore_list, potential_target)) + continue + if(!valid_target(controller, potential_target)) + continue + if(!can_see(controller.pawn, potential_target, radius)) + continue + candidates += potential_target + + if(!length(candidates)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[living_pawn] bot_search ([type]): no valid target found in radius [radius]") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + candidate_stash = candidates + return start_async() + +/datum/bt_node/ai_behavior/bot_search/perform_async(datum/ai_controller/basic_controller/bot/controller) + var/mob/living/living_pawn = controller.pawn + var/found = FALSE + for(var/atom/potential_target as anything in candidate_stash) + if(!async_still_valid()) + break + if(controller.set_if_can_reach(key = target_key, target = potential_target, distance = pathing_distance, bypass_add_to_blacklist = bypass_add_blacklist, minimum_distance = minimum_distance)) + found = TRUE + break + if(!async_still_valid()) + return + if(!found) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[living_pawn] bot_search ([type]): no reachable target found") + finish_async(found ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/datum/bt_node/ai_behavior/bot_search/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + candidate_stash = null + +/datum/bt_node/ai_behavior/bot_search/proc/get_looking_for_typecache() + return + +/datum/bt_node/ai_behavior/bot_search/proc/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + return TRUE + + +///Performs bot speech from a list of options +/datum/bt_node/ai_behavior/bot_speech + var/list/list_to_pick_from + var/announce_key + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/bot_speech/perform(seconds_per_tick, datum/ai_controller/controller) + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[announce_key] + if(isnull(announcement) || !length(list_to_pick_from)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + announcement.announce(pick(list_to_pick_from)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + +///Interact with an object. Could probably be moved to a generic behavior as the only unique thing is the blacklist. +/datum/bt_node/ai_behavior/bot_interact + var/target_key + var/clear_target = TRUE + +/datum/bt_node/ai_behavior/bot_interact/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(get_dist(living_pawn, target) > 1) + return AI_BEHAVIOR_INSTANT + living_pawn.UnarmedAttack(target, proximity_flag = TRUE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/bot_interact/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(clear_target) + controller.clear_blackboard_key(target_key) + if(!succeeded && !isnull(target)) + controller.add_to_blacklist(target) + +/// Variant that keeps the target key after interacting (caller must clear it). +/datum/bt_node/ai_behavior/bot_interact/keep_target + clear_target = FALSE + + +/// Searches GLOB.deliverybeacons for a beacon whose location matches the tag in tag_key, and sets it as target_key. +/datum/bt_node/ai_behavior/find_delivery_beacon + var/target_key + /// Blackboard key holding the location tag string to match against beacon.location. + var/tag_key + time_between_perform = 2 SECONDS + +/datum/bt_node/ai_behavior/find_delivery_beacon/perform(seconds_per_tick, datum/ai_controller/controller) + var/beacon_tag = controller.blackboard[tag_key] + if(isnull(beacon_tag)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + for(var/obj/machinery/navbeacon/beacon as anything in GLOB.deliverybeacons) + if(beacon.location != beacon_tag) + continue + controller.set_blackboard_key(target_key, beacon) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +///Find the closest beacon and set it as the target +/datum/bt_node/ai_behavior/find_first_beacon_target + var/target_key + +/datum/bt_node/ai_behavior/find_first_beacon_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/closest_distance = INFINITY + var/mob/living/basic/bot/bot_pawn = controller.pawn + var/atom/final_target + var/atom/previous_target = controller.blackboard[BB_PREVIOUS_BEACON_TARGET] + for(var/obj/machinery/navbeacon/beacon as anything in GLOB.navbeacons["[bot_pawn.z]"]) + var/dist = get_dist(bot_pawn, beacon) + if(beacon == previous_target || dist <= 1) + continue + if(dist > closest_distance) + continue + closest_distance = dist + final_target = beacon + + if(isnull(final_target)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] find_first_beacon_target: no beacon found on z=[bot_pawn.z] (previous=[previous_target])") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] first beacon target: [final_target]", get_turf(final_target), "Beacon") + EVLOG_LINES(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "Beacon path", get_turf(bot_pawn), get_turf(final_target)) + controller.set_blackboard_key(target_key, final_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +///Find the next beacon from a previous target and set it as the new target +/datum/bt_node/ai_behavior/find_next_beacon_target + var/target_key + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/find_next_beacon_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + + var/mob/living/basic/bot/bot_pawn = controller.pawn + var/obj/machinery/navbeacon/prev_beacon = controller.blackboard[BB_PREVIOUS_BEACON_TARGET] + if(QDELETED(prev_beacon)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] find_next_beacon_target: previous beacon is deleted") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/atom/final_target + for(var/obj/machinery/navbeacon/beacon as anything in GLOB.navbeacons["[bot_pawn.z]"]) + if(beacon.location == prev_beacon.codes[NAVBEACON_PATROL_NEXT]) + final_target = beacon + break + + if(isnull(final_target)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] find_next_beacon_target: no beacon with location=[prev_beacon.codes[NAVBEACON_PATROL_NEXT]] (prev=[prev_beacon])") + controller.clear_blackboard_key(BB_PREVIOUS_BEACON_TARGET) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(BB_PREVIOUS_BEACON_TARGET, final_target) + controller.set_blackboard_key(target_key, final_target) + return AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/find_next_beacon_target/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + + +/// Records the beacon as visited and clears the target key once the bot is on the same turf. +/datum/bt_node/ai_behavior/arrive_at_beacon + var/target_key + +/datum/bt_node/ai_behavior/arrive_at_beacon/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + var/obj/machinery/navbeacon/beacon = controller.blackboard[target_key] + if(QDELETED(beacon)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(get_dist(controller.pawn, beacon) > 0) + return AI_BEHAVIOR_INSTANT + controller.set_blackboard_key(BB_PREVIOUS_BEACON_TARGET, beacon) + controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +/// Completes summon travel once the bot reaches the summon target's turf. +/datum/bt_node/ai_behavior/complete_summon_travel + var/target_key + +/datum/bt_node/ai_behavior/complete_summon_travel/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/bot/bot_pawn = controller.pawn + if(QDELETED(bot_pawn)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/atom/target = controller.blackboard[target_key] + if(get_dist(bot_pawn, target) > 0) + return AI_BEHAVIOR_INSTANT + bot_pawn.calling_ai_ref = null + bot_pawn.update_bot_mode(new_mode = BOT_IDLE) + controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +///Find a valid authority to salute and set them as the target +/datum/bt_node/ai_behavior/find_valid_authority + var/target_key + +/datum/bt_node/ai_behavior/find_valid_authority/perform(seconds_per_tick, datum/ai_controller/controller) + for(var/mob/living/nearby_mob in oview(7, controller.pawn)) + if(!HAS_TRAIT(nearby_mob, TRAIT_COMMISSIONED)) + continue + controller.set_blackboard_key(target_key, nearby_mob) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +///Salute the authority /(o.o) +/datum/bt_node/ai_behavior/salute_authority + var/target_key + var/salute_keys + +/datum/bt_node/ai_behavior/salute_authority/perform(seconds_per_tick, datum/ai_controller/controller) + if(!controller.blackboard_key_exists(target_key)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/list/salute_list = controller.blackboard[salute_keys] + if(!length(salute_list)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/basic/bot/bot_pawn = controller.pawn + var/obj/item/our_hat = (locate(/obj/item/clothing/head) in bot_pawn) + if(our_hat) + salute_list += "tips [our_hat] at " + bot_pawn.manual_emote(pick(salute_list) + " [controller.blackboard[target_key]]!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/salute_authority/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + + +/// Travel to BB_BOT_SUMMON_TARGET if set, completing when on the same turf. +/datum/bt_node/subtree/bot_respond_to_summon + behavior_tree_json = "code/datums/ai/bots/bot_respond_to_summon.bt.json" + +/// Salute any commissioned officer in range +/datum/bt_node/subtree/bot_salute_authority + behavior_tree_json = "code/datums/ai/bots/bot_salute_authority.bt.json" + +/** + * Patrol to navbeacons in sequence when autopatrol is enabled and not on cooldown. + * Priority: travel to current target -> find next in chain -> find first (nearest) beacon. + */ +/datum/bt_node/subtree/bot_patrol + behavior_tree_json = "code/datums/ai/bots/bot_patrol.bt.json" + + +#undef BOT_NO_BEACON_PATH_PENALTY diff --git a/code/datums/ai/bt_viewer.dm b/code/datums/ai/bt_viewer.dm new file mode 100644 index 000000000000..04106fca6d15 --- /dev/null +++ b/code/datums/ai/bt_viewer.dm @@ -0,0 +1,161 @@ +GLOBAL_DATUM_INIT(bt_viewer, /datum/bt_viewer, new()) + +/datum/bt_viewer + /// The controller currently being viewed. + var/datum/ai_controller/viewing_controller = null + /// The mob owning the controller. + var/mob/viewing_mob = null + /// TRUE while waiting for admin to click a mob. + var/awaiting_pick = FALSE + /// The admin waiting to pick, used to unregister the click signal. + var/mob/awaiting_pick_user = null + +/datum/bt_viewer/Destroy() + _clear_target() + _end_pick() + return ..() + +/datum/bt_viewer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "BehaviorTreeViewer", "Behavior Tree Viewer") + ui.open() + +/datum/bt_viewer/ui_close(mob/user) + . = ..() + if(awaiting_pick_user == user) + _end_pick() + +/datum/bt_viewer/ui_state(mob/user) + return ADMIN_STATE(R_DEBUG) + +/datum/bt_viewer/ui_assets(mob/user) + return list(get_asset_datum(/datum/asset/simple/plane_background)) + +/datum/bt_viewer/ui_data(mob/user) + var/list/data = list() + data["mob_name"] = viewing_mob ? viewing_mob.name : null + data["controller_type"] = viewing_controller ? "[viewing_controller.type]" : null + data["active_execution_index"] = viewing_controller ? viewing_controller.active_execution_index : 0 + if(viewing_controller?.bt_execution_log != null) + data["fired_indices"] = viewing_controller.bt_execution_log.Copy() + viewing_controller.bt_execution_log.Cut() //Clear the list every time we update + else + data["fired_indices"] = list() + data["awaiting_pick"] = awaiting_pick + if(viewing_controller) + var/list/root_indices = list() + var/list/node_list = list() + collect_nodes(root_indices, node_list) + data["roots"] = root_indices + data["nodes"] = node_list + var/list/bb_entries = list() + var/list/bb = viewing_controller.blackboard + for(var/key in bb) + var/value = bb[key] + var/str_val + if(isnull(value)) + str_val = "null" + else if(islist(value)) + str_val = "list([length(value)])" + else + str_val = "[value]" + bb_entries += list(list("key" = key, "value" = str_val)) + data["blackboard"] = bb_entries + else + data["roots"] = list() + data["nodes"] = list() + data["blackboard"] = list() + return data + +/datum/bt_viewer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + var/mob/user = ui.user + switch(action) + if("pick_target") + _end_pick() + awaiting_pick = TRUE + awaiting_pick_user = user + RegisterSignal(user, COMSIG_MOB_CLICKON, PROC_REF(on_pick_click)) + return TRUE + if("clear") + _clear_target() + return TRUE + +/datum/bt_viewer/proc/on_pick_click(mob/source, atom/clicked, list/modifiers) + SIGNAL_HANDLER + _end_pick() + + var/atom/target = clicked + if(!target.ai_controller) + return NONE + set_target(target) + return NONE + +/datum/bt_viewer/proc/set_target(mob/target) + _clear_target() + viewing_mob = target + viewing_controller = target.ai_controller + viewing_controller.bt_execution_log = list() + RegisterSignal(viewing_mob, COMSIG_PREQDELETED, PROC_REF(on_mob_deleted)) + +/datum/bt_viewer/proc/on_mob_deleted(datum/source) + SIGNAL_HANDLER + _clear_target() + +/datum/bt_viewer/proc/_clear_target() + if(viewing_mob) + UnregisterSignal(viewing_mob, COMSIG_PREQDELETED) + if(viewing_controller) + viewing_controller.bt_execution_log = null + viewing_mob = null + viewing_controller = null + +/datum/bt_viewer/proc/_end_pick() + awaiting_pick = FALSE + if(awaiting_pick_user) + UnregisterSignal(awaiting_pick_user, COMSIG_MOB_CLICKON) + awaiting_pick_user = null + +/datum/bt_viewer/proc/collect_nodes(list/root_indices, list/node_list) + var/priority = 1 + for(var/datum/bt_node/root as anything in viewing_controller.behavior_nodes) + root_indices += root.execution_index + _collect_node(root, node_list, priority++) + +// Recursively adds node and all descendants to node_list as flat entries. +// Each entry has exec_index as its unique key, with children as a flat list of child exec_indices. +/datum/bt_viewer/proc/_collect_node(datum/bt_node/node, list/node_list, priority_index) + var/exec = node.execution_index || 0 + var/last = node.last_execution_index || 0 + + var/list/node_data = list( + "exec_index" = exec, + "label" = node.label, + "node_type" = node.node_type, + "priority" = priority_index, + ) + if(last != exec) + node_data["last_exec_index"] = last + + if(node.node_type == BT_NODE_DECORATOR) + var/datum/bt_node/decorator/dec = node + if(dec.observer_abort) + node_data["observer_abort"] = dec.observer_abort + if(dec.invert) + node_data["invert"] = TRUE + + var/list/children = node.get_children() + if(length(children)) + var/list/child_indices = list() + for(var/i in 1 to length(children)) + var/datum/bt_node/child = children[i] + if(!child) + continue + child_indices += child.execution_index + _collect_node(child, node_list, i) + node_data["children"] = child_indices + + node_list += list(node_data) diff --git a/code/datums/ai/cursed/cursed.bt.json b/code/datums/ai/cursed/cursed.bt.json new file mode 100644 index 000000000000..9ec75cdfe204 --- /dev/null +++ b/code/datums/ai/cursed/cursed.bt.json @@ -0,0 +1,72 @@ +{ + "dm_type": "/datum/ai_controller/cursed", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_HAUNT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": 20, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/throw_attack/haunted", + "vars": { + "target_key": "BB_HAUNT_TARGET", + "throw_count_key": "BB_HAUNTED_THROW_ATTEMPT_COUNT", + "haunt_list_key": "BB_TO_HAUNT_LIST" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HAUNT_TARGET", + "required_dist": 3, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_ghost_item" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURSE_TARGET", + "target_source": "/datum/target_source/oview_single_type/carbon_mob", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": "CURSED_VIEW_RANGE" + } + } + ] +} diff --git a/code/datums/ai/cursed/cursed_behaviors.dm b/code/datums/ai/cursed/cursed_behaviors.dm index 63e7310f968e..76c991e08d7c 100644 --- a/code/datums/ai/cursed/cursed_behaviors.dm +++ b/code/datums/ai/cursed/cursed_behaviors.dm @@ -1,7 +1,8 @@ -/datum/ai_behavior/item_move_close_and_attack/ghostly/cursed +/// Cursed variant: keeps target if still in range when throws are exhausted. +/datum/bt_node/ai_behavior/throw_attack/cursed -/datum/ai_behavior/item_move_close_and_attack/ghostly/cursed/reset_blackboard(datum/ai_controller/controller, succeeded, target_key, throw_count_key) - var/atom/throw_target = controller.blackboard[target_key] - //dropping our target from the blackboard if they are no longer a valid target after the attack behavior +/datum/bt_node/ai_behavior/throw_attack/cursed/on_throws_exhausted(datum/ai_controller/controller, atom/throw_target, target_key, throw_count_key) + controller.set_blackboard_key(throw_count_key, 0) if(get_dist(throw_target, controller.pawn) > CURSED_VIEW_RANGE) controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED diff --git a/code/datums/ai/cursed/cursed_controller.dm b/code/datums/ai/cursed/cursed_controller.dm index aa32496f3572..ba38888c3ea3 100644 --- a/code/datums/ai/cursed/cursed_controller.dm +++ b/code/datums/ai/cursed/cursed_controller.dm @@ -6,14 +6,13 @@ * Added by /datum/element/cursed, and as such will try to remove this element and go dormant when it finds a victim to curse */ /datum/ai_controller/cursed + behavior_tree_json = "code/datums/ai/cursed/cursed.bt.json" movement_delay = 0.4 SECONDS blackboard = list( BB_CURSE_TARGET, BB_TARGET_SLOT, BB_CURSED_THROW_ATTEMPT_COUNT ) - planning_subtrees = list(/datum/ai_planning_subtree/cursed) - idle_behavior = /datum/idle_behavior/idle_ghost_item /datum/ai_controller/cursed/TryPossessPawn(atom/new_pawn) if(!isitem(new_pawn)) diff --git a/code/datums/ai/cursed/cursed_subtrees.dm b/code/datums/ai/cursed/cursed_subtrees.dm deleted file mode 100644 index dd33312900ab..000000000000 --- a/code/datums/ai/cursed/cursed_subtrees.dm +++ /dev/null @@ -1,13 +0,0 @@ -/datum/ai_planning_subtree/cursed/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/obj/item/item_pawn = controller.pawn - - //make sure we have a target - var/mob/living/carbon/curse_target = controller.blackboard[BB_CURSE_TARGET] - if(!curse_target) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_CURSE_TARGET, /mob/living/carbon, CURSED_VIEW_RANGE) - return - //make sure attack is valid - if(get_dist(curse_target, item_pawn) > CURSED_VIEW_RANGE) - controller.clear_blackboard_key(BB_CURSE_TARGET) - return - controller.queue_behavior(/datum/ai_behavior/item_move_close_and_attack/ghostly/cursed, BB_CURSE_TARGET) diff --git a/code/datums/ai/dog/dog.bt.json b/code/datums/ai/dog/dog.bt.json new file mode 100644 index 000000000000..102947488c03 --- /dev/null +++ b/code/datums/ai/dog/dog.bt.json @@ -0,0 +1,44 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/dog", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/dog_harassment" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_dog" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/datums/ai/dog/dog_behaviors.dm b/code/datums/ai/dog/dog_behaviors.dm deleted file mode 100644 index dc031a1d3f11..000000000000 --- a/code/datums/ai/dog/dog_behaviors.dm +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Pursue the target, growl if we're close, and bite if we're adjacent - * Dogs are actually not very aggressive and won't attack unless you approach them - * Adds a floor to the melee damage of the dog, as most pet dogs don't actually have any melee strength - */ -/datum/ai_behavior/basic_melee_attack/dog - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - required_distance = 3 - -/datum/ai_behavior/basic_melee_attack/dog/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - controller.behavior_cooldowns[src] = world.time + get_cooldown(controller) - var/mob/living/living_pawn = controller.pawn - if(!(isturf(living_pawn.loc) || HAS_TRAIT(living_pawn, TRAIT_AI_BAGATTACK))) // Void puppies can attack from inside bags - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - // Unfortunately going to repeat this check in parent call but what can you do - var/atom/target = controller.blackboard[target_key] - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if (!targeting_strategy.can_attack(living_pawn, target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - if (!living_pawn.Adjacent(target)) - growl_at(living_pawn, target, seconds_per_tick) - return AI_BEHAVIOR_INSTANT - - if(!controller.blackboard[BB_DOG_HARASS_HARM]) - paw_harmlessly(living_pawn, target, seconds_per_tick) - return AI_BEHAVIOR_INSTANT - - . = ..() // Bite time - - return AI_BEHAVIOR_DELAY - -/// Swat at someone we don't like but won't hurt -/datum/ai_behavior/basic_melee_attack/dog/proc/paw_harmlessly(mob/living/living_pawn, atom/target, seconds_per_tick) - if(!SPT_PROB(20, seconds_per_tick)) - return - living_pawn.do_attack_animation(target, ATTACK_EFFECT_DISARM) - playsound(target, 'sound/items/weapons/thudswoosh.ogg', 50, TRUE, -1) - target.visible_message(span_danger("[living_pawn] paws ineffectually at [target]!"), span_danger("[living_pawn] paws ineffectually at you!")) - -/// Let them know we mean business -/datum/ai_behavior/basic_melee_attack/dog/proc/growl_at(mob/living/living_pawn, atom/target, seconds_per_tick) - if(!SPT_PROB(15, seconds_per_tick)) - return - living_pawn.manual_emote("[pick("barks", "growls", "stares")] menacingly at [target]!") - if(!SPT_PROB(40, seconds_per_tick)) - return - playsound(living_pawn, SFX_GROWL, 50, TRUE, -1) diff --git a/code/datums/ai/dog/dog_bt.dm b/code/datums/ai/dog/dog_bt.dm new file mode 100644 index 000000000000..fc5573faef79 --- /dev/null +++ b/code/datums/ai/dog/dog_bt.dm @@ -0,0 +1,113 @@ + +/** + * BT version of basic_melee_attack/dog. + * When adjacent and BB_DOG_HARASS_HARM = FALSE: paws harmlessly (animation, no damage). + * When adjacent and BB_DOG_HARASS_HARM = TRUE: bites normally. + * Returns FAILURE if targeting strategy rejects the target. + */ +/datum/bt_node/ai_behavior/basic_melee_attack/dog + +/datum/bt_node/ai_behavior/basic_melee_attack/dog/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!(isturf(living_pawn.loc) || HAS_TRAIT(living_pawn, TRAIT_AI_BAGATTACK))) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/atom/target = controller.blackboard[target_key] + + if(!ispath(targeting_strategy)) + targeting_strategy = controller.blackboard[targeting_strategy] + + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(targeting_strategy) + if(QDELETED(target) || !strategy?.is_valid_target(living_pawn, target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + if(!controller.blackboard[BB_DOG_HARASS_HARM]) + paw_harmlessly(living_pawn, target, seconds_per_tick) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return ..() + +/// Swat at someone we don't like but won't hurt +/datum/bt_node/ai_behavior/basic_melee_attack/dog/proc/paw_harmlessly(mob/living/living_pawn, atom/target, seconds_per_tick) + if(!SPT_PROB(20, seconds_per_tick)) + return + living_pawn.do_attack_animation(target, ATTACK_EFFECT_DISARM) + playsound(target, 'sound/items/weapons/thudswoosh.ogg', 50, TRUE, -1) + target.visible_message(span_danger("[living_pawn] paws ineffectually at [target]!"), span_danger("[living_pawn] paws ineffectually at you!")) + + +/** + * Searches for a target with TRAIT_HATED_BY_DOGS within 2 tiles. Sets BB_DOG_HARASS_TARGET and + * BB_DOG_HARASS_HARM = FALSE if found. Returns FAILURE if no valid target is found. + * Combine with a cooldown decorator or embed the SPT_PROB gate in the parent tree. + */ +/datum/bt_node/ai_behavior/find_hated_dog_target + var/target_key + var/targeting_strategy = BB_TARGETING_STRATEGY + +/datum/bt_node/ai_behavior/find_hated_dog_target/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(10, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/dog = controller.pawn + + if(!ispath(targeting_strategy)) + targeting_strategy = controller.blackboard[targeting_strategy] + + + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(targeting_strategy) + for(var/mob/living/iter_living in oview(2, dog)) + if(iter_living.stat != CONSCIOUS || !HAS_TRAIT(iter_living, TRAIT_HATED_BY_DOGS)) + continue + if(!isnull(dog.buckled)) + dog.audible_message(span_notice("[dog] growls at [iter_living], yet [dog.p_they()] [dog.p_are()] much too comfy to move."), hearing_distance = COMBAT_MESSAGE_RANGE) + continue + if(!strategy?.is_valid_target(dog, iter_living)) + continue + dog.audible_message(span_warning("[dog] growls at [iter_living], seemingly annoyed by [iter_living.p_their()] presence."), hearing_distance = COMBAT_MESSAGE_RANGE) + controller.set_blackboard_key(target_key, iter_living) + controller.set_blackboard_key(BB_DOG_HARASS_HARM, FALSE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + +/** + * Dog-specific idle: random walks at a dog-appropriate rate, and occasionally spins/tail-chases. + * Reads BB_DOG_IS_SLOW to determine movement chance. Always returns BT_RUNNING. + */ +/datum/bt_node/ai_behavior/idle_dog + +/datum/bt_node/ai_behavior/idle_dog/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + + var/obj/item/carry_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM] + if(carry_item && SPT_PROB(5, seconds_per_tick)) + living_pawn.visible_message(span_notice("[living_pawn] gently teethes on \the [carry_item] in [living_pawn.p_their()] mouth."), vision_distance = COMBAT_MESSAGE_RANGE) + + var/move_chance = controller.blackboard[BB_DOG_IS_SLOW] ? 2.5 : 5 + if(isturf(living_pawn.loc) && !living_pawn.pulledby) + if(SPT_PROB(move_chance, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE)) + var/move_dir = pick(GLOB.alldirs) + living_pawn.Move(get_step(living_pawn, move_dir), move_dir) + else if(SPT_PROB(2, seconds_per_tick)) + living_pawn.manual_emote(pick("dances around.", "chases [living_pawn.p_their()] tail!")) + living_pawn.AddComponent(/datum/component/spinny) + + return AI_BEHAVIOR_DELAY + +///Dog speech updates the BB keys based on the dogs swag +/datum/bt_node/ai_behavior/random_speech_blackboard/dog_random_speech + +/datum/bt_node/ai_behavior/random_speech_blackboard/dog/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/pet/dog/dog_pawn = controller.pawn + if(!istype(dog_pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + dog_pawn.update_dog_speak_blackboard(controller) + return ..() + + +/// Dog harassment: find a TRAIT_HATED_BY_DOGS target nearby, then approach and paw/bite it. +/datum/bt_node/subtree/dog_harassment + behavior_tree_json = "code/datums/ai/dog/dog_harassment.bt.json" diff --git a/code/datums/ai/dog/dog_controller.dm b/code/datums/ai/dog/dog_controller.dm index cb17546bfbb4..65d6c05a9dba 100644 --- a/code/datums/ai/dog/dog_controller.dm +++ b/code/datums/ai/dog/dog_controller.dm @@ -5,17 +5,14 @@ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_dog - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/dog, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/dog_harassment, - ) + behavior_tree_json = "code/datums/ai/dog/dog.bt.json" /** * Same thing but with make tiny corgis and use access cards. */ /datum/ai_controller/basic_controller/dog/corgi + behavior_tree_json = "code/datums/ai/dog/dog_corgi.bt.json" + blackboard = list( BB_DOG_HARASS_HARM = TRUE, BB_VISION_RANGE = AI_DOG_VISION_RANGE, @@ -28,17 +25,6 @@ BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/dog/corgi/puppy), ) - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/dog, - /datum/ai_planning_subtree/make_babies, // Ian WILL prioritise sex over following your instructions - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/dog_harassment, - // Find targets to run away from (uses the targeting strategy from above) - /datum/ai_planning_subtree/simple_find_target, - // Flee from that target - /datum/ai_planning_subtree/flee_target, - ) - /datum/ai_controller/basic_controller/dog/corgi/get_access() var/mob/living/basic/pet/dog/corgi/corgi_pawn = pawn if(!istype(corgi_pawn)) @@ -54,11 +40,5 @@ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/holding_object, // With tongs in hand! BB_TARGET_HELD_ITEM = /obj/item/kitchen/tongs, - ) - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/dog, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/dog_harassment, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/flee_target, + BB_FUCKS = FALSE, // Puppies don't ) diff --git a/code/datums/ai/dog/dog_corgi.bt.json b/code/datums/ai/dog/dog_corgi.bt.json new file mode 100644 index 000000000000..3248cba5b994 --- /dev/null +++ b/code/datums/ai/dog/dog_corgi.bt.json @@ -0,0 +1,74 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/dog/corgi", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/dog_harassment" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_dog" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/datums/ai/dog/dog_harassment.bt.json b/code/datums/ai/dog/dog_harassment.bt.json new file mode 100644 index 000000000000..eca37f0a690a --- /dev/null +++ b/code/datums/ai/dog/dog_harassment.bt.json @@ -0,0 +1,39 @@ +{ + "dm_type": "/datum/bt_node/subtree/dog_harassment", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.1 + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_hated_dog_target", + "vars": { + "target_key": "BB_DOG_HARASS_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DOG_HARASS_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack/dog", + "vars": { + "target_key": "BB_DOG_HARASS_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } +} diff --git a/code/datums/ai/dog/dog_subtrees.dm b/code/datums/ai/dog/dog_subtrees.dm deleted file mode 100644 index 74c075adad3a..000000000000 --- a/code/datums/ai/dog/dog_subtrees.dm +++ /dev/null @@ -1,38 +0,0 @@ -/// Find someone we don't like and annoy them -/datum/ai_planning_subtree/dog_harassment - -/datum/ai_planning_subtree/dog_harassment/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(10, seconds_per_tick)) - return - controller.queue_behavior(/datum/ai_behavior/find_hated_dog_target, BB_DOG_HARASS_TARGET, BB_PET_TARGETING_STRATEGY) - var/atom/harass_target = controller.blackboard[BB_DOG_HARASS_TARGET] - if (isnull(harass_target)) - return - - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/dog, BB_DOG_HARASS_TARGET, BB_PET_TARGETING_STRATEGY) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/find_hated_dog_target - -/datum/ai_behavior/find_hated_dog_target/setup(datum/ai_controller/controller, target_key, targeting_strategy_key) - . = ..() - var/mob/living/dog = controller.pawn - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - for(var/mob/living/iter_living in oview(2, dog)) - if(iter_living.stat != CONSCIOUS || !HAS_TRAIT(iter_living, TRAIT_HATED_BY_DOGS)) - continue - if(!isnull(dog.buckled)) - dog.audible_message(span_notice("[dog] growls at [iter_living], yet [dog.p_they()] [dog.p_are()] much too comfy to move."), hearing_distance = COMBAT_MESSAGE_RANGE) - continue - if(!targeting_strategy.can_attack(dog, iter_living)) - continue - - dog.audible_message(span_warning("[dog] growls at [iter_living], seemingly annoyed by [iter_living.p_their()] presence."), hearing_distance = COMBAT_MESSAGE_RANGE) - controller.set_blackboard_key(target_key, iter_living) - controller.set_blackboard_key(BB_DOG_HARASS_HARM, FALSE) - return TRUE - - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/find_hated_dog_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic/find_and_set.dm b/code/datums/ai/generic/find_and_set.dm deleted file mode 100644 index ced56912c91c..000000000000 --- a/code/datums/ai/generic/find_and_set.dm +++ /dev/null @@ -1,223 +0,0 @@ -/**find and set - * Finds an item near themselves, sets a blackboard key as it. Very useful for ais that need to use machines or something. - * if you want to do something more complicated than find a single atom, change the search_tactic() proc - * cool tip: search_tactic() can set lists - */ -/datum/ai_behavior/find_and_set - action_cooldown = 2 SECONDS - -/datum/ai_behavior/find_and_set/perform(seconds_per_tick, datum/ai_controller/controller, set_key, locate_path, search_range) - if (controller.blackboard_key_exists(set_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if(QDELETED(controller.pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - var/find_this_thing = search_tactic(controller, locate_path, search_range) - if(isnull(find_this_thing)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [find_this_thing] as a target for blackboard key [set_key]! Behavior: [src]", get_turf(find_this_thing), "Target: [find_this_thing]") - EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(find_this_thing)) - controller.set_blackboard_key(set_key, find_this_thing) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_and_set/proc/search_tactic(datum/ai_controller/controller, locate_path, search_range = 3) - return locate(locate_path) in oview(search_range, controller.pawn) - -/** - * Variant of find and set that fails if the living pawn doesn't hold something - */ -/datum/ai_behavior/find_and_set/pawn_must_hold_item - -/datum/ai_behavior/find_and_set/pawn_must_hold_item/search_tactic(datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - if(!living_pawn.get_num_held_items()) - return //we want to fail the search if we don't have something held - return ..() - -/** - * Variant of find and set that also requires the item to be edible. checks hands too - */ -/datum/ai_behavior/find_and_set/food_or_drink - var/force_find_drinks = FALSE - -/datum/ai_behavior/find_and_set/food_or_drink/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - var/find_drinks = force_find_drinks || controller.blackboard[BB_IGNORE_DRINKS] || FALSE - - for(var/atom/held_candidate in living_pawn.held_items) - if(is_food_or_drink(controller, held_candidate, find_drinks)) - return held_candidate - - for(var/atom/local_candidate in oview(search_range, controller.pawn)) - if(is_food_or_drink(controller, local_candidate, find_drinks) && istype(local_candidate, locate_path)) - return local_candidate - - return null - -/datum/ai_behavior/find_and_set/food_or_drink/proc/is_food_or_drink(datum/ai_controller/controller, obj/item/thing, find_drinks = FALSE) - return is_food(thing) || (find_drinks && is_drink(thing)) - -/datum/ai_behavior/find_and_set/food_or_drink/proc/is_food(obj/item/thing) - if(IS_EDIBLE(thing)) - return TRUE - if(istype(thing, /obj/item/reagent_containers/cup/bowl)) - return thing.reagents.total_volume > 0 - return FALSE - -/datum/ai_behavior/find_and_set/food_or_drink/proc/is_drink(obj/item/thing) - if(istype(thing, /obj/item/reagent_containers/cup/glass)) - return thing.reagents.total_volume > 0 - return FALSE - -/datum/ai_behavior/find_and_set/food_or_drink/to_eat - -/datum/ai_behavior/find_and_set/food_or_drink/to_serve - force_find_drinks = TRUE - -/** - * Variant of find and set that only checks in hands, search range should be excluded for this - */ -/datum/ai_behavior/find_and_set/in_hands - -/datum/ai_behavior/find_and_set/in_hands/search_tactic(datum/ai_controller/controller, locate_path) - var/mob/living/living_pawn = controller.pawn - return locate(locate_path) in living_pawn.held_items - -/datum/ai_behavior/find_and_set/in_hands/given_list - -/datum/ai_behavior/find_and_set/in_hands/given_list/search_tactic(datum/ai_controller/controller, locate_paths) - var/list/found = typecache_filter_list(controller.pawn, locate_paths) - if(length(found)) - return pick(found) - -/** - * Variant of find and set that takes a list of things to find. - */ -/datum/ai_behavior/find_and_set/in_list - -/datum/ai_behavior/find_and_set/in_list/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) - if(length(found)) - return pick(found) - -/// Like find_and_set/in_list, but we return the turf location of the item instead of the item itself. -/datum/ai_behavior/find_and_set/in_list/turf_location - -/datum/ai_behavior/find_and_set/in_list/turf_location/search_tactic(datum/ai_controller/controller, locate_paths, search_range) - . = ..() - if(isnull(.)) - return null - - return get_turf(.) - -/** - * Variant of find and set which returns an object which can be animated with a staff of change - */ -/datum/ai_behavior/find_and_set/animatable - -/datum/ai_behavior/find_and_set/animatable/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - - var/list/nearby_items = list() - for (var/obj/new_friend in oview(search_range, controller.pawn)) - if (!isitem(new_friend) && !isstructure(new_friend)) - continue - if (is_type_in_list(new_friend, GLOB.animatable_blacklist)) - continue - if (living_pawn.see_invisible < new_friend.invisibility) - continue - nearby_items += new_friend - - if(nearby_items.len) - return pick(nearby_items) - -/** - * Variant of find and set which returns the nearest wall which isn't invulnerable - */ -/datum/ai_behavior/find_and_set/nearest_wall - -/datum/ai_behavior/find_and_set/nearest_wall/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - - var/list/nearby_walls = list() - for (var/turf/closed/new_wall in oview(search_range, controller.pawn)) - if (isindestructiblewall(new_wall)) - continue - nearby_walls += new_wall - - if(nearby_walls.len) - return get_closest_atom(/turf/closed/, nearby_walls, living_pawn) - -/** - * Variant of find and set which returns corpses who share your faction - */ -/datum/ai_behavior/find_and_set/friendly_corpses - -/datum/ai_behavior/find_and_set/friendly_corpses/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - var/list/nearby_bodies = list() - for (var/mob/living/dead_pal in oview(search_range, controller.pawn)) - if (!isturf(dead_pal.loc)) - continue - if (!dead_pal.stat || dead_pal.health > 0) - continue - if (living_pawn.see_invisible < dead_pal.invisibility) - continue - if (!living_pawn.faction_check_atom(dead_pal)) - continue - nearby_bodies += dead_pal - - if (nearby_bodies.len) - return pick(nearby_bodies) - -/** - * A variant that looks for a human who is not dead or incapacitated, and has a mind - */ -/datum/ai_behavior/find_and_set/conscious_person - -/datum/ai_behavior/find_and_set/conscious_person/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/customers = list() - for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) - if(IS_DEAD_OR_INCAP(target) || !target.mind) - continue - customers += target - - if(customers.len) - return pick(customers) - - return null - -/datum/ai_behavior/find_and_set/nearby_friends - action_cooldown = 2 SECONDS - -/datum/ai_behavior/find_and_set/nearby_friends/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/atom/friend = locate(/mob/living/carbon/human) in oview(search_range, controller.pawn) - - if(isnull(friend)) - return null - - var/mob/living/living_pawn = controller.pawn - var/potential_friend = living_pawn.has_ally(friend) ? friend : null - return potential_friend - - -/datum/ai_behavior/find_and_set/in_list/turf_types - -/datum/ai_behavior/find_and_set/in_list/turf_types/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = RANGE_TURFS(search_range, controller.pawn) - shuffle_inplace(found) - for(var/turf/possible_turf as anything in found) - if(!is_type_in_typecache(possible_turf, locate_paths)) - continue - if(can_see(controller.pawn, possible_turf, search_range)) - return possible_turf - return null - -/datum/ai_behavior/find_and_set/in_list/closest_turf - -/datum/ai_behavior/find_and_set/in_list/closest_turf/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = RANGE_TURFS(search_range, controller.pawn) - for(var/turf/possible_turf as anything in found) - if(!is_type_in_typecache(possible_turf, locate_paths) || !can_see(controller.pawn, possible_turf, search_range)) - found -= possible_turf - return (length(found)) ? get_closest_atom(/turf, found, controller.pawn) : null diff --git a/code/datums/ai/generic/generic_behaviors.dm b/code/datums/ai/generic/generic_behaviors.dm deleted file mode 100644 index d0e8c25c9d77..000000000000 --- a/code/datums/ai/generic/generic_behaviors.dm +++ /dev/null @@ -1,377 +0,0 @@ - -/datum/ai_behavior/resist/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - living_pawn.ai_controller.set_blackboard_key(BB_RESISTING, TRUE) - living_pawn.execute_resist() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/battle_screech - ///List of possible screeches the behavior has - var/list/screeches - -/datum/ai_behavior/battle_screech/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(screeches)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -///Moves to target then finishes -/datum/ai_behavior/move_to_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - -/datum/ai_behavior/move_to_target/perform(seconds_per_tick, datum/ai_controller/controller) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - -/datum/ai_behavior/break_spine - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 0.7 SECONDS - var/give_up_distance = 10 - -/datum/ai_behavior/break_spine/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/break_spine/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/batman = controller.blackboard[target_key] - var/mob/living/big_guy = controller.pawn //he was molded by the darkness - - if(QDELETED(batman) || get_dist(batman, big_guy) >= give_up_distance) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - if(batman.stat != CONSCIOUS) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - - big_guy.start_pulling(batman) - big_guy.face_atom(batman) - - batman.visible_message(span_warning("[batman] gets a slightly too tight hug from [big_guy]!"), span_userdanger("You feel your body break as [big_guy] embraces you!")) - - for(var/zone in GLOB.all_body_zones - BODY_ZONE_HEAD) - batman.apply_damage(15, BRUTE, zone, wound_bonus = 35) - - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/break_spine/finish_action(datum/ai_controller/controller, succeeded, target_key) - if(succeeded) - var/mob/living/bane = controller.pawn - if(QDELETED(bane)) // pawn can be null at this point - return ..() - bane.stop_pulling() - controller.clear_blackboard_key(target_key) - return ..() - -/// Use in hand the currently held item -/datum/ai_behavior/use_in_hand - behavior_flags = AI_BEHAVIOR_MOVE_AND_PERFORM - - -/datum/ai_behavior/use_in_hand/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/pawn = controller.pawn - var/obj/item/held = pawn.get_active_held_item() - if(!held) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - pawn.activate_hand() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/// Use the currently held item, or unarmed, on a weakref to an object in the world -/datum/ai_behavior/use_on_object - required_distance = 1 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/use_on_object/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - if(target == controller.pawn) // this can sometimes end up as ourselves, in which case there is no reason to move - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/use_on_object/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target = target, combat_mode = FALSE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/give - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - - -/datum/ai_behavior/give/setup(datum/ai_controller/controller, target_key) - . = ..() - set_movement_target(controller, controller.blackboard[target_key]) - -/datum/ai_behavior/give/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/pawn = controller.pawn - var/obj/item/held_item = pawn.get_active_held_item() - var/atom/target = controller.blackboard[target_key] - - if(!held_item) //if held_item is null, we pretend that action was successful - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - if(QDELETED(target) || !target.IsReachableBy(pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_target = target - if(!isliving(living_target)) // target should reasonably only ever be set to a living mob - stack_trace("Tried to give an item to a non-living target!") - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/perform_flags = try_to_give_item(controller, living_target, held_item) - if(perform_flags & AI_BEHAVIOR_FAILED) - return perform_flags - controller.PauseAi(1.5 SECONDS) - living_target.visible_message( - span_info("[pawn] starts trying to give [held_item] to [living_target]!"), - span_warning("[pawn] tries to give you [held_item]!") - ) - if(!do_after(pawn, 1 SECONDS, living_target)) - return AI_BEHAVIOR_DELAY | perform_flags - - perform_flags |= try_to_give_item(controller, living_target, held_item, actually_give = TRUE) - return AI_BEHAVIOR_DELAY | perform_flags - -/datum/ai_behavior/give/proc/try_to_give_item(datum/ai_controller/controller, mob/living/target, obj/item/held_item, actually_give) - if(QDELETED(held_item) || QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/has_left_pocket = target.can_equip(held_item, ITEM_SLOT_LPOCKET) - var/has_right_pocket = target.can_equip(held_item, ITEM_SLOT_RPOCKET) - var/has_valid_hand - - for(var/hand_index in target.get_empty_held_indexes()) - if(target.can_put_in_hand(held_item, hand_index)) - has_valid_hand = TRUE - break - - if(!has_left_pocket && !has_right_pocket && !has_valid_hand) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(!actually_give) - return AI_BEHAVIOR_DELAY - - if(!has_valid_hand || prob(50)) - target.equip_to_slot_if_possible(held_item, (!has_left_pocket ? ITEM_SLOT_RPOCKET : (prob(50) ? ITEM_SLOT_LPOCKET : ITEM_SLOT_RPOCKET))) - else - target.put_in_hands(held_item) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/give/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/consume - action_cooldown = 2 SECONDS - -/datum/ai_behavior/consume/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hunger_timer_key) - var/mob/living/living_pawn = controller.pawn - var/obj/item/target = controller.blackboard[target_key] - if(QDELETED(target) || !living_pawn.is_holding(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target = living_pawn, combat_mode = FALSE) - - return AI_BEHAVIOR_DELAY | (is_content(living_pawn, target) ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) - -/datum/ai_behavior/consume/finish_action(datum/ai_controller/controller, succeeded, target_key, hunger_timer_key) - . = ..() - if(!succeeded) - return - controller.set_blackboard_key(hunger_timer_key, world.time + rand(12 SECONDS, 60 SECONDS)) - - var/mob/living/living_pawn = controller.pawn - var/obj/item/target = controller.blackboard[target_key] - if(!QDELETED(target) && !DOING_INTERACTION_WITH_TARGET(living_pawn, target)) - controller.clear_blackboard_key(target_key) - living_pawn.dropItemToGround(target) // drops empty drink glasses - for(var/obj/item/trash/trash in living_pawn.held_items) - living_pawn.dropItemToGround(trash) // drops spawned trash items - -/// Check if the target is fully consumed, or being actively consumed, or if we're just bored of eating it -/datum/ai_behavior/consume/proc/is_content(mob/living/living_pawm, obj/item/target) - if(QDELETED(target)) - return TRUE - if(DOING_INTERACTION_WITH_TARGET(living_pawm, target)) - return TRUE - if(target.reagents?.total_volume <= 0) - return TRUE - // Even if we don't finish it all we can randomly decide to be done - return prob(10) - -// navigate to target item and pick it up if we can -/datum/ai_behavior/navigate_to_and_pick_up - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 2 SECONDS - -/datum/ai_behavior/navigate_to_and_pick_up/setup(datum/ai_controller/controller, target_key, drop_held = TRUE) - . = ..() - set_movement_target(controller, controller.blackboard[target_key]) - -/datum/ai_behavior/navigate_to_and_pick_up/setup(datum/ai_controller/controller, target_key, drop_held = TRUE) - var/mob/living/living_pawn = controller.pawn - var/obj/item/target = controller.blackboard[target_key] - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(living_pawn.is_holding(target)) // already in hands - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if(!target.IsReachableBy(living_pawn)) // can't reach it, despite being adjacent - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(living_pawn.get_active_held_item()) // something is in our hands already - if(!drop_held || !living_pawn.dropItemToGround(living_pawn.get_active_held_item())) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target, combat_mode = FALSE) - return AI_BEHAVIOR_DELAY | (target.loc == living_pawn ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) - -/** - * Drops items in hands, very important for future behaviors that require the pawn to grab stuff - */ -/datum/ai_behavior/drop_item - -/datum/ai_behavior/drop_item/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - var/list/my_held_items = living_pawn.held_items - GetBestWeapon(controller, null, living_pawn.held_items) - if(!length(my_held_items)) - return AI_BEHAVIOR_FAILED | AI_BEHAVIOR_DELAY - living_pawn.dropItemToGround(pick(my_held_items)) - return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY - -/// This behavior involves attacking a target. -/datum/ai_behavior/attack - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/attack/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn) || !isturf(living_pawn.loc)) - return AI_BEHAVIOR_DELAY - - var/atom/movable/attack_target = controller.blackboard[BB_ATTACK_TARGET] - if(!attack_target || !can_see(living_pawn, attack_target, length = controller.blackboard[BB_VISION_RANGE])) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_target = attack_target - if(istype(living_target) && (living_target.stat == DEAD)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - set_movement_target(controller, living_target) - attack(controller, living_target) - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/attack/finish_action(datum/ai_controller/controller, succeeded) - . = ..() - controller.clear_blackboard_key(BB_ATTACK_TARGET) - -/// A proc representing when the mob is pushed to actually attack the target. Again, subtypes can be used to represent different attacks from different animals, or it can be some other generic behavior -/datum/ai_behavior/attack/proc/attack(datum/ai_controller/controller, mob/living/living_target) - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn)) - return - living_pawn.ClickOn(living_target, list()) - -/// This behavior involves attacking a target. -/datum/ai_behavior/follow - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - -/datum/ai_behavior/follow/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn) || !isturf(living_pawn.loc)) - return AI_BEHAVIOR_DELAY - - var/atom/movable/follow_target = controller.blackboard[BB_FOLLOW_TARGET] - if(!follow_target || get_dist(living_pawn, follow_target) > controller.blackboard[BB_VISION_RANGE]) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_target = follow_target - if(istype(living_target) && (living_target.stat == DEAD)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - set_movement_target(controller, living_target) - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/follow/finish_action(datum/ai_controller/controller, succeeded) - . = ..() - controller.clear_blackboard_key(BB_FOLLOW_TARGET) - -/datum/ai_behavior/perform_emote - -/datum/ai_behavior/perform_emote/perform(seconds_per_tick, datum/ai_controller/controller, emote, speech_sound) - . = ..() - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn)) - return AI_BEHAVIOR_INSTANT - living_pawn.manual_emote(emote) - if(speech_sound) // Only audible emotes will pass in a sound - playsound(living_pawn, speech_sound, 80, vary = TRUE, pressure_affected =TRUE, ignore_walls = FALSE) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/perform_speech - -/datum/ai_behavior/perform_speech/perform(seconds_per_tick, datum/ai_controller/controller, speech, speech_sound) - . = ..() - - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn)) - return AI_BEHAVIOR_INSTANT - living_pawn.say(speech, forced = "AI Controller") - if(speech_sound) - playsound(living_pawn, speech_sound, 80, vary = TRUE) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/perform_speech_radio - -/datum/ai_behavior/perform_speech_radio/perform(seconds_per_tick, datum/ai_controller/controller, speech, obj/item/radio/speech_radio, list/try_channels = list(RADIO_CHANNEL_COMMON)) - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn) || !istype(speech_radio) || QDELETED(speech_radio) || !length(try_channels)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - speech_radio.talk_into(living_pawn, speech, pick(try_channels)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -//song behaviors - -/datum/ai_behavior/setup_instrument - -/datum/ai_behavior/setup_instrument/perform(seconds_per_tick, datum/ai_controller/controller, song_instrument_key, song_lines_key) - var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key] - var/datum/song/song = song_instrument.song - var/song_lines = controller.blackboard[song_lines_key] - - //just in case- it won't do anything if the instrument isn't playing - song.stop_playing() - song.ParseSong(new_song = song_lines) - song.repeat = 10 - song.volume = song.max_volume - 10 - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/play_instrument - -/datum/ai_behavior/play_instrument/perform(seconds_per_tick, datum/ai_controller/controller, song_instrument_key) - var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key] - var/datum/song/song = song_instrument.song - - song.start_playing(controller.pawn) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_nearby - -/datum/ai_behavior/find_nearby/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/list/possible_targets = list() - for(var/atom/thing in view(2, controller.pawn)) - if(!thing.mouse_opacity) - continue - if(thing.IsObscured()) - continue - if(isitem(thing)) - var/obj/item/item = thing - if(item.item_flags & ABSTRACT) - continue - possible_targets += thing - if(!possible_targets.len) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(target_key, pick(possible_targets)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic/generic_subtrees.dm b/code/datums/ai/generic/generic_subtrees.dm deleted file mode 100644 index 6fe92668f916..000000000000 --- a/code/datums/ai/generic/generic_subtrees.dm +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Generic Instrument Subtree, For your pawn playing instruments - * - * Requires at least a living mob that can hold items. - * - * relevant blackboards: - * * BB_SONG_INSTRUMENT - set by this subtree, is the song datum the pawn plays music from. - * * BB_SONG_LINES - not set by this subtree, is the song loaded into the song datum. - */ -/datum/ai_planning_subtree/generic_play_instrument/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/obj/item/instrument/song_player = controller.blackboard[BB_SONG_INSTRUMENT] - - if(!song_player) - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_hands, BB_SONG_INSTRUMENT, /obj/item/instrument) - return //we can't play a song since we do not have an instrument - - var/list/parsed_song_lines = splittext(controller.blackboard[BB_SONG_LINES], "\n") - popleft(parsed_song_lines) //remove BPM as it is parsed out - if(!compare_list(song_player.song.lines, parsed_song_lines) || !song_player.song.repeat) - controller.queue_behavior(/datum/ai_behavior/setup_instrument, BB_SONG_INSTRUMENT, BB_SONG_LINES) - - if(!song_player.song.playing) //we may stop playing if we weren't playing before, were setting up dk theme, or ran out of repeats (also causing setup behavior) - controller.queue_behavior(/datum/ai_behavior/play_instrument, BB_SONG_INSTRUMENT) - -/datum/ai_planning_subtree/generic_play_instrument/end_planning - -/datum/ai_planning_subtree/generic_play_instrument/end_planning/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if (controller.blackboard_key_exists(BB_SONG_INSTRUMENT)) - return SUBTREE_RETURN_FINISH_PLANNING // Don't plan anything else if we're playing an instrument - - -/** - * Generic Resist Subtree, resist if it makes sense to! - * - * Requires nothing beyond a living pawn, makes sense on a good amount of mobs since anything can get buckled. - * - * relevant blackboards: - * * None! - */ -/datum/ai_planning_subtree/generic_resist/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - - if(SHOULD_RESIST(living_pawn) && SPT_PROB(RESIST_SUBTREE_PROB, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/resist) //BRO IM ON FUCKING FIRE BRO - return SUBTREE_RETURN_FINISH_PLANNING //IM NOT DOING ANYTHING ELSE BUT EXTINGUISH MYSELF, GOOD GOD HAVE MERCY. - -/** - * Generic Hunger Subtree, - * - * Requires at least a living mob that can hold items. - * - * relevant blackboards: - * * BB_NEXT_HUNGRY - set by this subtree, is when the controller is next hungry - */ -/datum/ai_planning_subtree/generic_hunger - -/datum/ai_planning_subtree/generic_hunger/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.nutrition > NUTRITION_LEVEL_HUNGRY) - return - - var/next_eat = controller.blackboard[BB_NEXT_HUNGRY] - if(!next_eat) - //inits the blackboard timer - next_eat = world.time + rand(0, 30 SECONDS) - controller.set_blackboard_key(BB_NEXT_HUNGRY, next_eat) - - if(world.time < next_eat) - return - - // find food - var/atom/food_target = controller.blackboard[BB_FOOD_TARGET] - if(isnull(food_target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/food_or_drink/to_eat, BB_FOOD_TARGET, /obj/item, 2) - return SUBTREE_RETURN_FINISH_PLANNING - - if(living_pawn.is_holding(food_target)) - controller.queue_behavior(/datum/ai_behavior/consume, BB_FOOD_TARGET, BB_NEXT_HUNGRY) - // it's been moved since we found it - else if(!isturf(food_target.loc)) - // someone took it. we will fight over it! - if(isliving(food_target.loc) && will_fight_for_food(food_target.loc, living_pawn, controller)) - controller.add_blackboard_key_assoc(BB_MONKEY_ENEMIES, food_target.loc, MONKEY_FOOD_HATRED_AMOUNT) - // eh, find something else - else - controller.clear_blackboard_key(BB_FOOD_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - else - controller.queue_behavior(/datum/ai_behavior/navigate_to_and_pick_up, BB_FOOD_TARGET, TRUE) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_planning_subtree/generic_hunger/proc/will_fight_for_food(mob/living/thief, mob/living/monkey, datum/ai_controller/controller) - if(controller.blackboard[BB_MONKEY_AGGRESSIVE]) - return TRUE - if(controller.blackboard[BB_MONKEY_TAMED]) - return FALSE - return prob(100 * ((NUTRITION_LEVEL_HUNGRY - monkey.nutrition) / NUTRITION_LEVEL_HUNGRY)) diff --git a/code/datums/ai/generic_behaviors/acquire_injured_target.dm b/code/datums/ai/generic_behaviors/acquire_injured_target.dm new file mode 100644 index 000000000000..073e109997be --- /dev/null +++ b/code/datums/ai/generic_behaviors/acquire_injured_target.dm @@ -0,0 +1,15 @@ +/datum/bt_node/ai_behavior/retrieve_injured_rider + ///where do we save our target + var/target_key + +/datum/bt_node/ai_behavior/retrieve_injured_rider/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_mob = controller.pawn + if (!length(living_mob.buckled_mobs) || !isliving(living_mob.buckled_mobs[1])) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/rider = living_mob.buckled_mobs[1] + if (rider.stat == CONSCIOUS || rider.stat == DEAD || rider.health >= rider.maxHealth) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(target_key, rider) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/acquire_target.dm b/code/datums/ai/generic_behaviors/acquire_target.dm new file mode 100644 index 000000000000..73b1608f13db --- /dev/null +++ b/code/datums/ai/generic_behaviors/acquire_target.dm @@ -0,0 +1,196 @@ +///Baseline for target picking behaviors +/datum/bt_node/ai_behavior/acquire_target + /// Blackboard key to write the found target into. + var/target_key + /// Either a /datum/targeting_strategy typepath (resolved directly) or a blackboard key string + var/targeting_strategy = BB_TARGETING_STRATEGY + /// Typepath of the /datum/target_source type used to gather candidates. turned into the singleton instance when used + var/target_source = /datum/target_source/oview + /// How far to scan for candidates (passed to the target source). Can be a key too. + var/vision_range = 7 + /// How to behave when a target is already set. See TARGET_* defines in ai.dm. + var/revalidation_mode = TARGET_REVALIDATE + /// Extended range for retaining an existing target when candidates run dry. 0 = disabled. + var/target_loss_distance = 12 + /// Optional blackboard key holding a lazylist of atoms to skip while filtering candidates. + var/ignore_list_key + /// If TRUE, only candidates the controller can path to are eligible; unreachable ones are reported via note_unreachable_target. + var/must_be_reachable = FALSE + /// Pathfinding distance limit used when must_be_reachable is set. + var/reach_distance = 10 + /// How close the reachability path must get to the target (0 = onto/adjacent). Passed to can_reach_target when must_be_reachable is set. + var/minimum_distance = 0 + /// Strategy/range snapshot from the perform() that kicked off the current async search. + VAR_PRIVATE/datum/targeting_strategy/search_strategy + /// Range snapshot from the perform() that kicked off the current async search. + VAR_PRIVATE/search_range + +/datum/bt_node/ai_behavior/acquire_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + if(!can_search(controller)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/datum/targeting_strategy/strategy = get_targeting_strategy(controller) + var/atom/current_target = controller.blackboard[target_key] + + if(should_keep_target(controller, strategy, current_target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + if(!isnum(vision_range)) //If there's a blackboard override, use that + vision_range = controller.blackboard[vision_range] || vision_range + + if(!must_be_reachable) + return find_and_set_target(controller, strategy, vision_range) + + // Kick off async reachability search. + search_strategy = strategy + search_range = vision_range + return start_async() + +/datum/bt_node/ai_behavior/acquire_target/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + search_strategy = null + search_range = null + +/// Returns TRUE to abort the search before it starts (e.g. a detection field is already active). +/datum/bt_node/ai_behavior/acquire_target/proc/can_search(datum/ai_controller/controller) + return TRUE + +/// Returns TRUE if the current target is still good and we should skip the search. +/datum/bt_node/ai_behavior/acquire_target/proc/should_keep_target(datum/ai_controller/controller, datum/targeting_strategy/strategy, atom/current_target) + switch(revalidation_mode) + if(TARGET_KEEP_IF_SET) + return !isnull(current_target) + if(TARGET_REVALIDATE) + return !isnull(current_target) && strategy.is_valid_target(controller.pawn, current_target, vision_range, controller) + return FALSE + +///Resolves the targeting strategy for this behavior +/datum/bt_node/ai_behavior/acquire_target/proc/get_targeting_strategy(datum/ai_controller/controller) + if(!targeting_strategy) + return GET_TARGETING_STRATEGY(/datum/targeting_strategy/anything) + if(ispath(targeting_strategy)) + return GET_TARGETING_STRATEGY(targeting_strategy) + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy]) + if(!strategy) + CRASH("No targeting strategy was supplied in the blackboard for [controller.pawn]") + return strategy + + +///Actual behavior for collecting and filtering targets +/datum/bt_node/ai_behavior/acquire_target/proc/find_and_set_target(datum/ai_controller/controller, datum/targeting_strategy/targeting_strategy, range) + var/mob/living/living_mob = controller.pawn + var/datum/target_source/source = GET_TARGET_SOURCE(target_source) + if(!source) + CRASH("No target source found for type [target_source] on [controller.pawn]") + + var/atom/current_target = controller.blackboard[target_key] + var/list/candidates = source.collect_candidates(living_mob, controller, range) + + if(!length(candidates)) + candidates = on_no_candidates(controller, current_target, targeting_strategy, range) + if(!length(candidates)) + clear_stale_target(controller, current_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/list/filtered = filter_candidates(controller, candidates, targeting_strategy, current_target) + + if(!length(filtered)) + on_no_valid_candidates(controller, current_target) + clear_stale_target(controller, current_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/atom/target = pick_final_target(controller, filtered) + + if(isnull(target)) + on_no_valid_candidates(controller, current_target) + clear_stale_target(controller, current_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [target] as a target for blackboard key [target_key]! Behavior: [src]", get_turf(target), "Target: [target]") + EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(target)) + + if(target != current_target) + controller.set_blackboard_key(target_key, target) + + on_target_found(controller, target, targeting_strategy) + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/// Clears the target key when revalidating and the search turned up nothing, so a stale target doesn't linger. +/datum/bt_node/ai_behavior/acquire_target/proc/clear_stale_target(datum/ai_controller/controller, atom/current_target) + if(revalidation_mode != TARGET_REVALIDATE || isnull(current_target)) + return + controller.clear_blackboard_key(target_key) + +///Fallback for no targets found. +/datum/bt_node/ai_behavior/acquire_target/proc/on_no_candidates(datum/ai_controller/controller, atom/current_target, datum/targeting_strategy/strategy, range) + if(!target_loss_distance || !current_target) + return list() + if(strategy.can_keep_target(controller.pawn, current_target, target_loss_distance)) + return list(current_target) + return list() + +/// Filters the candidate list to valid targets. Override to add priority filtering or other per-candidate criteria. +/datum/bt_node/ai_behavior/acquire_target/proc/filter_candidates(datum/ai_controller/controller, list/candidates, datum/targeting_strategy/strategy, atom/current_target) + var/mob/living/pawn = controller.pawn + var/list/ignore_list = ignore_list_key ? controller.blackboard[ignore_list_key] : null + var/list/filtered = list() + for(var/atom/candidate as anything in candidates) + if(LAZYACCESS(ignore_list, candidate)) + continue + if(!strategy.is_valid_target(pawn, candidate, vision_range, controller)) + continue + filtered += candidate + return filtered + +/// Called when filter_candidates produces nothing. Override to trigger side effects (e.g. spawning a detection field). +/datum/bt_node/ai_behavior/acquire_target/proc/on_no_valid_candidates(datum/ai_controller/controller, atom/current_target) + return + +/// Called after a target is selected and written to the blackboard. Override for post-selection side effects. +/datum/bt_node/ai_behavior/acquire_target/proc/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + return + +/// Picks the final target from filtered candidates. Only valid for the non-reachable path; the reachable path goes async. +/datum/bt_node/ai_behavior/acquire_target/proc/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + return pick(filtered_targets) + +/// perform_async(): walks filtered candidates checking reachability (may sleep), then commits the result via finish_async(). +/datum/bt_node/ai_behavior/acquire_target/perform_async(datum/ai_controller/controller) + var/datum/targeting_strategy/strategy = search_strategy + var/range = search_range + var/mob/living/living_mob = controller.pawn + var/datum/target_source/source = GET_TARGET_SOURCE(target_source) + var/atom/current_target = controller.blackboard[target_key] + var/list/candidates = source.collect_candidates(living_mob, controller, range) + if(!length(candidates)) + candidates = on_no_candidates(controller, current_target, strategy, range) + var/list/filtered = filter_candidates(controller, candidates, strategy, current_target) + var/atom/target + for(var/atom/candidate as anything in filtered) + // get_path_to may sleep here check abort flag after it returns. + if(controller.can_reach_target(candidate, reach_distance, minimum_distance)) + target = candidate + break + controller.note_unreachable_target(candidate) + // If finish_action fired while we were sleeping, bail without touching anything. + if(!async_still_valid()) + return + if(!isnull(target)) + if(target != controller.blackboard[target_key]) + controller.set_blackboard_key(target_key, target) + on_target_found(controller, target, strategy) + else + on_no_valid_candidates(controller, current_target) + clear_stale_target(controller, current_target) + finish_async(isnull(target) ? AI_BEHAVIOR_FAILED : AI_BEHAVIOR_SUCCEEDED) + +///Finds a nearby target to interact with, used as a baseline for behaviors that need to interact with something nearby. +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target + target_source = /datum/target_source/oview + revalidation_mode = TARGET_REVALIDATE + time_between_perform = 2 SECONDS diff --git a/code/datums/ai/generic_behaviors/ai_interact.dm b/code/datums/ai/generic_behaviors/ai_interact.dm new file mode 100644 index 000000000000..29e54e626e2d --- /dev/null +++ b/code/datums/ai/generic_behaviors/ai_interact.dm @@ -0,0 +1,17 @@ +/// Interacts with the atom at target_key once, with optional combat_mode (default FALSE). +/datum/bt_node/ai_behavior/ai_interact + /// Blackboard key holding the atom to interact with. + var/target_key + /// Whether to interact in combat mode. + var/combat_mode = FALSE + +/datum/bt_node/ai_behavior/ai_interact/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/ai_interact/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, combat_mode) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/attack_obstacles.dm b/code/datums/ai/generic_behaviors/attack_obstacles.dm new file mode 100644 index 000000000000..aeb9a7476eb0 --- /dev/null +++ b/code/datums/ai/generic_behaviors/attack_obstacles.dm @@ -0,0 +1,65 @@ + +///Destroy shit int he way +/datum/bt_node/ai_behavior/attack_obstructions + var/target_key + time_between_perform = 2 SECONDS + /// If we should attack walls, be prepared for complaints about breaches + var/can_attack_turfs = FALSE + /// For if you want your mob to be able to attack dense objects + var/can_attack_dense_objects = FALSE + +/datum/bt_node/ai_behavior/attack_obstructions/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/basic_mob = controller.pawn + var/atom/target = controller.blackboard[target_key] + + if(QDELETED(target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/turf/next_step = get_step_towards(basic_mob, target) + if(!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED // Path clear let selector fall through to melee + + var/dir_to_next_step = get_dir(basic_mob, next_step) + var/list/dirs_to_move = list() + if(ISDIAGONALDIR(dir_to_next_step)) + for(var/direction in GLOB.cardinals) + if(direction & dir_to_next_step) + dirs_to_move += direction + else + dirs_to_move += dir_to_next_step + + for(var/direction in dirs_to_move) + if(attack_in_direction(controller, basic_mob, direction)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED // Nothing smashable let selector fall through + +/datum/bt_node/ai_behavior/attack_obstructions/proc/attack_in_direction(datum/ai_controller/controller, mob/living/basic/basic_mob, direction) + var/turf/next_step = get_step(basic_mob, direction) + if(!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn)) + return FALSE + + for(var/obj/object as anything in next_step.contents) + if(!can_smash_object(basic_mob, object)) + continue + basic_mob.melee_attack(object) + return TRUE + + if(can_attack_turfs) + basic_mob.melee_attack(next_step) + return TRUE + return FALSE + +/datum/bt_node/ai_behavior/attack_obstructions/proc/can_smash_object(mob/living/basic/basic_mob, obj/object) + if(!object.density && !can_attack_dense_objects) + return FALSE + if(object.IsObscured()) + return FALSE + if(basic_mob.see_invisible < object.invisibility) + return FALSE + var/list/whitelist = basic_mob.ai_controller.blackboard[BB_OBSTACLE_TARGETING_WHITELIST] + if(whitelist && !is_type_in_typecache(object, whitelist)) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/attack_obstructions/attack_turfs + can_attack_turfs = TRUE diff --git a/code/datums/ai/generic_behaviors/battle_screech.dm b/code/datums/ai/generic_behaviors/battle_screech.dm new file mode 100644 index 000000000000..eee357e942ef --- /dev/null +++ b/code/datums/ai/generic_behaviors/battle_screech.dm @@ -0,0 +1,10 @@ +/// Emotes a random screech from a list of screeches defined on the subtype. +/datum/bt_node/ai_behavior/battle_screech + /// List of possible screeches the behavior has + var/list/screeches + +/datum/bt_node/ai_behavior/battle_screech/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(screeches)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + diff --git a/code/datums/ai/generic_behaviors/break_out_of_object.dm b/code/datums/ai/generic_behaviors/break_out_of_object.dm new file mode 100644 index 000000000000..239222e55f5b --- /dev/null +++ b/code/datums/ai/generic_behaviors/break_out_of_object.dm @@ -0,0 +1,40 @@ +/// Attacks an escape target until the pawn is no longer buckled to or contained by it. +/datum/bt_node/ai_behavior/break_out_of_object + time_between_perform = 0.2 SECONDS + /// The object to break out of by attacking it. + var/atom/target_atom + +/datum/bt_node/ai_behavior/break_out_of_object/setup(datum/ai_controller/controller) + if (!should_attack_target(controller, target_atom)) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/break_out_of_object/perform(seconds_per_tick, datum/ai_controller/controller) + if (!should_attack_target(controller, target_atom)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target_atom, TRUE) + return AI_BEHAVIOR_DELAY + +/datum/bt_node/ai_behavior/break_out_of_object/proc/should_attack_target(datum/ai_controller/controller, atom/target) + if (QDELETED(target)) + return FALSE + var/mob/living/pawn = controller.pawn + if (!target.IsReachableBy(pawn)) + return FALSE + return pawn.loc == target || pawn.buckled == target + +/// Variant that reads the escape target from a blackboard key instead of a direct reference. +/datum/bt_node/ai_behavior/break_out_of_object/from_bb + /// Blackboard key holding the object to break out of. + var/target_key + +/datum/bt_node/ai_behavior/break_out_of_object/from_bb/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + return should_attack_target(controller, target) + +/datum/bt_node/ai_behavior/break_out_of_object/from_bb/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(!should_attack_target(controller, target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, TRUE) + return AI_BEHAVIOR_DELAY diff --git a/code/datums/ai/generic_behaviors/break_spine.dm b/code/datums/ai/generic_behaviors/break_spine.dm new file mode 100644 index 000000000000..013c1ee2915b --- /dev/null +++ b/code/datums/ai/generic_behaviors/break_spine.dm @@ -0,0 +1,42 @@ +/// Applies full-body brute damage to a target while pulling them. Used with a move_to_target sequence. +/datum/bt_node/ai_behavior/break_spine + var/target_key + var/give_up_distance = 10 + +/datum/bt_node/ai_behavior/break_spine/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/batman = controller.blackboard[target_key] + var/mob/living/big_guy = controller.pawn + + if(QDELETED(batman) || get_dist(batman, big_guy) >= give_up_distance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + if(batman.stat != CONSCIOUS) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + INVOKE_ASYNC(big_guy, TYPE_PROC_REF(/atom/movable, start_pulling), batman) + big_guy.face_atom(batman) + batman.visible_message(span_warning("[batman] gets a slightly too tight hug from [big_guy]!"), span_userdanger("You feel your body break as [big_guy] embraces you!")) + for(var/zone in GLOB.all_body_zones - BODY_ZONE_HEAD) + batman.apply_damage(15, BRUTE, zone, wound_bonus = 35) + + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/break_spine/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(succeeded) + var/mob/living/attacker = controller.pawn + if(!QDELETED(attacker)) + attacker.stop_pulling() + controller.clear_blackboard_key(target_key) + +/// Bane variant: says a quote from bane.json on successful spine-breaking. +/datum/bt_node/ai_behavior/break_spine/bane + +/datum/bt_node/ai_behavior/break_spine/bane/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(succeeded) + var/mob/living/bane = controller.pawn + if(QDELETED(bane)) + return + var/list/bane_quotes = strings("bane.json", "bane") + INVOKE_ASYNC(bane, TYPE_PROC_REF(/atom/movable, say), pick(bane_quotes)) diff --git a/code/datums/ai/generic_behaviors/cancel_current_plan.dm b/code/datums/ai/generic_behaviors/cancel_current_plan.dm new file mode 100644 index 000000000000..74fe54b83e7e --- /dev/null +++ b/code/datums/ai/generic_behaviors/cancel_current_plan.dm @@ -0,0 +1,6 @@ +/// Cancels the controller's current plan, causing the BT to re-evaluate from the root next tick. +/datum/bt_node/ai_behavior/cancel_current_plan + +/datum/bt_node/ai_behavior/cancel_current_plan/perform(seconds_per_tick, datum/ai_controller/controller) + controller.cancel_current_plan() + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/clear_key.dm b/code/datums/ai/generic_behaviors/clear_key.dm new file mode 100644 index 000000000000..fffbc70e9511 --- /dev/null +++ b/code/datums/ai/generic_behaviors/clear_key.dm @@ -0,0 +1,8 @@ +/// BT-native version: clears a single blackboard key. Returns INSTANT SUCCESS. +/datum/bt_node/ai_behavior/clear_key + /// Blackboard key to clear. + var/key + +/datum/bt_node/ai_behavior/clear_key/perform(seconds_per_tick, datum/ai_controller/controller) + controller.clear_blackboard_key(key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/consume.dm b/code/datums/ai/generic_behaviors/consume.dm new file mode 100644 index 000000000000..f8562ce6b197 --- /dev/null +++ b/code/datums/ai/generic_behaviors/consume.dm @@ -0,0 +1,43 @@ +/// Uses the pawn's held food/drink item on themselves until consumed. +/datum/bt_node/ai_behavior/consume + time_between_perform = 2 SECONDS + /// Blackboard key holding the food/drink item to consume. + var/target_key + /// Blackboard key used to store the next-hunger time once finished. + var/hunger_timer_key + +/datum/bt_node/ai_behavior/consume/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/obj/item/target = controller.blackboard[target_key] + if(QDELETED(target) || !living_pawn.is_holding(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), living_pawn, FALSE) + + return AI_BEHAVIOR_DELAY | (is_content(living_pawn, target) ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/datum/bt_node/ai_behavior/consume/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(!succeeded) + return + controller.set_blackboard_key(hunger_timer_key, world.time + rand(12 SECONDS, 60 SECONDS)) + + var/mob/living/living_pawn = controller.pawn + var/obj/item/target = controller.blackboard[target_key] + if(!QDELETED(target) && !DOING_INTERACTION_WITH_TARGET(living_pawn, target)) + controller.clear_blackboard_key(target_key) + living_pawn.dropItemToGround(target) // drops empty drink glasses + for(var/obj/item/trash/trash in living_pawn.held_items) + living_pawn.dropItemToGround(trash) // drops spawned trash items + +/// Check if the target is fully consumed, or being actively consumed, or if we're just bored of eating it +/datum/bt_node/ai_behavior/consume/proc/is_content(mob/living/living_pawm, obj/item/target) + if(QDELETED(target)) + return TRUE + if(DOING_INTERACTION_WITH_TARGET(living_pawm, target)) + return TRUE + if(target.reagents?.total_volume <= 0) + return TRUE + // Even if we don't finish it all we can randomly decide to be done + return prob(10) + diff --git a/code/datums/ai/generic_behaviors/copy_bb_key.dm b/code/datums/ai/generic_behaviors/copy_bb_key.dm new file mode 100644 index 000000000000..19cd6e4bd471 --- /dev/null +++ b/code/datums/ai/generic_behaviors/copy_bb_key.dm @@ -0,0 +1,8 @@ +/// Copies the value of source_key to dest_key. +/datum/bt_node/ai_behavior/copy_bb_key + var/dest_key + var/source_key + +/datum/bt_node/ai_behavior/copy_bb_key/perform(seconds_per_tick, datum/ai_controller/controller) + controller.set_blackboard_key(dest_key, controller.blackboard[source_key]) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/drag_target.dm b/code/datums/ai/generic_behaviors/drag_target.dm new file mode 100644 index 000000000000..2ef882e2c87e --- /dev/null +++ b/code/datums/ai/generic_behaviors/drag_target.dm @@ -0,0 +1,21 @@ +/** + * BT-native drag behavior. Moves to the target and starts pulling it. + * If already pulling the target, returns SUCCESS immediately (idempotent). + * Does NOT clear the target key on finish callers must clear it when done. + * Use move_to_target after this to drag the pulled mob/item to a destination. + */ +/datum/bt_node/ai_behavior/drag_target + /// Blackboard key holding the atom to drag. + var/target_key + +/datum/bt_node/ai_behavior/drag_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/our_mob = controller.pawn + var/atom/movable/target = controller.blackboard[target_key] + if(QDELETED(target) || target.anchored || target.pulledby) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(our_mob.pulling == target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + if(!our_mob.Adjacent(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(our_mob, TYPE_PROC_REF(/atom/movable, start_pulling), target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/drop_all_held_items.dm b/code/datums/ai/generic_behaviors/drop_all_held_items.dm new file mode 100644 index 000000000000..5c4653062aa2 --- /dev/null +++ b/code/datums/ai/generic_behaviors/drop_all_held_items.dm @@ -0,0 +1,9 @@ +/// Drops everything the pawn is currently holding in its hands onto its current turf. +/datum/bt_node/ai_behavior/drop_all_held_items + +/datum/bt_node/ai_behavior/drop_all_held_items/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!living_pawn.get_num_held_items()) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + living_pawn.drop_all_held_items() + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/express_happiness.dm b/code/datums/ai/generic_behaviors/express_happiness.dm new file mode 100644 index 000000000000..eaf181ca6965 --- /dev/null +++ b/code/datums/ai/generic_behaviors/express_happiness.dm @@ -0,0 +1,53 @@ +/// Occasionally emotes based on the pawn's current happiness level. +/datum/bt_node/ai_behavior/express_happiness + /// Probability (%) of emoting per per second + var/emote_probability = 5 + /// Happiness >= this threshold -> happy emotions + var/high_happiness_threshold = 0.7 + /// Happiness >= this threshold -> moderate emotions + var/moderate_happiness_threshold = 0.5 + /// Blackboard key holding the happiness value + var/happiness_key = BB_BASIC_HAPPINESS + /// Blackboard key holding a custom happy emotions list + var/happy_key = BB_HAPPY_EMOTIONS + /// Blackboard key holding a custom moderate emotions list + var/moderate_key = BB_MODERATE_EMOTIONS + /// Blackboard key holding a custom sad emotions list + var/sad_key = BB_SAD_EMOTIONS + + var/static/list/default_happy_emotions = list( + "celebrates happily!", + "dances around in excitement!", + ) + var/static/list/default_moderate_emotions = list( + "looks satisfied.", + "trots around.", + ) + var/static/list/default_depressed_emotions = list( + "looks depressed...", + "turns its back and sulks...", + "looks towards the floor in dissapointment...", + ) + +/datum/bt_node/ai_behavior/express_happiness/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(emote_probability, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + var/happiness = controller.blackboard[happiness_key] + if(isnull(happiness)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + var/list/emotion_list + if(happiness >= high_happiness_threshold) + emotion_list = controller.blackboard[happy_key] || default_happy_emotions + else if(happiness >= moderate_happiness_threshold) + emotion_list = controller.blackboard[moderate_key] || default_moderate_emotions + else + emotion_list = controller.blackboard[sad_key] || default_depressed_emotions + + if(!length(emotion_list)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + var/mob/living/living_pawn = controller.pawn + living_pawn.manual_emote(pick(emotion_list)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/face_target_or_face_initial.dm b/code/datums/ai/generic_behaviors/face_target_or_face_initial.dm new file mode 100644 index 000000000000..14f216d42025 --- /dev/null +++ b/code/datums/ai/generic_behaviors/face_target_or_face_initial.dm @@ -0,0 +1,24 @@ +/// Faces the target each tick while it's within range. When the target is absent or out of range, resets to BB_STARTING_DIRECTION. +/// Captures BB_STARTING_DIRECTION once on setup from the pawn's current facing direction. +/datum/bt_node/ai_behavior/face_target_or_face_initial + var/target_key = BB_CURRENT_TARGET + +/datum/bt_node/ai_behavior/face_target_or_face_initial/setup(datum/ai_controller/controller) + . = ..() + var/mob/living/we = controller.pawn + if(!istype(we)) + return FALSE + var/atom/movable/target = controller.blackboard[target_key] + if(!ismovable(target) || !isturf(target.loc)) + return FALSE + controller.set_blackboard_key(BB_STARTING_DIRECTION, we.dir) + return TRUE + +/datum/bt_node/ai_behavior/face_target_or_face_initial/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/we = controller.pawn + var/atom/movable/target = controller.blackboard[target_key] + if(isnull(target) || get_dist(we, target) > 8) + we.dir = controller.blackboard[BB_STARTING_DIRECTION] + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + we.face_atom(target) + return AI_BEHAVIOR_DELAY diff --git a/code/datums/ai/generic_behaviors/fail.dm b/code/datums/ai/generic_behaviors/fail.dm new file mode 100644 index 000000000000..6a0e6fe583fc --- /dev/null +++ b/code/datums/ai/generic_behaviors/fail.dm @@ -0,0 +1,4 @@ +/datum/bt_node/ai_behavior/fail + +/datum/bt_node/ai_behavior/fail/perform(seconds_per_tick, datum/ai_controller/controller) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED diff --git a/code/datums/ai/generic_behaviors/find_furthest_turf_from_target.dm b/code/datums/ai/generic_behaviors/find_furthest_turf_from_target.dm new file mode 100644 index 000000000000..015d63c8e9e7 --- /dev/null +++ b/code/datums/ai/generic_behaviors/find_furthest_turf_from_target.dm @@ -0,0 +1,32 @@ +/// Finds the open turf furthest from the keyed target and stores it, used to pick an escape destination. +/// Returns INSTANT SUCCESS if a turf is found, INSTANT FAILURE if none are available. +/datum/bt_node/ai_behavior/find_furthest_turf_from_target + /// Blackboard key holding the atom to flee from. + var/target_key + /// Blackboard key to store the chosen turf in. + var/set_key + /// How many tiles outward from the target to scan. + var/range = 2 + +/datum/bt_node/ai_behavior/find_furthest_turf_from_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/best_distance = 0 + var/turf/chosen_turf + for(var/turf/open/potential_destination in oview(range, living_target)) + if(potential_destination.is_blocked_turf()) + continue + var/new_distance = get_dist(potential_destination, living_target) + if(new_distance > best_distance) + chosen_turf = potential_destination + best_distance = new_distance + if(best_distance == range) + break // already at the furthest possible distance + + if(isnull(chosen_turf)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(set_key, chosen_turf) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/find_nearby.dm b/code/datums/ai/generic_behaviors/find_nearby.dm new file mode 100644 index 000000000000..427b7ea79bd7 --- /dev/null +++ b/code/datums/ai/generic_behaviors/find_nearby.dm @@ -0,0 +1,21 @@ +/// Picks a random visible, non-abstract atom within range 2 and stores it in a blackboard key. +/datum/bt_node/ai_behavior/find_nearby + /// Blackboard key to store the found atom in. + var/target_key + +/datum/bt_node/ai_behavior/find_nearby/perform(seconds_per_tick, datum/ai_controller/controller) + var/list/possible_targets = list() + for(var/atom/thing in view(2, controller.pawn)) + if(!thing.mouse_opacity) + continue + if(thing.IsObscured()) + continue + if(isitem(thing)) + var/obj/item/item = thing + if(item.item_flags & ABSTRACT) + continue + possible_targets += thing + if(!possible_targets.len) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + controller.set_blackboard_key(target_key, pick(possible_targets)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/find_target_facing_turf.dm b/code/datums/ai/generic_behaviors/find_target_facing_turf.dm new file mode 100644 index 000000000000..5c968ad0d91a --- /dev/null +++ b/code/datums/ai/generic_behaviors/find_target_facing_turf.dm @@ -0,0 +1,19 @@ +/// Finds the turf directly in front of the keyed target (the tile it is facing) and stores it, +/// so we can line up an action on its front. Fails if the turf is missing or blocked. +/datum/bt_node/ai_behavior/find_target_facing_turf + /// Blackboard key holding the target we want to line up against. + var/target_key + /// Blackboard key to write the found turf into. + var/set_key + +/datum/bt_node/ai_behavior/find_target_facing_turf/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/turf/facing_turf = get_step(target, target.dir) + if(isnull(facing_turf) || facing_turf.is_blocked_turf(ignore_atoms = list(controller.pawn))) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(set_key, facing_turf) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/find_unwebbed_turf.dm b/code/datums/ai/generic_behaviors/find_unwebbed_turf.dm new file mode 100644 index 000000000000..5232fe829f9c --- /dev/null +++ b/code/datums/ai/generic_behaviors/find_unwebbed_turf.dm @@ -0,0 +1,46 @@ +/// Find a nearby unwebbed turf to spin webs on and store it in a blackboard key. +/// Returns INSTANT SUCCESS if we already have a valid target, or if we find a new one. +/// Returns INSTANT FAILURE if no valid turf is nearby. +/datum/bt_node/ai_behavior/find_unwebbed_turf + /// How many tiles outward to scan for valid turfs. + var/scan_range = 3 + /// Blackboard key holding/storing the unwebbed turf. + var/target_key + +/datum/bt_node/ai_behavior/find_unwebbed_turf/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/spider = controller.pawn + var/atom/current_target = controller.blackboard[target_key] + if(current_target && !(locate(/obj/structure/spider/stickyweb) in current_target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + controller.clear_blackboard_key(target_key) + var/turf/our_turf = get_turf(spider) + if(is_valid_web_turf(our_turf, spider)) + controller.set_blackboard_key(target_key, our_turf) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + var/list/turfs_by_range = list() + for(var/i in 1 to scan_range) + turfs_by_range["[i]"] = list() + for(var/turf/turf_in_view in oview(scan_range, our_turf)) + if(!is_valid_web_turf(turf_in_view, spider)) + continue + turfs_by_range["[get_dist(our_turf, turf_in_view)]"] += turf_in_view + + var/list/final_turfs + for(var/list/turf_list as anything in turfs_by_range) + if(length(turfs_by_range[turf_list])) + final_turfs = turfs_by_range[turf_list] + break + if(!length(final_turfs)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(target_key, pick(final_turfs)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/find_unwebbed_turf/proc/is_valid_web_turf(turf/target_turf, mob/living/spider) + if(locate(/obj/structure/spider/stickyweb) in target_turf) + return FALSE + if(HAS_TRAIT(target_turf, TRAIT_SPINNING_WEB_TURF)) + return FALSE + return !target_turf.is_blocked_turf(source_atom = spider) diff --git a/code/datums/ai/generic_behaviors/find_valid_teleport_location.dm b/code/datums/ai/generic_behaviors/find_valid_teleport_location.dm new file mode 100644 index 000000000000..4b0eeeb0529f --- /dev/null +++ b/code/datums/ai/generic_behaviors/find_valid_teleport_location.dm @@ -0,0 +1,28 @@ +/// Finds a random open turf near the keyed target that the target can see, used to pick an ambush teleport destination. +/// Returns SUCCESS if a turf is found, FAILURE if none are available. +/datum/bt_node/ai_behavior/find_valid_teleport_location + /// Blackboard key holding the atom to teleport next to. + var/target_key = BB_CURRENT_TARGET + /// Blackboard key to store the chosen turf in. + var/set_key + /// How many tiles outward from the target to scan. + var/range = 3 + +/datum/bt_node/ai_behavior/find_valid_teleport_location/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/list/possible_turfs = list() + for(var/turf/open/potential_turf in oview(range, target)) + if(potential_turf.is_blocked_turf()) + continue + if(!can_see(target, potential_turf, range)) + continue + possible_turfs += potential_turf + + if(!length(possible_turfs)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(set_key, pick(possible_turfs)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/give.dm b/code/datums/ai/generic_behaviors/give.dm new file mode 100644 index 000000000000..7ff038f1d3a7 --- /dev/null +++ b/code/datums/ai/generic_behaviors/give.dm @@ -0,0 +1,86 @@ +/// gives the pawn's active held item to a blackboard-keyed target. +/datum/bt_node/ai_behavior/give + /// Blackboard key holding the mob to give the held item to. + var/target_key + /// Target snapshotted in perform(), for perform_async() to read. + VAR_PRIVATE/mob/living/give_target + /// Held item snapshotted in perform(), for perform_async() to read. + VAR_PRIVATE/obj/item/give_held_item + +/datum/bt_node/ai_behavior/give/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/mob/living/pawn = controller.pawn + var/obj/item/held_item = pawn.get_active_held_item() + var/atom/target = controller.blackboard[target_key] + + if(!held_item) //if held_item is null, we pretend that action was successful + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + if(QDELETED(target) || !target.IsReachableBy(pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/living_target = target + if(!isliving(living_target)) // target should reasonably only ever be set to a living mob + stack_trace("Tried to give an item to a non-living target!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + if(!can_give_item(living_target, held_item)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + living_target.visible_message( + span_info("[pawn] starts trying to give [held_item] to [living_target]!"), + span_warning("[pawn] tries to give you [held_item]!") + ) + give_target = living_target + give_held_item = held_item + return start_async() + +/datum/bt_node/ai_behavior/give/perform_async(datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + var/mob/living/living_target = give_target + var/obj/item/held_item = give_held_item + var/result_flags = AI_BEHAVIOR_FAILED + if(do_after(pawn, 1 SECONDS, living_target)) + result_flags = try_to_give_item(living_target, held_item) + finish_async(result_flags) + +/// Returns a list(has_left_pocket, has_right_pocket, has_valid_hand) if the item can be given, null otherwise. +/datum/bt_node/ai_behavior/give/proc/can_give_item(mob/living/target, obj/item/held_item) + if(QDELETED(held_item) || QDELETED(target)) + return null + + var/has_left_pocket = target.can_equip(held_item, ITEM_SLOT_LPOCKET) + var/has_right_pocket = target.can_equip(held_item, ITEM_SLOT_RPOCKET) + var/has_valid_hand + + for(var/hand_index in target.get_empty_held_indexes()) + if(target.can_put_in_hand(held_item, hand_index)) + has_valid_hand = TRUE + break + + if(!has_left_pocket && !has_right_pocket && !has_valid_hand) + return null + + return list(has_left_pocket, has_right_pocket, has_valid_hand) + +/datum/bt_node/ai_behavior/give/proc/try_to_give_item(mob/living/target, obj/item/held_item) + var/list/give_slots = can_give_item(target, held_item) + if(!give_slots) + return AI_BEHAVIOR_FAILED + + var/has_left_pocket = give_slots[1] + var/has_valid_hand = give_slots[3] + + if(!has_valid_hand || prob(50)) + target.equip_to_slot_if_possible(held_item, (!has_left_pocket ? ITEM_SLOT_RPOCKET : (prob(50) ? ITEM_SLOT_LPOCKET : ITEM_SLOT_RPOCKET))) + else + INVOKE_ASYNC(target, TYPE_PROC_REF(/mob, put_in_hands), held_item) + return AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/give/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + give_target = null + give_held_item = null + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/generic_behaviors/grab_target.dm b/code/datums/ai/generic_behaviors/grab_target.dm new file mode 100644 index 000000000000..8698199de1dd --- /dev/null +++ b/code/datums/ai/generic_behaviors/grab_target.dm @@ -0,0 +1,33 @@ +/** + * Grabs (starts pulling) the atom at the given blackboard key. + * Succeeds immediately if already pulling the target. + * Fails if the target is anchored + */ +/datum/bt_node/ai_behavior/grab_target + /// Blackboard key holding the atom to grab. + var/target_key + +/datum/bt_node/ai_behavior/grab_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/atom/movable/target = controller.blackboard[target_key] + if(QDELETED(target) || target.anchored) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] grab_target: can't grab [target] (deleted=[QDELETED(target)], anchored=[target?.anchored])") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/our_mob = controller.pawn + if(our_mob.pulling == target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return start_async() + +/datum/bt_node/ai_behavior/grab_target/perform_async(datum/ai_controller/controller) + var/mob/living/our_mob = controller.pawn + var/atom/movable/target = controller.blackboard[target_key] + var/result = our_mob.start_pulling(target) + if(!async_still_valid()) + return + if(result) + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[our_mob] grabbing [target]", get_turf(target), "Grab") + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) diff --git a/code/datums/ai/generic_behaviors/heal_eye_damage.dm b/code/datums/ai/generic_behaviors/heal_eye_damage.dm new file mode 100644 index 000000000000..61f61c75bf5a --- /dev/null +++ b/code/datums/ai/generic_behaviors/heal_eye_damage.dm @@ -0,0 +1,22 @@ +/// Heals the eye damage of the keyed target. Movement to the target is handled externally. +/datum/bt_node/ai_behavior/heal_eye_damage + /// Blackboard key holding the target whose eyes we heal. + var/target_key + +/datum/bt_node/ai_behavior/heal_eye_damage/setup(datum/ai_controller/controller) + var/mob/living/carbon/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/heal_eye_damage/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/carbon/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/basic/eyeball/eye = controller.pawn + var/obj/item/organ/eyes/eyes = target.get_organ_slot(ORGAN_SLOT_EYES) + eye.heal_eye_damage(target, eyes) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/heal_eye_damage/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/generic_behaviors/hunt_target.dm b/code/datums/ai/generic_behaviors/hunt_target.dm new file mode 100644 index 000000000000..5eb684e79dbf --- /dev/null +++ b/code/datums/ai/generic_behaviors/hunt_target.dm @@ -0,0 +1,184 @@ +/// Performs an action on a blackboard-keyed hunt target once in range. +/// Movement to the target is handled externally via a move_to_target leaf. +/datum/bt_node/ai_behavior/hunt_target + /// Blackboard key holding the atom to hunt + var/target_key + /// Blackboard key to record the cooldown timestamp on success; if null, no cooldown is set + var/cooldown_key + /// Duration of the post-hunt cooldown + var/hunt_cooldown = 5 SECONDS + /// If TRUE, clears target_key after every hunt regardless of success + var/always_reset_target = FALSE + /// Target snapshotted in perform() for async subtypes, for perform_async() to read. + VAR_PRIVATE/atom/hunt_async_target + +/datum/bt_node/ai_behavior/hunt_target/setup(datum/ai_controller/controller) + var/atom/hunted = controller.blackboard[target_key] + return !QDELETED(hunted) + +/datum/bt_node/ai_behavior/hunt_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/hunted = controller.blackboard[target_key] + if(QDELETED(hunted)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + target_caught(controller.pawn, hunted) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/hunt_target/proc/target_caught(mob/living/hunter, atom/hunted) + if(isliving(hunted)) + var/mob/living/living_target = hunted + hunter.manual_emote("chomps [living_target]!") + living_target.investigate_log("has been killed by [key_name(hunter)].", INVESTIGATE_DEATHS) + living_target.death() + else if(IS_EDIBLE(hunted)) + hunted.attack_animal(hunter) + else + hunter.manual_emote("chomps [hunted]!") + qdel(hunted) + +/datum/bt_node/ai_behavior/hunt_target/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + hunt_async_target = null + if(succeeded && cooldown_key) + controller.set_blackboard_key(cooldown_key, world.time + hunt_cooldown) + else if(target_key) + controller.clear_blackboard_key(target_key) + if(always_reset_target && target_key) + controller.clear_blackboard_key(target_key) + +/// Uses ai_interact() on the target instead of default kill/eat/qdel. +/// behavior_combat_mode, always_reset_target, and hunt_cooldown are all configurable +/datum/bt_node/ai_behavior/hunt_target/interact_with_target + /// Combat mode to use when interacting with the target + var/behavior_combat_mode = TRUE + +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/atom/hunted = controller.blackboard[target_key] + if(QDELETED(hunted)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + hunt_async_target = hunted + return start_async() + +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/perform_async(datum/ai_controller/controller) + target_caught(controller.pawn, hunt_async_target) + if(!async_still_valid()) + return + finish_async(AI_BEHAVIOR_SUCCEEDED) + +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/target_caught(mob/living/hunter, atom/hunted) + hunter.ai_controller.ai_interact(target = hunted, combat_mode = behavior_combat_mode) + +/// Uses a cooldown ability from ability_key on the target. +/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target + always_reset_target = TRUE + /// Blackboard key holding the /datum/action/cooldown ability to use + var/ability_key + /// Ability snapshotted in perform(), for perform_async() to read. + VAR_PRIVATE/datum/action/cooldown/hunt_async_ability + +/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + if(!ability?.IsAvailable()) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/atom/hunted = controller.blackboard[target_key] + if(QDELETED(hunted)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + hunt_async_target = hunted + hunt_async_ability = ability + return start_async() + +/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target/perform_async(datum/ai_controller/controller) + target_caught(controller.pawn, hunt_async_target) + if(!async_still_valid()) + return + finish_async(AI_BEHAVIOR_SUCCEEDED) + +/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + hunt_async_ability = null + +/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target/target_caught(mob/living/hunter, atom/hunted) + hunt_async_ability.InterceptClickOn(hunter, null, hunted) + +/// Celebrates around the target with a spin animation. +/datum/bt_node/ai_behavior/hunt_target/snail_people + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/snail_people/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("Celebrates around [hunted]!") + hunter.SpinAnimation(speed = 1, loops = 3) + +/// Starts pulling the target item toward the hunter. +/datum/bt_node/ai_behavior/hunt_target/pull_target + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/pull_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/obj/item/hunted = controller.blackboard[target_key] + if(QDELETED(hunted)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + hunt_async_target = hunted + return start_async() + +/datum/bt_node/ai_behavior/hunt_target/pull_target/perform_async(datum/ai_controller/controller) + target_caught(controller.pawn, hunt_async_target) + if(!async_still_valid()) + return + finish_async(AI_BEHAVIOR_SUCCEEDED) + +/datum/bt_node/ai_behavior/hunt_target/pull_target/target_caught(mob/living/hunter, obj/item/hunted) + hunter.start_pulling(hunted) + +/// Emotes enjoyment of the target's scent. +/datum/bt_node/ai_behavior/hunt_target/sniff_flora + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/sniff_flora/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("Enjoys the sweet scent eminating from [hunted::name]!") + +/// Playfully headbutts the target's legs. +/datum/bt_node/ai_behavior/hunt_target/headbutt_leg + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/headbutt_leg/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("playfully headbutts [hunted]'s legs!") + +/// Attempts to buckle (latch onto) the target mob. +/datum/bt_node/ai_behavior/hunt_target/latch_onto + +/datum/bt_node/ai_behavior/hunt_target/latch_onto/setup(datum/ai_controller/controller) + if(!..()) + return FALSE + var/mob/living/living_pawn = controller.pawn + return !living_pawn.buckled + +/datum/bt_node/ai_behavior/hunt_target/latch_onto/target_caught(mob/living/hunter, obj/hunted) + if(hunter.buckled) + return FALSE + if(!hunted.buckle_mob(hunter, force = TRUE)) + return FALSE + hunted.visible_message(span_notice("[hunted] has been latched onto by [hunter]!")) + return TRUE + + +/datum/bt_node/ai_behavior/hunt_target/play_with_owner + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/play_with_owner/target_caught(mob/living/hunter, atom/hunted) + var/list/interactions_list = hunter.ai_controller.blackboard[BB_INTERACTIONS_WITH_OWNER] + var/interaction_message = length(interactions_list) ? pick(interactions_list) : "Plays with" + hunter.manual_emote("[interaction_message] [hunted]!") diff --git a/code/datums/ai/generic_behaviors/issue_pet_command.dm b/code/datums/ai/generic_behaviors/issue_pet_command.dm new file mode 100644 index 000000000000..92aaea16e221 --- /dev/null +++ b/code/datums/ai/generic_behaviors/issue_pet_command.dm @@ -0,0 +1,41 @@ +/** + * Issues a spoken pet command and points at the target. + * Requires a blackboard key holding a list of /datum/pet_command instances. + * Use a cooldown decorator in the tree time_between_perform has no effect on one-shot behaviors. + */ +/datum/bt_node/ai_behavior/issue_pet_command + /// Blackboard key holding a list of /datum/pet_command instances to search. + var/command_list_key + /// Typepath of the /datum/pet_command to locate in the list. + var/command_type + /// Blackboard key holding the target atom to point at. + var/target_key + /// If set, setup() requires at least one mob of this type within command_distance. + var/commandable_mob_type + /// Range to search for commandable mobs. + var/command_distance = 5 + +/datum/bt_node/ai_behavior/issue_pet_command/setup(datum/ai_controller/controller) + . = ..() + if(!.) + return FALSE + if(commandable_mob_type) + if(!locate(commandable_mob_type) in oview(command_distance, controller.pawn)) + return FALSE + var/atom/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/issue_pet_command/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/list/commands = controller.blackboard[command_list_key] + if(!length(commands)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/datum/pet_command/cmd = locate(command_type) in commands + if(isnull(cmd)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), pick(cmd.speech_commands), forced = "controller") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, _pointed), target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/keep_playing_instrument.dm b/code/datums/ai/generic_behaviors/keep_playing_instrument.dm new file mode 100644 index 000000000000..e0d9c28f27a6 --- /dev/null +++ b/code/datums/ai/generic_behaviors/keep_playing_instrument.dm @@ -0,0 +1,19 @@ +/// Checks that a song instrument's song is still properly configured and playing. +/// Returns SUCCEEDED while the song is playing correctly, FAILED when it needs setup or restart. +/datum/bt_node/ai_behavior/keep_playing_instrument + time_between_perform = 1 SECONDS + /// Blackboard key holding the instrument being played. + var/song_instrument_key + +/datum/bt_node/ai_behavior/keep_playing_instrument/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/instrument/song_player = controller.blackboard[song_instrument_key] + if(QDELETED(song_player)) + controller.clear_blackboard_key(song_instrument_key) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/list/parsed_song_lines = splittext(controller.blackboard[BB_SONG_LINES], "\n") + popleft(parsed_song_lines) // remove BPM as it is parsed out + if(!compare_list(song_player.song.lines, parsed_song_lines) || !song_player.song.repeat) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!song_player.song.playing) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/maintain_distance.dm b/code/datums/ai/generic_behaviors/maintain_distance.dm new file mode 100644 index 000000000000..0f48cc359cb4 --- /dev/null +++ b/code/datums/ai/generic_behaviors/maintain_distance.dm @@ -0,0 +1,94 @@ +/// Moves away from a target if too close, or toward it if too far, to stay within the blackboard-keyed distance band. +/datum/bt_node/ai_behavior/maintain_distance + /// Set by on_movement_failed() when the movement system gives up pathing. + var/movement_failed = FALSE + /// Blackboard key holding the atom to keep distance from. + var/target_key + /// Blackboard key holding the minimum desired distance. + var/min_dist_key = BB_RANGED_SKIRMISH_MIN_DISTANCE + /// Blackboard key holding the maximum desired distance. + var/max_dist_key = BB_RANGED_SKIRMISH_MAX_DISTANCE + /// Movement type to use while approaching a target that's too far. Null falls back to the controller's default movement type. Reset on finish. + var/approach_movement_type = null + +/datum/bt_node/ai_behavior/maintain_distance/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(!isliving(target) || !can_see(controller.pawn, target, 10)) + return FALSE + RegisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED, PROC_REF(on_movement_failed)) + return TRUE + +/datum/bt_node/ai_behavior/maintain_distance/proc/on_movement_failed(atom/source) + SIGNAL_HANDLER + movement_failed = TRUE + +/datum/bt_node/ai_behavior/maintain_distance/perform(seconds_per_tick, datum/ai_controller/controller) + if(movement_failed) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_FAILED + + + var/minimum_distance = controller.blackboard[min_dist_key] || 4 + var/maximum_distance = controller.blackboard[max_dist_key] || 6 + var/range = get_dist(controller.pawn, target) + + if(range >= minimum_distance && range <= maximum_distance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + if(range < minimum_distance) + if(!retreat(controller, target, minimum_distance)) + return AI_BEHAVIOR_FAILED + else + controller.change_ai_movement_type(approach_movement_type || initial(controller.ai_movement)) + controller.ai_movement.start_moving_towards(controller, target, maximum_distance) + + return AI_BEHAVIOR_INSTANT + +/datum/bt_node/ai_behavior/maintain_distance/finish_action(datum/ai_controller/controller, succeeded) + UnregisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED) + movement_failed = FALSE + controller.ai_movement.stop_moving_towards(controller) + controller.change_ai_movement_type(initial(controller.ai_movement)) + return ..() + +/// Steps one tile away from target using backstep avoidance, falling back to shuffled directions if blocked. +/datum/bt_node/ai_behavior/maintain_distance/proc/retreat(datum/ai_controller/controller, atom/target, minimum_distance) + controller.change_ai_movement_type(/datum/ai_movement/basic_avoidance/backstep) + var/mob/pawn = controller.pawn + pawn.face_atom(target) + var/turf/next_step = get_step_away(pawn, target) + if(!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) + controller.ai_movement.start_moving_towards(controller, next_step, 0, controller.movement_delay * 2) + return TRUE + var/list/all_dirs = GLOB.alldirs.Copy() + all_dirs -= get_dir(pawn, next_step) + all_dirs -= get_dir(pawn, target) + shuffle_inplace(all_dirs) + for(var/dir in all_dirs) + next_step = get_step(pawn, dir) + if(!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) + controller.ai_movement.start_moving_towards(controller, next_step, 0, controller.movement_delay * 2) + return TRUE + return FALSE + +/// Retreats to the furthest available open turf within reach rather than a single step. +/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance + +/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance/retreat(datum/ai_controller/controller, atom/target, minimum_distance) + var/atom/movable/pawn = controller.pawn + var/required_distance = minimum_distance - get_dist(pawn, target) + var/best_distance = 0 + var/turf/chosen_turf + for(var/turf/open/potential_turf in oview(required_distance, pawn)) + if(potential_turf.is_blocked_turf()) + continue + var/new_distance = get_dist(potential_turf, target) + if(new_distance > best_distance) + chosen_turf = potential_turf + best_distance = new_distance + if(isnull(chosen_turf)) + return FALSE + controller.ai_movement.start_moving_towards(controller, chosen_turf, 0) + return TRUE diff --git a/code/datums/ai/generic_behaviors/mine_walls.dm b/code/datums/ai/generic_behaviors/mine_walls.dm new file mode 100644 index 000000000000..aa6f95061b2d --- /dev/null +++ b/code/datums/ai/generic_behaviors/mine_walls.dm @@ -0,0 +1,53 @@ +/// Searches for a nearby mineral wall the pawn can mine and sets the target key. +/datum/bt_node/ai_behavior/find_mineral_wall + time_between_perform = 2 SECONDS + /// Blackboard key to store the found mineral wall in. + var/target_key + +/datum/bt_node/ai_behavior/find_mineral_wall/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living_pawn = controller.pawn + for(var/turf/closed/mineral/potential_wall in oview(9, living_pawn)) + if(!check_if_mineable(controller, potential_wall)) + continue + controller.set_blackboard_key(target_key, potential_wall) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Returns TRUE if the given wall can be approached and mined. +/datum/bt_node/ai_behavior/find_mineral_wall/proc/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/mob/living/source = controller.pawn + var/direction_to_turf = get_dir(target_wall, source) + if(!ISDIAGONALDIR(direction_to_turf)) + return TRUE + for(var/direction_check in GLOB.cardinals) + if(!(direction_check & direction_to_turf)) + continue + var/turf/test_turf = get_step(target_wall, direction_check) + if(isnull(test_turf)) + continue + if(!test_turf.is_blocked_turf(ignore_atoms = list(source))) + return TRUE + return FALSE + +/// Mines the mineral wall at target_key when adjacent. Clears the target key on finish. +/datum/bt_node/ai_behavior/mine_wall + /// Blackboard key holding the mineral wall to mine. + var/target_key + +/datum/bt_node/ai_behavior/mine_wall/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/living_pawn = controller.pawn + var/turf/closed/mineral/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!living_pawn.Adjacent(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!controller.ai_can_interact(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target) + if(istype(target, /turf/closed/mineral/gibtonite)) + living_pawn.manual_emote("sighs...") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/mine_wall/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/generic_behaviors/move_to_target.dm b/code/datums/ai/generic_behaviors/move_to_target.dm new file mode 100644 index 000000000000..076012a59172 --- /dev/null +++ b/code/datums/ai/generic_behaviors/move_to_target.dm @@ -0,0 +1,55 @@ +/// Moves toward a blackboard-keyed target each tick. Succeeds when within required_dist by default. +/// Pass finish_on_arrival = FALSE to keep running indefinitely (used in combat parallels). +/// Pass movement_type to temporarily override the controller's ai_movement; resets to the initial type on finish. +/datum/bt_node/ai_behavior/move_to_target + /// Set by on_movement_failed() when the movement system gives up pathing. + VAR_FINAL/movement_failed = FALSE + /// Blackboard key holding the atom to move toward. + var/target_key + /// Distance at which arrival is considered reached. + var/required_dist = 1 + /// Whether to succeed once within required_dist; FALSE keeps running indefinitely. + var/finish_on_arrival = TRUE + /// Optional ai_movement type override; resets to the initial type on finish. + var/movement_type = null + /// Tracks the last atom we started moving toward so we can retarget when the key changes. + VAR_PRIVATE/atom/tracked_target + +/datum/bt_node/ai_behavior/move_to_target/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + if(movement_type) + controller.change_ai_movement_type(movement_type) + RegisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED, PROC_REF(on_movement_failed)) + controller.ai_movement.start_moving_towards(controller, target, required_dist) + tracked_target = target + return TRUE + +/datum/bt_node/ai_behavior/move_to_target/proc/on_movement_failed(atom/source) + SIGNAL_HANDLER + movement_failed = TRUE + +/datum/bt_node/ai_behavior/move_to_target/perform(seconds_per_tick, datum/ai_controller/controller) + if(movement_failed) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_FAILED + if(target != tracked_target) + controller.ai_movement.start_moving_towards(controller, target, required_dist) + tracked_target = target + else if(!controller.ai_movement.moving_controllers[controller]) + controller.ai_movement.start_moving_towards(controller, target, required_dist) + if(finish_on_arrival && get_dist(controller.pawn, target) <= required_dist) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_INSTANT + +/datum/bt_node/ai_behavior/move_to_target/finish_action(datum/ai_controller/controller, succeeded) + UnregisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED) + movement_failed = FALSE + tracked_target = null + controller.ai_movement.stop_moving_towards(controller) + if(movement_type) + controller.change_ai_movement_type(initial(controller.ai_movement)) + return ..() diff --git a/code/datums/ai/generic_behaviors/perform_emote.dm b/code/datums/ai/generic_behaviors/perform_emote.dm new file mode 100644 index 000000000000..c0f49f05066f --- /dev/null +++ b/code/datums/ai/generic_behaviors/perform_emote.dm @@ -0,0 +1,11 @@ +/// Performs a named emote on the pawn (BT-native). Use for standard emote types like "flip", "wave", etc. +/datum/bt_node/ai_behavior/perform_emote + /// Name of the emote to perform. + var/emote + +/datum/bt_node/ai_behavior/perform_emote/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!istype(living_pawn)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), emote) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/pick_random_ability.dm b/code/datums/ai/generic_behaviors/pick_random_ability.dm new file mode 100644 index 000000000000..3f81dbfe096d --- /dev/null +++ b/code/datums/ai/generic_behaviors/pick_random_ability.dm @@ -0,0 +1,27 @@ +/// Picks a random available ability from a pool of blackboard keys, excluding the last-used one. +/// Writes the selected action object to result_key for a targeted_mob_ability leaf to fire. +/// Returns INSTANT FAILURE if no ability in the pool is currently available. +/datum/bt_node/ai_behavior/pick_random_ability + /// List of blackboard key name strings to pick from. + var/list/ability_keys = null + /// Blackboard key storing the last-picked key name string (anti-repeat). Can be null. + var/last_used_key = null + /// Blackboard key to write the selected action object into. + var/result_key = BB_GENERIC_ACTION + +/datum/bt_node/ai_behavior/pick_random_ability/perform(seconds_per_tick, datum/ai_controller/controller) + var/list/possible = ability_keys.Copy() + var/last_used = last_used_key ? controller.blackboard[last_used_key] : null + if(last_used) + possible -= last_used + for(var/bb_key in possible) + var/datum/action/ability = controller.blackboard[bb_key] + if(QDELETED(ability) || !ability.IsAvailable()) + possible -= bb_key + if(!length(possible)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/picked_key = pick(possible) + if(last_used_key) + controller.set_blackboard_key(last_used_key, picked_key) + controller.set_blackboard_key(result_key, controller.blackboard[picked_key]) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/pick_up.dm b/code/datums/ai/generic_behaviors/pick_up.dm new file mode 100644 index 000000000000..fba4ffbacd13 --- /dev/null +++ b/code/datums/ai/generic_behaviors/pick_up.dm @@ -0,0 +1,28 @@ +/// Picks up a blackboard-keyed item. Pair with move_to_target in the BT tree for navigation. +/datum/bt_node/ai_behavior/pick_up + time_between_perform = 2 SECONDS + /// Blackboard key holding the item to pick up. + var/target_key + /// Whether to drop a currently-held item to free a hand. + var/drop_held = TRUE + +/datum/bt_node/ai_behavior/pick_up/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/pick_up/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/obj/item/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + if(living_pawn.is_holding(target)) // already in hands + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + if(!target.IsReachableBy(living_pawn)) // cant reach! + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(living_pawn.get_active_held_item()) // something is in our hands already + if(!drop_held || !living_pawn.dropItemToGround(living_pawn.get_active_held_item())) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, FALSE) + return AI_BEHAVIOR_DELAY | (target.loc == living_pawn ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) diff --git a/code/datums/ai/generic_behaviors/play_instrument.dm b/code/datums/ai/generic_behaviors/play_instrument.dm new file mode 100644 index 000000000000..a2ba4a08fde5 --- /dev/null +++ b/code/datums/ai/generic_behaviors/play_instrument.dm @@ -0,0 +1,13 @@ +/// Starts playing the song loaded into a blackboard-keyed instrument. +/datum/bt_node/ai_behavior/play_instrument + var/volume = 50 + /// Blackboard key holding the instrument to play. + var/song_instrument_key + +/datum/bt_node/ai_behavior/play_instrument/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key] + var/datum/song/song = song_instrument.song + song.volume = volume + + song.start_playing(controller.pawn) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/random_walk.dm b/code/datums/ai/generic_behaviors/random_walk.dm new file mode 100644 index 000000000000..4cbda837ba26 --- /dev/null +++ b/code/datums/ai/generic_behaviors/random_walk.dm @@ -0,0 +1,111 @@ +// BT-native idle wander leaves. +/datum/bt_node/ai_behavior/idle_random_walk + /// Chance that the mob random walks per second. + var/walk_chance = 25 + +/datum/bt_node/ai_behavior/idle_random_walk/proc/can_move(mob/living/living_pawn) + return living_pawn && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby + +/datum/bt_node/ai_behavior/idle_random_walk/proc/try_random_step(mob/living/living_pawn, seconds_per_tick, step_walk_chance) + if(LAZYLEN(living_pawn.do_afters)) + return FALSE + + if(!SPT_PROB(step_walk_chance, seconds_per_tick) || !can_move(living_pawn)) + return FALSE + + var/move_dir = pick(GLOB.alldirs) + var/turf/destination_turf = get_step(living_pawn, move_dir) + if(!destination_turf?.can_cross_safely(living_pawn)) + return FALSE + + living_pawn.Move(destination_turf, move_dir) + return TRUE + +/datum/bt_node/ai_behavior/idle_random_walk/proc/try_near_target_step(mob/living/living_pawn, atom/target, seconds_per_tick, step_walk_chance, minimum_distance) + if(isnull(target) || !can_move(living_pawn) || LAZYLEN(living_pawn.do_afters)) + return FALSE + + if(!SPT_PROB(step_walk_chance, seconds_per_tick)) + return FALSE + + var/distance = get_dist(target, living_pawn) + if(distance > minimum_distance) + return try_random_step(living_pawn, seconds_per_tick, step_walk_chance) + + var/list/possible_turfs = list() + for(var/direction in GLOB.alldirs) + var/turf/possible_step = get_step(living_pawn, direction) + if(get_dist(possible_step, target) > minimum_distance) + continue + if(possible_step.is_blocked_turf() || !possible_step.can_cross_safely(living_pawn)) + continue + possible_turfs += possible_step + + if(!length(possible_turfs)) + return FALSE + + var/turf/picked_turf = pick(possible_turfs) + living_pawn.Move(picked_turf, get_dir(living_pawn, picked_turf)) + return TRUE + +/datum/bt_node/ai_behavior/idle_random_walk/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + try_random_step(living_pawn, seconds_per_tick, walk_chance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +// Only walk if we don't have a target. +/datum/bt_node/ai_behavior/idle_random_walk/no_target + /// Where do we look for a target? + var/target_key = BB_CURRENT_TARGET + +/datum/bt_node/ai_behavior/idle_random_walk/no_target/perform(seconds_per_tick, datum/ai_controller/controller) + if(controller.blackboard_key_exists(target_key)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return ..() + +// Only walk if we are not on the target's location. +/datum/bt_node/ai_behavior/idle_random_walk/not_while_on_target + /// What is the spot we have to stand on? + var/target_key + +/datum/bt_node/ai_behavior/idle_random_walk/not_while_on_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(!QDELETED(target) && ((isturf(target) && controller.pawn.loc == target) || (target.loc == controller.pawn.loc))) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return ..() + +// Walk more when healthy and on rust (explore for more), or damaged and off rust (seek healing rust). +/datum/bt_node/ai_behavior/idle_random_walk/rust + +/datum/bt_node/ai_behavior/idle_random_walk/rust/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/turf/my_turf = get_turf(living_pawn) + var/on_rust = HAS_TRAIT(my_turf, TRAIT_RUSTY) + var/damaged = living_pawn.health < living_pawn.maxHealth + try_random_step(living_pawn, seconds_per_tick, (on_rust == damaged) ? 10 : 50) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +// Walk randomly, but stay near a target when possible. +/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target + /// Distance we are allowed to stay from the target. + var/minimum_distance = 20 + /// Key that holds target. + var/target_key + +/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + target = null + + if(!can_move(living_pawn) || LAZYLEN(living_pawn.do_afters) || !SPT_PROB(walk_chance, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + if(isnull(target) || get_dist(target, living_pawn) > minimum_distance) + try_random_step(living_pawn, seconds_per_tick, walk_chance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + if(!try_near_target_step(living_pawn, target, seconds_per_tick, walk_chance, minimum_distance)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/resist.dm b/code/datums/ai/generic_behaviors/resist.dm new file mode 100644 index 000000000000..aa5a43111984 --- /dev/null +++ b/code/datums/ai/generic_behaviors/resist.dm @@ -0,0 +1,14 @@ +/datum/bt_node/ai_behavior/resist/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + return start_async() + +/datum/bt_node/ai_behavior/resist/perform_async(datum/ai_controller/controller) + + if(!async_still_valid()) + return + var/mob/living/living_pawn = controller.pawn + living_pawn.execute_resist() + finish_async(AI_BEHAVIOR_SUCCEEDED) diff --git a/code/datums/ai/generic_behaviors/run_emote.dm b/code/datums/ai/generic_behaviors/run_emote.dm new file mode 100644 index 000000000000..985cb18a3b0d --- /dev/null +++ b/code/datums/ai/generic_behaviors/run_emote.dm @@ -0,0 +1,21 @@ +/datum/bt_node/ai_behavior/run_emote + /// Blackboard key holding the emote (or list of emotes) to perform. + var/emote_key = BB_EMOTE_KEY + +/datum/bt_node/ai_behavior/run_emote/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!isliving(living_pawn)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/list/emote_list = controller.blackboard[emote_key] + var/emote + if(islist(emote_list)) + emote = length(emote_list) ? pick(emote_list) : null + else + emote = emote_list + + if(isnull(emote)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), emote) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/set_bb_cooldown.dm b/code/datums/ai/generic_behaviors/set_bb_cooldown.dm new file mode 100644 index 000000000000..36e03e266e47 --- /dev/null +++ b/code/datums/ai/generic_behaviors/set_bb_cooldown.dm @@ -0,0 +1,10 @@ +/// Sets the given blackboard key to a future timestamp, blocking key_off_cooldown until that time. +/datum/bt_node/ai_behavior/set_bb_cooldown + /// Blackboard key to write the cooldown timestamp into. + var/cooldown_key + /// Cooldown duration, in seconds. + var/cooldown_duration + +/datum/bt_node/ai_behavior/set_bb_cooldown/perform(seconds_per_tick, datum/ai_controller/controller) + controller.set_blackboard_key(cooldown_key, world.time + cooldown_duration) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/set_bb_key.dm b/code/datums/ai/generic_behaviors/set_bb_key.dm new file mode 100644 index 000000000000..0470bf116222 --- /dev/null +++ b/code/datums/ai/generic_behaviors/set_bb_key.dm @@ -0,0 +1,10 @@ +/// Sets the given blackboard key to a value. +/datum/bt_node/ai_behavior/set_bb_key + /// Blackboard key to set. + var/target_key + /// Value to store in the key. + var/value + +/datum/bt_node/ai_behavior/set_bb_key/perform(seconds_per_tick, datum/ai_controller/controller) + controller.set_blackboard_key(target_key, value) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/setup_instrument.dm b/code/datums/ai/generic_behaviors/setup_instrument.dm new file mode 100644 index 000000000000..e26bea9a5ca3 --- /dev/null +++ b/code/datums/ai/generic_behaviors/setup_instrument.dm @@ -0,0 +1,18 @@ +/// Parses and loads a song into a blackboard-keyed instrument, preparing it for playback. +/datum/bt_node/ai_behavior/setup_instrument + /// Blackboard key holding the instrument to load the song into. + var/song_instrument_key + /// Blackboard key holding the song lines to parse. + var/song_lines_key + +/datum/bt_node/ai_behavior/setup_instrument/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key] + var/datum/song/song = song_instrument.song + var/song_lines = controller.blackboard[song_lines_key] + + //just in case- it won't do anything if the instrument isn't playing + song.stop_playing() + song.ParseSong(new_song = song_lines) + song.repeat = 10 + song.volume = song.max_volume - 10 + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/speech.dm b/code/datums/ai/generic_behaviors/speech.dm new file mode 100644 index 000000000000..6a5acfc02d4e --- /dev/null +++ b/code/datums/ai/generic_behaviors/speech.dm @@ -0,0 +1,184 @@ +///Random speech behavior, for speech thats random +/datum/bt_node/ai_behavior/random_speech + time_between_perform = 1 SECONDS + /// Chance that the mob will speak. + var/speech_chance = 1 + /// Hearable emotes (e.g. "barks.") played with sound if sound list is populated. + var/list/emote_hear + /// Visible-only emotes (e.g. "wags tail.") no sound. + var/list/emote_see + /// Spoken lines. + var/list/speak + /// Sound files to play alongside emote_hear or speak lines. + var/list/sound + +/datum/bt_node/ai_behavior/random_speech/perform(seconds_per_tick, datum/ai_controller/controller) + if(!prob(speech_chance)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/audible = length(emote_hear) + var/visible = length(emote_see) + var/spoken = length(speak) + var/total = audible + visible + spoken + if(!total) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/pawn = controller.pawn + if(!istype(pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/roll = rand(1, total) + + if(roll <= audible) + pawn.manual_emote(pick(emote_hear)) + if(length(sound)) + playsound(pawn, pick(sound), 80, vary = TRUE, pressure_affected = TRUE, ignore_walls = FALSE) + else if(roll <= audible + visible) + pawn.manual_emote(pick(emote_see)) + else + INVOKE_ASYNC(src, PROC_REF(speak), pawn, controller) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/random_speech/proc/speak(mob/living/pawn, datum/ai_controller/controller) + pawn.say(pick(speak), forced = "AI Controller") + if(length(sound)) + playsound(pawn, pick(sound), 80, vary = TRUE) + +/datum/bt_node/ai_behavior/random_speech/mothroach + speech_chance = 15 + emote_hear = list("flutters.") + +/datum/bt_node/ai_behavior/random_speech/mouse + speech_chance = 1 + speak = list("Squeak!", "SQUEAK!", "Squeak?") + sound = list('sound/mobs/non-humanoids/mouse/mousesqueek.ogg') + emote_hear = list("squeaks.") + emote_see = list("runs in a circle.", "shakes.") + +/datum/bt_node/ai_behavior/random_speech/frog + speech_chance = 3 + emote_see = list("jumps in a circle.", "shakes.") + +/datum/bt_node/ai_behavior/random_speech/lizard + speech_chance = 3 + emote_hear = list("stamps around some.", "hisses a bit.") + emote_see = list("blehs the tongue.", "tilts the head.", "does a spin.") + +/datum/bt_node/ai_behavior/random_speech/faithless + speech_chance = 1 + emote_see = list("wails.") + +/datum/bt_node/ai_behavior/random_speech/garden_gnome + speech_chance = 5 + speak = list("Gnot a gnelf!", "Gnot a gnoblin!", "Howdy chum!") + emote_hear = list("snores.", "burps.") + emote_see = list("blinks.") + +/datum/bt_node/ai_behavior/random_speech/killer_tomato + speech_chance = 3 + emote_hear = list("gnashes.", "growls lowly.", "snarls.") + emote_see = list("salivates.") + +/datum/bt_node/ai_behavior/random_speech/ant + speech_chance = 1 + speak = list("BZZZZT!", "CHTCHTCHT!", "Bzzz", "ChtChtCht") + sound = list('sound/mobs/non-humanoids/insect/chitter.ogg') + emote_hear = list("buzzes.", "clacks.") + emote_see = list("shakes their head.", "twitches their antennae.") + +/datum/bt_node/ai_behavior/random_speech/fox + speech_chance = 1 + speak = list("Ack-Ack", "Ack-Ack-Ack-Ackawoooo", "Geckers", "Awoo", "Tchoff") + emote_hear = list("howls.", "barks.", "screams.") + emote_see = list("shakes their head.", "shivers.") + +/datum/bt_node/ai_behavior/random_speech/crab + speech_chance = 1 + sound = list('sound/mobs/non-humanoids/crab/claw_click.ogg') + emote_hear = list("clicks.") + emote_see = list("clacks.") + +/datum/bt_node/ai_behavior/random_speech/penguin + speech_chance = 5 + speak = list("Gah Gah!", "NOOT NOOT!", "NOOT!", "Noot", "noot", "Prah!", "Grah!") + emote_hear = list("squawks", "gakkers") + +/datum/bt_node/ai_behavior/random_speech/bear + speech_chance = 5 + emote_hear = list("rawrs.", "grumbles.", "grawls.", "stomps!") + emote_see = list("stares ferociously.") + +/datum/bt_node/ai_behavior/random_speech/cats + speech_chance = 10 + sound = list(SFX_CAT_MEOW) + emote_hear = list("meows.") + emote_see = list("meows.") + +/// Make spooky sounds, if we have a corpse inside then impersonate them +/datum/bt_node/ai_behavior/random_speech/legion + speech_chance = 1 + speak = list("Come...", "Legion...", "Why...?") + emote_hear = list("groans.", "wails.", "whimpers.") + emote_see = list("twitches.", "shudders.") + /// Stuff to specifically say into a radio + var/list/radio_speech = list("Come...", "Why...?") + +/datum/bt_node/ai_behavior/random_speech/legion/speak(mob/living/pawn, datum/ai_controller/controller) + var/mob/living/carbon/human/victim = controller.blackboard[BB_LEGION_CORPSE] + if (QDELETED(victim) || prob(30)) + return ..() + + if (HAS_MIND_TRAIT(victim, TRAIT_MIMING)) // mimes cant talk + return + + var/list/remembered_speech = controller.blackboard[BB_LEGION_RECENT_LINES] || list() + + if (length(remembered_speech) && prob(50)) // Don't spam the radio + pawn.say(pick(remembered_speech), forced = "AI Controller") + return + + var/obj/item/radio/mob_radio = locate() in victim + if (QDELETED(mob_radio)) + return ..() // No radio, just talk funny + mob_radio.talk_into(pawn, pick(radio_speech + remembered_speech), pick(RADIO_CHANNEL_SUPPLY, RADIO_CHANNEL_COMMON)) + +///Speech behavior that reads from a blackboard to pick what to say. Useful for things with dynamic speech behaviors +/datum/bt_node/ai_behavior/random_speech_blackboard + +/datum/bt_node/ai_behavior/random_speech_blackboard/perform(seconds_per_tick, datum/ai_controller/controller) + var/list/speech_lines = controller.blackboard[BB_BASIC_MOB_SPEAK_LINES] + if(isnull(speech_lines)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/speech_chance = speech_lines[BB_SPEAK_CHANCE] || 1 + if(!prob(speech_chance)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/list/emote_hear = speech_lines[BB_EMOTE_HEAR] || list() + var/list/emote_see = speech_lines[BB_EMOTE_SEE] || list() + var/list/speak = speech_lines[BB_EMOTE_SAY] || list() + var/list/sounds = speech_lines[BB_EMOTE_SOUND] || list() + + var/total = length(emote_hear) + length(emote_see) + length(speak) + if(!total) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/pawn = controller.pawn + if(!istype(pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/sound_to_play = length(sounds) ? pick(sounds) : null + var/roll = rand(1, total) + + if(roll <= length(emote_hear)) + pawn.manual_emote(pick(emote_hear)) + if(sound_to_play) + playsound(pawn, sound_to_play, 80, vary = TRUE, pressure_affected = TRUE, ignore_walls = FALSE) + else if(roll <= length(emote_hear) + length(emote_see)) + pawn.manual_emote(pick(emote_see)) + else + INVOKE_ASYNC(pawn, TYPE_PROC_REF(/atom/movable, say), pick(speak), forced = "AI Controller") + if(sound_to_play) + playsound(pawn, sound_to_play, 80, vary = TRUE) + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/spin_web.dm b/code/datums/ai/generic_behaviors/spin_web.dm new file mode 100644 index 000000000000..91f7b684ca8c --- /dev/null +++ b/code/datums/ai/generic_behaviors/spin_web.dm @@ -0,0 +1,35 @@ +/// Trigger a web-spinning action at the current web target turf. +/// Expects move_to_target to have positioned the spider first. +/// Clears BB_SPIDER_WEB_TARGET on finish. +/datum/bt_node/ai_behavior/spin_web + time_between_perform = 15 SECONDS + /// Blackboard key holding the web-spinning action. + var/action_key + /// Blackboard key holding the target turf to web. + var/target_key + +/datum/bt_node/ai_behavior/spin_web/setup(datum/ai_controller/controller) + if(!controller.blackboard_key_exists(action_key) || !controller.blackboard_key_exists(target_key)) + return FALSE + return ..() + +/datum/bt_node/ai_behavior/spin_web/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/datum/action/cooldown/web_action = controller.blackboard[action_key] + if(!web_action) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return start_async() + +/datum/bt_node/ai_behavior/spin_web/perform_async(datum/ai_controller/controller) + var/datum/action/cooldown/web_action = controller.blackboard[action_key] + var/result = web_action.Trigger() + if(!async_still_valid()) + return + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/datum/bt_node/ai_behavior/spin_web/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/generic_behaviors/stop_dragging.dm b/code/datums/ai/generic_behaviors/stop_dragging.dm new file mode 100644 index 000000000000..24c28fea42d7 --- /dev/null +++ b/code/datums/ai/generic_behaviors/stop_dragging.dm @@ -0,0 +1,7 @@ +/// Stops the pawn from pulling whatever it is currently dragging. +/datum/bt_node/ai_behavior/stop_dragging + +/datum/bt_node/ai_behavior/stop_dragging/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + living_pawn.stop_pulling() + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/stuff_in_disposal.dm b/code/datums/ai/generic_behaviors/stuff_in_disposal.dm new file mode 100644 index 000000000000..07b85ad95d95 --- /dev/null +++ b/code/datums/ai/generic_behaviors/stuff_in_disposal.dm @@ -0,0 +1,37 @@ +/// Grabs a downed target mob and stuffs them into a nearby disposal unit. +/datum/bt_node/ai_behavior/stuff_in_disposal + time_between_perform = 2 SECONDS + /// Blackboard key holding the mob to stuff. + var/attack_target_key + /// Blackboard key holding the disposal unit. + var/disposal_target_key + +/datum/bt_node/ai_behavior/stuff_in_disposal/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/mob/living/target = controller.blackboard[attack_target_key] + var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key] + var/mob/living/living_pawn = controller.pawn + if(QDELETED(target) || QDELETED(disposal)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!living_pawn.Adjacent(disposal)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return start_async() + +/datum/bt_node/ai_behavior/stuff_in_disposal/perform_async(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[attack_target_key] + var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key] + var/mob/living/living_pawn = controller.pawn + var/stuffed = disposal.stuff_mob_in(target, living_pawn) + if(!async_still_valid()) + return + if(stuffed && !QDELETED(disposal)) + disposal.flush() + finish_async(AI_BEHAVIOR_SUCCEEDED) + +/datum/bt_node/ai_behavior/stuff_in_disposal/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(attack_target_key) + controller.clear_blackboard_key(disposal_target_key) diff --git a/code/datums/ai/generic_behaviors/succeed.dm b/code/datums/ai/generic_behaviors/succeed.dm new file mode 100644 index 000000000000..19d31d8d406b --- /dev/null +++ b/code/datums/ai/generic_behaviors/succeed.dm @@ -0,0 +1,4 @@ +/datum/bt_node/ai_behavior/succeed + +/datum/bt_node/ai_behavior/succeed/perform(seconds_per_tick, datum/ai_controller/controller) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/target_retaliate.dm b/code/datums/ai/generic_behaviors/target_retaliate.dm new file mode 100644 index 000000000000..dff7da1d5bab --- /dev/null +++ b/code/datums/ai/generic_behaviors/target_retaliate.dm @@ -0,0 +1,53 @@ + +///Pick a target from our retaliate list +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list + target_source = /datum/target_source/from_bb_list/retaliate_list + revalidation_mode = TARGET_REVALIDATE + time_between_perform = 2 SECONDS + vision_range = 9 + /// Blackboard key in which to store the target's hiding location. + var/hiding_location_key + /// If FALSE, temporarily ignores faction during the search. + var/check_faction = FALSE + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/perform(seconds_per_tick, datum/ai_controller/controller) + if(!check_faction) // This is lame, but comeon man the polar bears kept killing each other + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) + . = ..() + var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction) + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/filter_candidates(datum/ai_controller/controller, list/candidates, datum/targeting_strategy/strategy, atom/current_target) + var/mob/living/pawn = controller.pawn + var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[BB_TARGET_PRIORITY_STRATEGY]) + var/current_priority = priority_strategy ? priority_strategy.get_target_priority(controller, current_target) : 0 + var/list/filtered = list() + for(var/atom/candidate as anything in candidates) + if(!strategy.is_valid_target(pawn, candidate, vision_range, controller)) + continue + if(priority_strategy && priority_strategy.get_target_priority(controller, candidate) < current_priority) + continue + filtered += candidate + return filtered + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + var/atom/hiding = strategy.find_hidden_mobs(controller.pawn, target) + if(hiding) + controller.set_blackboard_key(hiding_location_key, hiding) + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/on_no_valid_candidates(datum/ai_controller/controller, atom/current_target) + if(current_target) + controller.clear_blackboard_key(target_key) + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[BB_TARGET_PRIORITY_STRATEGY]) + if(!priority_strategy) + return pick(filtered_targets) + return priority_strategy.select_target(controller, filtered_targets) + +/// Nearest-attacker variant +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/turf/our_position = get_turf(controller.pawn) + return get_closest_atom(/atom/, filtered_targets, our_position) diff --git a/code/datums/ai/generic_behaviors/use_in_hand.dm b/code/datums/ai/generic_behaviors/use_in_hand.dm new file mode 100644 index 000000000000..01fc28d5a4c3 --- /dev/null +++ b/code/datums/ai/generic_behaviors/use_in_hand.dm @@ -0,0 +1,10 @@ +/// Uses the pawn's currently active held item in-hand. +/datum/bt_node/ai_behavior/use_in_hand + +/datum/bt_node/ai_behavior/use_in_hand/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + var/obj/item/held = pawn.get_active_held_item() + if(!held) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(pawn, TYPE_PROC_REF(/mob, activate_hand)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/use_on_object.dm b/code/datums/ai/generic_behaviors/use_on_object.dm new file mode 100644 index 000000000000..5661221406c0 --- /dev/null +++ b/code/datums/ai/generic_behaviors/use_on_object.dm @@ -0,0 +1,12 @@ +/// Uses the pawn's held item (or unarmed) on a blackboard-keyed target. +/datum/bt_node/ai_behavior/use_on_object + /// Blackboard key holding the atom to use the held item on. + var/target_key + +/datum/bt_node/ai_behavior/use_on_object/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, FALSE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/virtual_pick_up_item.dm b/code/datums/ai/generic_behaviors/virtual_pick_up_item.dm new file mode 100644 index 000000000000..b22367d0b63d --- /dev/null +++ b/code/datums/ai/generic_behaviors/virtual_pick_up_item.dm @@ -0,0 +1,59 @@ +/// Moves the item at target_key onto the pawn and records it in storage_key. Does not use hands storage_key is the virtual carry slot. Clears target_key on finish. +/datum/bt_node/ai_behavior/pick_up_item_virtual + /// Blackboard key holding the item to pick up. + var/target_key + /// Blackboard key acting as the virtual carry slot. + var/storage_key + +/datum/bt_node/ai_behavior/pick_up_item_virtual/setup(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[target_key] + return isitem(target) && isturf(target.loc) && !target.anchored + +/datum/bt_node/ai_behavior/pick_up_item_virtual/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[target_key] + if(QDELETED(target) || !isturf(target.loc)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!controller.pawn.Adjacent(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + _pickup(controller, target, storage_key) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/pick_up_item_virtual/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +/datum/bt_node/ai_behavior/pick_up_item_virtual/proc/_pickup(datum/ai_controller/controller, obj/item/target, storage_key) + var/atom/pawn = controller.pawn + var/obj/item/held = controller.blackboard[storage_key] + if(held?.loc == pawn) + pawn.visible_message(span_notice("[pawn] drops [held].")) + held.forceMove(get_turf(pawn)) + controller.clear_blackboard_key(storage_key) + pawn.visible_message(span_notice("[pawn] picks up [target].")) + target.forceMove(pawn) + controller.set_blackboard_key(storage_key, target) + +/// Passes the item at storage_key to whoever is at delivery_key (must already be adjacent). +/datum/bt_node/ai_behavior/pass_item_virtual + /// Blackboard key holding the recipient to deliver to. + var/delivery_key + /// Blackboard key holding the virtually-carried item. + var/storage_key + +/datum/bt_node/ai_behavior/pass_item_virtual/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[delivery_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/pass_item_virtual/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[delivery_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/pawn = controller.pawn + var/obj/item/item = controller.blackboard[storage_key] + if(QDELETED(item) || item.loc != pawn) + pawn.visible_message(span_notice("[pawn] looks around as if [pawn.p_they()] [pawn.p_have()] lost something.")) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + pawn.visible_message(span_notice("[pawn] delivers [item] to [target].")) + item.forceMove(get_turf(target)) + controller.clear_blackboard_key(storage_key) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/wait.dm b/code/datums/ai/generic_behaviors/wait.dm new file mode 100644 index 000000000000..b609803c89f9 --- /dev/null +++ b/code/datums/ai/generic_behaviors/wait.dm @@ -0,0 +1,19 @@ +/// Does nothing for a given duration (deciseconds, so use the SECONDS define pls), then succeeds. duration = 0 waits forever. +/datum/bt_node/ai_behavior/wait + /// world.time when the wait ends. 0 when waiting forever + VAR_PRIVATE/end_time = 0 + /// How long to wait, in deciseconds. 0 waits forever. + var/duration = 0 + +/datum/bt_node/ai_behavior/wait/setup(datum/ai_controller/controller) + end_time = duration > 0 ? world.time + duration : 0 + return TRUE + +/datum/bt_node/ai_behavior/wait/perform(seconds_per_tick, datum/ai_controller/controller) + if(!end_time || world.time < end_time) + return AI_BEHAVIOR_INSTANT + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/wait/reset_tick_state() + end_time = 0 + ..() diff --git a/code/datums/ai/generic_decorators/ability_available.dm b/code/datums/ai/generic_decorators/ability_available.dm new file mode 100644 index 000000000000..99050ba44497 --- /dev/null +++ b/code/datums/ai/generic_decorators/ability_available.dm @@ -0,0 +1,8 @@ +/// Gates on whether a mob ability stored in a blackboard key is currently available. Needs to poll since we dont have nice signals to register too : ( +/datum/bt_node/decorator/ability_available + /// Blackboard key holding the ability datum + var/ability_key = BB_GENERIC_ACTION + +/datum/bt_node/decorator/ability_available/check_condition(datum/ai_controller/controller) + var/datum/action/action = controller.blackboard[ability_key] + return !QDELETED(action) && action.IsAvailable() diff --git a/code/datums/ai/generic_decorators/bb_key_at_least.dm b/code/datums/ai/generic_decorators/bb_key_at_least.dm new file mode 100644 index 000000000000..b7e9c5e3c60d --- /dev/null +++ b/code/datums/ai/generic_decorators/bb_key_at_least.dm @@ -0,0 +1,8 @@ +/// Passes if the given blackboard key holds a number >= minimum. Returns FAILURE if the key is unset or below minimum. +/datum/bt_node/decorator/bb_key_at_least + var/key + var/minimum + +/datum/bt_node/decorator/bb_key_at_least/check_condition(datum/ai_controller/controller) + var/value = controller.blackboard[key] + return !isnull(value) && value >= minimum diff --git a/code/datums/ai/generic_decorators/bb_key_equals.dm b/code/datums/ai/generic_decorators/bb_key_equals.dm new file mode 100644 index 000000000000..dfabc05ffa90 --- /dev/null +++ b/code/datums/ai/generic_decorators/bb_key_equals.dm @@ -0,0 +1,7 @@ +/// Passes if the given blackboard key equals the expected value. +/datum/bt_node/decorator/bb_key_equals + var/key + var/value + +/datum/bt_node/decorator/bb_key_equals/check_condition(datum/ai_controller/controller) + return bb_key_equals(controller, key, value) diff --git a/code/datums/ai/generic_decorators/bb_key_list_min_count.dm b/code/datums/ai/generic_decorators/bb_key_list_min_count.dm new file mode 100644 index 000000000000..27792bc4af6a --- /dev/null +++ b/code/datums/ai/generic_decorators/bb_key_list_min_count.dm @@ -0,0 +1,14 @@ +/// Passes if the blackboard key holds a list with at least min_count entries. +/datum/bt_node/decorator/bb_key_list_min_count + var/key + var/min_count = 1 + +/datum/bt_node/decorator/bb_key_list_min_count/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/bb_key_list_min_count/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/datum/bt_node/decorator/bb_key_list_min_count/check_condition(datum/ai_controller/controller) + return LAZYLEN(controller.blackboard[key]) >= min_count diff --git a/code/datums/ai/generic_decorators/bb_key_set.dm b/code/datums/ai/generic_decorators/bb_key_set.dm new file mode 100644 index 000000000000..68edc5971370 --- /dev/null +++ b/code/datums/ai/generic_decorators/bb_key_set.dm @@ -0,0 +1,13 @@ +/// Is the key set to a non-null value +/datum/bt_node/decorator/bb_key_set + var/key = null + +/datum/bt_node/decorator/bb_key_set/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/bb_key_set/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/datum/bt_node/decorator/bb_key_set/check_condition(datum/ai_controller/controller) + return controller.blackboard_key_exists(key) diff --git a/code/datums/ai/generic_decorators/bb_key_true.dm b/code/datums/ai/generic_decorators/bb_key_true.dm new file mode 100644 index 000000000000..680ac0afb008 --- /dev/null +++ b/code/datums/ai/generic_decorators/bb_key_true.dm @@ -0,0 +1,7 @@ +/// Passes if the given blackboard key equals a true-ey value +/datum/bt_node/decorator/bb_key_true + var/key + +/datum/bt_node/decorator/bb_key_true/check_condition(datum/ai_controller/controller) + var/result = controller.blackboard[key] + return !!result diff --git a/code/datums/ai/generic_decorators/buckle_target_dangerous.dm b/code/datums/ai/generic_decorators/buckle_target_dangerous.dm new file mode 100644 index 000000000000..115f415236a9 --- /dev/null +++ b/code/datums/ai/generic_decorators/buckle_target_dangerous.dm @@ -0,0 +1,10 @@ +/// Gates on the buckle target having TRAIT_DANGEROUS_BUCKLE; fails if pacifist = TRUE. +/datum/bt_node/decorator/buckle_target_dangerous + var/pacifist = FALSE + var/target_key = BB_BASIC_MOB_ESCAPE_TARGET + +/datum/bt_node/decorator/buckle_target_dangerous/check_condition(datum/ai_controller/controller) + if(pacifist) + return FALSE + var/atom/target = controller.blackboard[target_key] + return !isnull(target) && HAS_TRAIT(target, TRAIT_DANGEROUS_BUCKLE) diff --git a/code/datums/ai/generic_decorators/can_see_target.dm b/code/datums/ai/generic_decorators/can_see_target.dm new file mode 100644 index 000000000000..9a2ea5b7fe24 --- /dev/null +++ b/code/datums/ai/generic_decorators/can_see_target.dm @@ -0,0 +1,17 @@ +/** + * Gates child on a blackboard key holding a visible, non-deleted atom. + * If the target is null, deleted, or not visible to the pawn, clears the key and returns BT_FAILURE. + */ +/datum/bt_node/decorator/can_see_target + /// The blackboard key whose value is the atom to validate. + var/key + /// Visibility range passed to can_see(). Default matches typical bot search radius. + var/range = 7 + +/datum/bt_node/decorator/can_see_target/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target) || !can_see(controller.pawn, target, range)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_DECISIONMAKING, "[controller.pawn] can_see_target([key]): target lost") + controller.clear_blackboard_key(key) + return FALSE + return TRUE diff --git a/code/datums/ai/generic_decorators/check_cooldown.dm b/code/datums/ai/generic_decorators/check_cooldown.dm new file mode 100644 index 000000000000..721179dd9e11 --- /dev/null +++ b/code/datums/ai/generic_decorators/check_cooldown.dm @@ -0,0 +1,28 @@ +/// Check if the specified blackboard key is off cooldown. +/datum/bt_node/decorator/key_off_cooldown + var/cooldown_key + +/datum/bt_node/decorator/key_off_cooldown/check_condition(datum/ai_controller/controller) + var/cooldown_time = controller.blackboard[cooldown_key] + return isnull(cooldown_time) || cooldown_time <= world.time + +/** + * Checks if a blackboard cooldown key is off cooldown before ticking the child, + * then sets it when the child finishes replacing the key_off_cooldown + * + * cooldown_duration is in deciseconds (e.g. 30 SECONDS). + * lock_on_succeed = TRUE (default): lock after child SUCCESS only. + * lock_on_succeed = FALSE: lock after any completion (SUCCESS or FAILURE). + */ +/datum/bt_node/decorator/cooldown + var/cooldown_key + var/cooldown_duration + var/lock_on_succeed = TRUE + +/datum/bt_node/decorator/cooldown/check_condition(datum/ai_controller/controller) + var/cooldown_time = controller.blackboard[cooldown_key] + return isnull(cooldown_time) || cooldown_time <= world.time + +/datum/bt_node/decorator/cooldown/on_child_complete(datum/ai_controller/controller, result) + if(!lock_on_succeed || result == BT_SUCCESS) + controller.set_blackboard_key(cooldown_key, world.time + cooldown_duration) diff --git a/code/datums/ai/generic_decorators/check_rider_stat.dm b/code/datums/ai/generic_decorators/check_rider_stat.dm new file mode 100644 index 000000000000..7f082d11ed4d --- /dev/null +++ b/code/datums/ai/generic_decorators/check_rider_stat.dm @@ -0,0 +1,11 @@ +///Checks the health status of our rider, if any. returns false if we dont have a rider to begin with +/datum/bt_node/decorator/check_rider_stat + ///stat we're interested in + var/target_stat = UNCONSCIOUS + +/datum/bt_node/decorator/check_rider_stat/check_condition(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!length(living_pawn.buckled_mobs)) + return FALSE + var/mob/living/buckled_to = living_pawn.buckled_mobs[1] + return buckled_to.stat == target_stat diff --git a/code/datums/ai/generic_decorators/container_attackable.dm b/code/datums/ai/generic_decorators/container_attackable.dm new file mode 100644 index 000000000000..8e4303e74e44 --- /dev/null +++ b/code/datums/ai/generic_decorators/container_attackable.dm @@ -0,0 +1,15 @@ +/// Gates on the container being worth attacking (pawn.obj_damage > damage_deflection); fails if pacifist = TRUE. +/datum/bt_node/decorator/container_attackable + var/pacifist = FALSE + var/target_key = BB_BASIC_MOB_ESCAPE_TARGET + +/datum/bt_node/decorator/container_attackable/check_condition(datum/ai_controller/controller) + if(pacifist) + return FALSE + if(!isbasicmob(controller.pawn)) + return FALSE + var/atom/target = controller.blackboard[target_key] + if(isnull(target)) + return FALSE + var/mob/living/basic/basic_pawn = controller.pawn + return basic_pawn.obj_damage > target.damage_deflection diff --git a/code/datums/ai/generic_decorators/is_at_distance.dm b/code/datums/ai/generic_decorators/is_at_distance.dm new file mode 100644 index 000000000000..d8852e1b8954 --- /dev/null +++ b/code/datums/ai/generic_decorators/is_at_distance.dm @@ -0,0 +1,21 @@ +/// Decorator that requires the controller's pawn to be within range of a blackboard target. +/datum/bt_node/decorator/is_at_distance + /// Blackboard key holding the atom to approach. Must be set on the subtype or via configure(). + var/target_key = null + /// Minimum distance (inclusive) from target. 0 means no lower bound. + var/min_distance = -1 + /// Maximum distance (inclusive) from target + var/maximum_distance = -1 + /// If TRUE, also verifies target.IsReachableBy(pawn) before passing to child. + var/require_reach = FALSE + +/datum/bt_node/decorator/is_at_distance/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + + var/atom/movable/pawn = controller.pawn + var/dist = get_dist(pawn, target) + var/reachable = !require_reach || target.IsReachableBy(pawn) + + return (maximum_distance == -1 || dist <= maximum_distance) && dist >= min_distance && reachable diff --git a/code/datums/ai/generic_decorators/is_dragging.dm b/code/datums/ai/generic_decorators/is_dragging.dm new file mode 100644 index 000000000000..6abe04896af8 --- /dev/null +++ b/code/datums/ai/generic_decorators/is_dragging.dm @@ -0,0 +1,6 @@ +/// Gates child on pawn currently pulling something. Use invert = TRUE for the opposite. Checked each tick. +/datum/bt_node/decorator/is_dragging + +/datum/bt_node/decorator/is_dragging/check_condition(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + return !!living_pawn.pulling diff --git a/code/datums/ai/generic_decorators/is_grabbing_target.dm b/code/datums/ai/generic_decorators/is_grabbing_target.dm new file mode 100644 index 000000000000..9e9dd8fae31a --- /dev/null +++ b/code/datums/ai/generic_decorators/is_grabbing_target.dm @@ -0,0 +1,20 @@ +///Are we grabbing the specified target key? +/datum/bt_node/decorator/is_grabbing_target + /// The blackboard key whose value is the grabbed atom. + var/key + +/datum/bt_node/decorator/is_grabbing_target/check_condition(datum/ai_controller/controller) + var/atom/movable/target = controller.blackboard[key] + var/mob/living/our_mob = controller.pawn + if(QDELETED(target) || our_mob.pulling != target) + controller.clear_blackboard_key(key) + return FALSE + return TRUE + +/datum/bt_node/decorator/is_grabbing_target/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_LIVING_START_PULL, COMSIG_ATOM_NO_LONGER_PULLING, COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/is_grabbing_target/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_LIVING_START_PULL, COMSIG_ATOM_NO_LONGER_PULLING, COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + diff --git a/code/datums/ai/generic_decorators/is_holding_target.dm b/code/datums/ai/generic_decorators/is_holding_target.dm new file mode 100644 index 000000000000..dd433a7a0856 --- /dev/null +++ b/code/datums/ai/generic_decorators/is_holding_target.dm @@ -0,0 +1,24 @@ +/** + * Gates child on the pawn currently holding the item at the given blackboard key in its hands. + * Clears the key and returns BT_FAILURE if the item has been deleted. + * Returns BT_FAILURE without clearing the key if the item exists but is not held the item + * may be in a bag or on the floor and other branches may still need to locate it. + */ +/datum/bt_node/decorator/is_holding_target + /// The blackboard key whose value is the item to check. + var/key + +/datum/bt_node/decorator/is_holding_target/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM, COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/is_holding_target/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM, COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/datum/bt_node/decorator/is_holding_target/check_condition(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[key] + var/mob/mob_pawn = controller.pawn + if(QDELETED(target)) + controller.clear_blackboard_key(key) + return FALSE + return mob_pawn.is_holding(target) diff --git a/code/datums/ai/generic_decorators/is_in_vent.dm b/code/datums/ai/generic_decorators/is_in_vent.dm new file mode 100644 index 000000000000..de4e08f500c0 --- /dev/null +++ b/code/datums/ai/generic_decorators/is_in_vent.dm @@ -0,0 +1,13 @@ +/// Gates on the pawn currently being inside a vent (has TRAIT_MOVE_VENTCRAWLING). +/datum/bt_node/decorator/is_in_vent + observer_abort = BT_ABORT_LOWER_PRIORITY + +/datum/bt_node/decorator/is_in_vent/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(SIGNAL_ADDTRAIT(TRAIT_MOVE_VENTCRAWLING), SIGNAL_REMOVETRAIT(TRAIT_MOVE_VENTCRAWLING)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/is_in_vent/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(SIGNAL_ADDTRAIT(TRAIT_MOVE_VENTCRAWLING), SIGNAL_REMOVETRAIT(TRAIT_MOVE_VENTCRAWLING))) + +/datum/bt_node/decorator/is_in_vent/check_condition(datum/ai_controller/controller) + return HAS_TRAIT(controller.pawn, TRAIT_MOVE_VENTCRAWLING) diff --git a/code/datums/ai/generic_decorators/is_target_stunned.dm b/code/datums/ai/generic_decorators/is_target_stunned.dm new file mode 100644 index 000000000000..13f11c799d8e --- /dev/null +++ b/code/datums/ai/generic_decorators/is_target_stunned.dm @@ -0,0 +1,11 @@ +/// Gates child on the mob held in a blackboard key being paralyzed (stunned and on the ground). +/// Use "invert": true to gate on the target NOT being stunned. Used by arrest bots like ED209 to close in once the target is incapacitated. +/datum/bt_node/decorator/is_target_stunned + /// Blackboard key holding the mob to check. + var/key = BB_CURRENT_TARGET + +/datum/bt_node/decorator/is_target_stunned/check_condition(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[key] + if(QDELETED(target) || !isliving(target)) + return FALSE + return !!target.IsParalyzed() diff --git a/code/datums/ai/generic_decorators/item_inside_pawn.dm b/code/datums/ai/generic_decorators/item_inside_pawn.dm new file mode 100644 index 000000000000..d4c3d31013dd --- /dev/null +++ b/code/datums/ai/generic_decorators/item_inside_pawn.dm @@ -0,0 +1,46 @@ +/** + * Gates child on the item at the given blackboard key being located anywhere inside the pawn + * (recursive contents check via locate()). Clears the key and returns FALSE if the item is deleted. + * Does NOT clear the key if the item simply isn't inside the pawn other branches may still use it. + */ +/datum/bt_node/decorator/item_inside_pawn + /// Blackboard key holding the item to check. + var/key = null + /// The item currently being observed for location changes. + var/obj/item/observed_item = null + +/datum/bt_node/decorator/item_inside_pawn/register_observe_signals(atom/pawn) + var/obj/item/target = owning_controller?.blackboard[key] + if(target) + observed_item = target + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_item_key_changed)) + return TRUE + +/datum/bt_node/decorator/item_inside_pawn/unregister_observe_signals(atom/pawn) + if(observed_item) + UnregisterSignal(observed_item, COMSIG_MOVABLE_MOVED) + observed_item = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/// Fires when the blackboard key changes. Rebinds the move observer to the new item and re-evaluates. +/datum/bt_node/decorator/item_inside_pawn/proc/on_item_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/obj/item/target = owning_controller?.blackboard[key] + if(target == observed_item) + return + if(observed_item) + UnregisterSignal(observed_item, COMSIG_MOVABLE_MOVED) + observed_item = null + if(target) + observed_item = target + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/item_inside_pawn/check_condition(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[key] + if(QDELETED(target)) + controller.clear_blackboard_key(key) + return FALSE + return !isnull(locate(target) in controller.pawn) diff --git a/code/datums/ai/generic_decorators/key_in_typelist.dm b/code/datums/ai/generic_decorators/key_in_typelist.dm new file mode 100644 index 000000000000..0bb11ecc9d2a --- /dev/null +++ b/code/datums/ai/generic_decorators/key_in_typelist.dm @@ -0,0 +1,30 @@ +/// Gates child on the atom in `key` being an instance of a type in the typelist held at `typelist_key`. Use "invert": true for the opposite. +/datum/bt_node/decorator/key_in_typelist + /// Blackboard key holding the atom to type-check. + var/key = BB_CURRENT_TARGET + /// Blackboard key holding the list of typepaths to check against. + var/typelist_key + +/datum/bt_node/decorator/key_in_typelist/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key), + COMSIG_AI_BLACKBOARD_KEY_SET(typelist_key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(typelist_key), + ), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/key_in_typelist/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key), + COMSIG_AI_BLACKBOARD_KEY_SET(typelist_key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(typelist_key), + )) + +/datum/bt_node/decorator/key_in_typelist/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + var/list/types = controller.blackboard[typelist_key] + if(QDELETED(target) || isnull(types)) + return FALSE + return is_type_in_list(target, types) diff --git a/code/datums/ai/generic_decorators/keys_different_gender.dm b/code/datums/ai/generic_decorators/keys_different_gender.dm new file mode 100644 index 000000000000..fa5335cb2051 --- /dev/null +++ b/code/datums/ai/generic_decorators/keys_different_gender.dm @@ -0,0 +1,30 @@ +///Checks for gender difference, I know, not very 2026 of me but until we add m-preg this'll have to do. +/datum/bt_node/decorator/keys_different_gender + /// Blackboard key holding the first mob. + var/key_a = BB_CURRENT_TARGET + /// Blackboard key holding the second mob. + var/key_b + +/datum/bt_node/decorator/keys_different_gender/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(key_a), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key_a), + COMSIG_AI_BLACKBOARD_KEY_SET(key_b), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key_b), + ), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/keys_different_gender/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(key_a), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key_a), + COMSIG_AI_BLACKBOARD_KEY_SET(key_b), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key_b), + )) + +/datum/bt_node/decorator/keys_different_gender/check_condition(datum/ai_controller/controller) + var/mob/mob_a = controller.blackboard[key_a] + var/mob/mob_b = controller.blackboard[key_b] + if(isnull(mob_a) || isnull(mob_b)) + return FALSE + return mob_a.gender != mob_b.gender diff --git a/code/datums/ai/generic_decorators/mob_stat_at_least.dm b/code/datums/ai/generic_decorators/mob_stat_at_least.dm new file mode 100644 index 000000000000..ae8f8d8e413e --- /dev/null +++ b/code/datums/ai/generic_decorators/mob_stat_at_least.dm @@ -0,0 +1,45 @@ +/// Passes when the mob held in a blackboard key has a stat value at that is at least X. Higher is more dead. +/datum/bt_node/decorator/mob_stat_at_least + /// Blackboard key holding the mob to check. + var/key = null + /// Minimum stat value (inclusive) for the condition to pass. Default: CONSCIOUS. + var/min_stat = CONSCIOUS + /// The mob currently being observed. Tracked so we can unregister when the key changes or teardown runs. + var/mob/observed_mob = null + +/datum/bt_node/decorator/mob_stat_at_least/register_observe_signals(atom/pawn) + var/mob/target = owning_controller?.blackboard[key] + if(target) + observed_mob = target + RegisterSignal(target, COMSIG_MOB_STATCHANGE, PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_mob_key_changed)) + return TRUE + +/datum/bt_node/decorator/mob_stat_at_least/unregister_observe_signals(atom/pawn) + if(observed_mob) + UnregisterSignal(observed_mob, COMSIG_MOB_STATCHANGE) + observed_mob = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + + +/// Fires when the blackboard key changes. Rebinds the stat observer to the new mob and re-evaluates. +/datum/bt_node/decorator/mob_stat_at_least/proc/on_mob_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/mob/target = owning_controller?.blackboard[key] + if(target == observed_mob) + return + if(observed_mob) + UnregisterSignal(observed_mob, COMSIG_MOB_STATCHANGE) + observed_mob = null + if(target) + observed_mob = target + RegisterSignal(target, COMSIG_MOB_STATCHANGE, PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + + +/datum/bt_node/decorator/mob_stat_at_least/check_condition(datum/ai_controller/controller) + var/mob/target = controller.blackboard[key] + if(!ismob(target)) + return FALSE + return target.stat >= min_stat diff --git a/code/datums/ai/generic_decorators/no_humans_watching.dm b/code/datums/ai/generic_decorators/no_humans_watching.dm new file mode 100644 index 000000000000..6ffadadbc666 --- /dev/null +++ b/code/datums/ai/generic_decorators/no_humans_watching.dm @@ -0,0 +1,11 @@ +/// Passes only when no living, conscious human can see or hear the pawn within range. +/// Used by cowardly mobs that only act when nobody is looking. +/datum/bt_node/decorator/no_humans_watching + /// How far to look for witnesses. + var/range = 7 + +/datum/bt_node/decorator/no_humans_watching/check_condition(datum/ai_controller/controller) + for(var/mob/living/carbon/human/watcher in hearers(range, controller.pawn)) + if(watcher.stat != DEAD) + return FALSE + return TRUE diff --git a/code/datums/ai/generic_decorators/pawn_buckled_to_obj.dm b/code/datums/ai/generic_decorators/pawn_buckled_to_obj.dm new file mode 100644 index 000000000000..045145882498 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_buckled_to_obj.dm @@ -0,0 +1,18 @@ +/// Gates on the pawn being buckled to an obj; writes pawn.buckled to the escape target key as a side effect. +/datum/bt_node/decorator/pawn_buckled_to_obj + var/target_key = BB_BASIC_MOB_ESCAPE_TARGET + observer_abort = BT_ABORT_LOWER_PRIORITY + +/datum/bt_node/decorator/pawn_buckled_to_obj/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_MOB_BUCKLED, COMSIG_MOB_UNBUCKLED), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_buckled_to_obj/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_MOB_BUCKLED, COMSIG_MOB_UNBUCKLED)) + +/datum/bt_node/decorator/pawn_buckled_to_obj/check_condition(datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + if(!isobj(pawn.buckled)) + return FALSE + controller.blackboard[target_key] = pawn.buckled + return TRUE diff --git a/code/datums/ai/generic_decorators/pawn_contained_in_obj.dm b/code/datums/ai/generic_decorators/pawn_contained_in_obj.dm new file mode 100644 index 000000000000..a49659df49da --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_contained_in_obj.dm @@ -0,0 +1,18 @@ +/// Gates on the pawn being inside an obj (not a turf, mob, or mob_holder); writes pawn.loc to the escape target key. +/datum/bt_node/decorator/pawn_contained_in_obj + var/target_key = BB_BASIC_MOB_ESCAPE_TARGET + observer_abort = BT_ABORT_LOWER_PRIORITY + +/datum/bt_node/decorator/pawn_contained_in_obj/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_MOVABLE_MOVED), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_contained_in_obj/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_MOVABLE_MOVED)) + +/datum/bt_node/decorator/pawn_contained_in_obj/check_condition(datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + if(isturf(pawn.loc) || ismob(pawn.loc) || istype(pawn.loc, /obj/item/mob_holder) || HAS_TRAIT(controller.pawn, TRAIT_MOVE_VENTCRAWLING)) + return FALSE + controller.blackboard[target_key] = pawn.loc + return TRUE diff --git a/code/datums/ai/generic_decorators/pawn_farther_than_from_key.dm b/code/datums/ai/generic_decorators/pawn_farther_than_from_key.dm new file mode 100644 index 000000000000..997f6f0abcd4 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_farther_than_from_key.dm @@ -0,0 +1,13 @@ +/// Passes if the pawn is farther than the distance stored in distance_key from the atom stored in anchor_key. +/datum/bt_node/decorator/pawn_farther_than_from_key + /// Blackboard key holding the anchor atom. + var/anchor_key + /// Blackboard key whose integer value is the minimum distance threshold (exclusive). + var/distance_key + +/datum/bt_node/decorator/pawn_farther_than_from_key/check_condition(datum/ai_controller/controller) + var/atom/anchor = controller.blackboard[anchor_key] + if(QDELETED(anchor)) + return FALSE + var/min_dist = controller.blackboard[distance_key] + return get_dist(controller.pawn, anchor) > min_dist diff --git a/code/datums/ai/generic_decorators/pawn_grabbed_by_enemy.dm b/code/datums/ai/generic_decorators/pawn_grabbed_by_enemy.dm new file mode 100644 index 000000000000..64e7a0df473f --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_grabbed_by_enemy.dm @@ -0,0 +1,22 @@ +/// Gates on the pawn being grabbed above GRAB_PASSIVE by a mob the targeting strategy considers an enemy. +/datum/bt_node/decorator/pawn_grabbed_by_enemy + var/targeting_strategy_key = BB_TARGETING_STRATEGY + child_typepath = /datum/bt_node/ai_behavior/resist + +/datum/bt_node/decorator/pawn_grabbed_by_enemy/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_LIVING_GET_PULLED, COMSIG_ATOM_NO_LONGER_PULLED, COMSIG_MOVABLE_SET_GRAB_STATE), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_grabbed_by_enemy/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_LIVING_GET_PULLED, COMSIG_ATOM_NO_LONGER_PULLED, COMSIG_MOVABLE_SET_GRAB_STATE)) + +/datum/bt_node/decorator/pawn_grabbed_by_enemy/check_condition(datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + var/mob/puller = pawn.pulledby + if(isnull(puller) || puller.grab_state <= GRAB_PASSIVE) + return FALSE + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + if(!strategy?.is_valid_target(pawn, puller)) + return FALSE + var/list/friends = controller.blackboard[BB_FRIENDS_LIST] || list() + return !(puller in friends) diff --git a/code/datums/ai/generic_decorators/pawn_has_gravity.dm b/code/datums/ai/generic_decorators/pawn_has_gravity.dm new file mode 100644 index 000000000000..78de74825b48 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_has_gravity.dm @@ -0,0 +1,13 @@ +/// Gates child on the pawn having gravity. Use invert = TRUE to gate on weightlessness instead. +/datum/bt_node/decorator/pawn_has_gravity + +/datum/bt_node/decorator/pawn_has_gravity/register_observe_signals(atom/pawn) + RegisterSignal(pawn, COMSIG_LIVING_GRAVITY_CHANGED, PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_has_gravity/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, COMSIG_LIVING_GRAVITY_CHANGED) + +/datum/bt_node/decorator/pawn_has_gravity/check_condition(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + return istype(living_pawn) && living_pawn.has_gravity() diff --git a/code/datums/ai/generic_decorators/pawn_has_trait_from.dm b/code/datums/ai/generic_decorators/pawn_has_trait_from.dm new file mode 100644 index 000000000000..b422f828717f --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_has_trait_from.dm @@ -0,0 +1,16 @@ +/// Gates child on the pawn having a given trait from a specific source. Use "invert": true to gate on the trait being absent. +/datum/bt_node/decorator/pawn_has_trait_from + /// The trait to check for. + var/trait = null + /// The source the trait must originate from. + var/source = null + +/datum/bt_node/decorator/pawn_has_trait_from/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_has_trait_from/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait))) + +/datum/bt_node/decorator/pawn_has_trait_from/check_condition(datum/ai_controller/controller) + return HAS_TRAIT_FROM(controller.pawn, trait, source) diff --git a/code/datums/ai/generic_decorators/pawn_health_below.dm b/code/datums/ai/generic_decorators/pawn_health_below.dm new file mode 100644 index 000000000000..9a7390251bc5 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_health_below.dm @@ -0,0 +1,20 @@ +/// Passes when the pawn's current health is below the configured threshold. +/datum/bt_node/decorator/pawn_health_below + /// Health value that the pawn must be below for this decorator to pass + var/health_threshold = 0 + /// blackboard value holding our threshold, if this is null, health_threshold will be used instead + var/health_blackboard_key + +/datum/bt_node/decorator/pawn_health_below/register_observe_signals(atom/pawn) + RegisterSignal(pawn, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_health_below/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, COMSIG_LIVING_HEALTH_UPDATE) + +/datum/bt_node/decorator/pawn_health_below/check_condition(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!isliving(living_pawn)) + return FALSE + var/final_threshold = controller.blackboard[health_blackboard_key] || health_threshold + return living_pawn.health < final_threshold diff --git a/code/datums/ai/generic_decorators/pawn_inside_mob.dm b/code/datums/ai/generic_decorators/pawn_inside_mob.dm new file mode 100644 index 000000000000..cef895c380a2 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_inside_mob.dm @@ -0,0 +1,13 @@ +/// Gates on the pawn being located inside another mob (e.g. absorbed or shapeshifted). +/datum/bt_node/decorator/pawn_inside_mob + observer_abort = BT_ABORT_LOWER_PRIORITY + +/datum/bt_node/decorator/pawn_inside_mob/register_observe_signals(atom/pawn) + RegisterSignal(pawn, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_inside_mob/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, COMSIG_MOVABLE_MOVED) + +/datum/bt_node/decorator/pawn_inside_mob/check_condition(datum/ai_controller/controller) + return ismob(controller.pawn.loc) diff --git a/code/datums/ai/generic_decorators/pawn_is_restrained.dm b/code/datums/ai/generic_decorators/pawn_is_restrained.dm new file mode 100644 index 000000000000..2a9b7c96f764 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_is_restrained.dm @@ -0,0 +1,14 @@ +/// Gates on the pawn having TRAIT_RESTRAINED; reacts to trait add/remove signals. +/datum/bt_node/decorator/pawn_is_restrained + observer_abort = BT_ABORT_LOWER_PRIORITY + child_typepath = /datum/bt_node/ai_behavior/resist + +/datum/bt_node/decorator/pawn_is_restrained/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(SIGNAL_ADDTRAIT(TRAIT_RESTRAINED), SIGNAL_REMOVETRAIT(TRAIT_RESTRAINED)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_is_restrained/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(SIGNAL_ADDTRAIT(TRAIT_RESTRAINED), SIGNAL_REMOVETRAIT(TRAIT_RESTRAINED))) + +/datum/bt_node/decorator/pawn_is_restrained/check_condition(datum/ai_controller/controller) + return HAS_TRAIT(controller.pawn, TRAIT_RESTRAINED) diff --git a/code/datums/ai/generic_decorators/pawn_loc_is_type.dm b/code/datums/ai/generic_decorators/pawn_loc_is_type.dm new file mode 100644 index 000000000000..339078ad49c5 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_loc_is_type.dm @@ -0,0 +1,15 @@ +/// Passes when the pawn's loc is of the given type (e.g. the pawn is inside a specific structure). +/datum/bt_node/decorator/pawn_loc_is_type + observer_abort = BT_ABORT_LOWER_PRIORITY + /// Typepath the pawn's loc must match. + var/loc_type + +/datum/bt_node/decorator/pawn_loc_is_type/register_observe_signals(atom/pawn) + RegisterSignal(pawn, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_loc_is_type/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, COMSIG_MOVABLE_MOVED) + +/datum/bt_node/decorator/pawn_loc_is_type/check_condition(datum/ai_controller/controller) + return istype(controller.pawn.loc, loc_type) diff --git a/code/datums/ai/generic_decorators/pawn_nutrition_below.dm b/code/datums/ai/generic_decorators/pawn_nutrition_below.dm new file mode 100644 index 000000000000..ea559a106a74 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_nutrition_below.dm @@ -0,0 +1,10 @@ +/// Passes if the pawn's nutrition is below the given threshold. +/datum/bt_node/decorator/pawn_nutrition_below + /// Nutrition threshold to check against. + var/nutrition_threshold = NUTRITION_LEVEL_HUNGRY + +/datum/bt_node/decorator/pawn_nutrition_below/check_condition(datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + if(!isliving(pawn)) + return FALSE + return pawn.nutrition < nutrition_threshold diff --git a/code/datums/ai/generic_decorators/pawn_same_z_as_key.dm b/code/datums/ai/generic_decorators/pawn_same_z_as_key.dm new file mode 100644 index 000000000000..eadaef069688 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_same_z_as_key.dm @@ -0,0 +1,9 @@ +/// Passes if the pawn is on the same z-level as the atom stored in the given blackboard key. +/datum/bt_node/decorator/pawn_same_z_as_key + var/key + +/datum/bt_node/decorator/pawn_same_z_as_key/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target)) + return FALSE + return controller.pawn.z == target.z diff --git a/code/datums/ai/generic_decorators/pawn_turf_has_trait.dm b/code/datums/ai/generic_decorators/pawn_turf_has_trait.dm new file mode 100644 index 000000000000..e5febf59704c --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_turf_has_trait.dm @@ -0,0 +1,7 @@ +/// Gates child on the pawn's current turf having a given trait. Use invert = TRUE to gate on the trait being absent. +/datum/bt_node/decorator/pawn_turf_has_trait + var/trait = null + +/datum/bt_node/decorator/pawn_turf_has_trait/check_condition(datum/ai_controller/controller) + var/turf/my_turf_in_the_middle_of_my_turf = get_turf(controller.pawn) + return HAS_TRAIT(my_turf_in_the_middle_of_my_turf, trait) diff --git a/code/datums/ai/generic_decorators/random_chance.dm b/code/datums/ai/generic_decorators/random_chance.dm new file mode 100644 index 000000000000..f33392904a3d --- /dev/null +++ b/code/datums/ai/generic_decorators/random_chance.dm @@ -0,0 +1,7 @@ +///Decorator with a probability to pass, useful for things that sometimes happen. Slay queen +/datum/bt_node/decorator/random_chance + /// 0.0–1.0 float; converted to percentage for prob(). Configure via BT_DECORATOR. + var/chance = 0.5 + +/datum/bt_node/decorator/random_chance/check_condition(datum/ai_controller/controller) + return prob(chance * 100) diff --git a/code/datums/ai/generic_decorators/random_chance_from_key.dm b/code/datums/ai/generic_decorators/random_chance_from_key.dm new file mode 100644 index 000000000000..9c7d778cd60b --- /dev/null +++ b/code/datums/ai/generic_decorators/random_chance_from_key.dm @@ -0,0 +1,10 @@ +/// Passes with a probability taken from a blackboard key (0-100 integer). +/datum/bt_node/decorator/random_chance_from_key + /// Blackboard key holding the integer probability (0-100) + var/chance_key = null + +/datum/bt_node/decorator/random_chance_from_key/check_condition(datum/ai_controller/controller) + var/chance = controller.blackboard[chance_key] + if(!chance) + return FALSE + return prob(chance) diff --git a/code/datums/ai/generic_decorators/target_has_reagent.dm b/code/datums/ai/generic_decorators/target_has_reagent.dm new file mode 100644 index 000000000000..85458169bf53 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_has_reagent.dm @@ -0,0 +1,44 @@ +/// Gates child on the atom held in a blackboard key having a specific reagent in its reagent list. Use "invert": true to gate on the reagent being absent. +/datum/bt_node/decorator/target_has_reagent + /// Blackboard key holding the atom to check. + var/key = null + /// Typepath of the reagent to look for. + var/reagent_type = null + /// Tracked so we can rebind signals when the key or its reagents change. + var/datum/reagents/observed_holder = null + +/datum/bt_node/decorator/target_has_reagent/register_observe_signals(atom/pawn) + var/atom/target = owning_controller?.blackboard[key] + if(target?.reagents) + observed_holder = target.reagents + RegisterSignal(observed_holder, COMSIG_REAGENTS_HOLDER_UPDATED, PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_target_key_changed)) + return TRUE + +/datum/bt_node/decorator/target_has_reagent/unregister_observe_signals(atom/pawn) + if(observed_holder) + UnregisterSignal(observed_holder, COMSIG_REAGENTS_HOLDER_UPDATED) + observed_holder = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/// Fires when the blackboard key changes. Rebinds the reagent holder observer to the new target and re-evaluates. +/datum/bt_node/decorator/target_has_reagent/proc/on_target_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/atom/target = owning_controller?.blackboard[key] + var/datum/reagents/new_holder = target?.reagents + if(new_holder == observed_holder) + return + if(observed_holder) + UnregisterSignal(observed_holder, COMSIG_REAGENTS_HOLDER_UPDATED) + observed_holder = null + if(new_holder) + observed_holder = new_holder + RegisterSignal(observed_holder, COMSIG_REAGENTS_HOLDER_UPDATED, PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/target_has_reagent/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target) || !target.reagents) + return FALSE + return !!target.reagents.has_reagent(reagent_type) diff --git a/code/datums/ai/generic_decorators/target_has_trait.dm b/code/datums/ai/generic_decorators/target_has_trait.dm new file mode 100644 index 000000000000..8dbd03100041 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_has_trait.dm @@ -0,0 +1,43 @@ +/// Gates child on the atom held in a blackboard key having a given trait. Use "invert": true to gate on the trait being absent. +/datum/bt_node/decorator/target_has_trait + /// Blackboard key holding the atom to check. + var/key = null + /// The trait the target must have for the child to run. + var/trait = null + /// Tracked so we can rebind signals when the key changes. + var/atom/observed_target = null + +/datum/bt_node/decorator/target_has_trait/register_observe_signals(atom/pawn) + var/atom/target = owning_controller?.blackboard[key] + if(target) + observed_target = target + RegisterSignals(target, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait)), PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_target_key_changed)) + return TRUE + +/datum/bt_node/decorator/target_has_trait/unregister_observe_signals(atom/pawn) + if(observed_target) + UnregisterSignal(observed_target, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait))) + observed_target = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/// Fires when the blackboard key changes. Rebinds trait signals to the new target and re-evaluates. +/datum/bt_node/decorator/target_has_trait/proc/on_target_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/atom/target = owning_controller?.blackboard[key] + if(target == observed_target) + return + if(observed_target) + UnregisterSignal(observed_target, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait))) + observed_target = null + if(target) + observed_target = target + RegisterSignals(target, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait)), PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/target_has_trait/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target)) + return FALSE + return HAS_TRAIT(target, trait) diff --git a/code/datums/ai/generic_decorators/target_health_below_fraction.dm b/code/datums/ai/generic_decorators/target_health_below_fraction.dm new file mode 100644 index 000000000000..0f2ebb0d5949 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_health_below_fraction.dm @@ -0,0 +1,42 @@ +/// Passes when the living mob held in a blackboard key has health below a fraction of its max health. +/datum/bt_node/decorator/target_health_below_fraction + /// Blackboard key holding the mob to check. + var/key = null + /// Health fraction threshold (0.0–1.0). Passes when health < maxHealth * fraction. + var/fraction = 0.75 + /// The mob currently being observed. + VAR_FINAL/mob/living/observed_mob = null + +/datum/bt_node/decorator/target_health_below_fraction/register_observe_signals(atom/pawn) + var/mob/living/target = owning_controller?.blackboard[key] + if(isliving(target)) + observed_mob = target + RegisterSignal(target, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_mob_key_changed)) + return TRUE + +/datum/bt_node/decorator/target_health_below_fraction/unregister_observe_signals(atom/pawn) + if(observed_mob) + UnregisterSignal(observed_mob, COMSIG_LIVING_HEALTH_UPDATE) + observed_mob = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/datum/bt_node/decorator/target_health_below_fraction/proc/on_mob_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/mob/living/target = owning_controller?.blackboard[key] + if(target == observed_mob) + return + if(observed_mob) + UnregisterSignal(observed_mob, COMSIG_LIVING_HEALTH_UPDATE) + observed_mob = null + if(isliving(target)) + observed_mob = target + RegisterSignal(target, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/target_health_below_fraction/check_condition(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[key] + if(!isliving(target)) + return FALSE + return target.health < target.maxHealth * fraction diff --git a/code/datums/ai/generic_decorators/target_holding_lit_item.dm b/code/datums/ai/generic_decorators/target_holding_lit_item.dm new file mode 100644 index 000000000000..1ef20f50cf52 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_holding_lit_item.dm @@ -0,0 +1,21 @@ +/// Passes when the keyed target is a carbon holding a lit item whose type is in a blackboard-keyed type list. +/// Use "invert": true for the opposite. +/datum/bt_node/decorator/target_holding_lit_item + /// Blackboard key holding the atom to check. + var/key = BB_CURRENT_TARGET + /// Blackboard key holding the list of item typepaths we care about. + var/item_types_key + +/datum/bt_node/decorator/target_holding_lit_item/check_condition(datum/ai_controller/controller) + var/mob/living/carbon/target = controller.blackboard[key] + if(!iscarbon(target)) + return FALSE + var/list/item_types = controller.blackboard[item_types_key] + if(!length(item_types)) + return FALSE + for(var/obj/item/held_item in target.held_items) + if(!is_type_in_list(held_item, item_types)) + continue + if(held_item.light_on) + return TRUE + return FALSE diff --git a/code/datums/ai/generic_decorators/target_is_holding_item.dm b/code/datums/ai/generic_decorators/target_is_holding_item.dm new file mode 100644 index 000000000000..888a327ea37f --- /dev/null +++ b/code/datums/ai/generic_decorators/target_is_holding_item.dm @@ -0,0 +1,42 @@ +/// Gates child on the mob held in a blackboard key currently holding any item in their hands. +/// Use "invert": true to gate on the target holding nothing. +/datum/bt_node/decorator/target_is_holding_item + /// Blackboard key holding the mob to check. + var/key = BB_CURRENT_TARGET + /// Tracked so we can rebind signals when the key changes. + var/mob/observed_target = null + +/datum/bt_node/decorator/target_is_holding_item/register_observe_signals(atom/pawn) + var/mob/target = owning_controller?.blackboard[key] + if(ismob(target)) + observed_target = target + RegisterSignals(target, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_target_key_changed)) + return TRUE + +/datum/bt_node/decorator/target_is_holding_item/unregister_observe_signals(atom/pawn) + if(observed_target) + UnregisterSignal(observed_target, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM)) + observed_target = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/// Fires when the blackboard key changes. Rebinds equip signals to the new target and re-evaluates. +/datum/bt_node/decorator/target_is_holding_item/proc/on_target_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/mob/target = owning_controller?.blackboard[key] + if(target == observed_target) + return + if(observed_target) + UnregisterSignal(observed_target, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM)) + observed_target = null + if(ismob(target)) + observed_target = target + RegisterSignals(target, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/target_is_holding_item/check_condition(datum/ai_controller/controller) + var/mob/target = controller.blackboard[key] + if(QDELETED(target)) + return FALSE + return target.get_num_held_items() > 0 diff --git a/code/datums/ai/generic_decorators/target_is_type.dm b/code/datums/ai/generic_decorators/target_is_type.dm new file mode 100644 index 000000000000..dba7d6a2618d --- /dev/null +++ b/code/datums/ai/generic_decorators/target_is_type.dm @@ -0,0 +1,12 @@ +/// Gates child on the atom held in a blackboard key being an instance of a given typepath. Use "invert": true for the opposite. +/datum/bt_node/decorator/target_is_type + /// Blackboard key holding the atom to check. + var/key = BB_CURRENT_TARGET + /// Typepath the target must be an instance of. + var/target_type + +/datum/bt_node/decorator/target_is_type/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target) || isnull(target_type)) + return FALSE + return istype(target, target_type) diff --git a/code/datums/ai/generic_decorators/target_legcuffed.dm b/code/datums/ai/generic_decorators/target_legcuffed.dm new file mode 100644 index 000000000000..7eabdb699b36 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_legcuffed.dm @@ -0,0 +1,10 @@ +/// Gates child on the carbon held in a blackboard key being legcuffed. Use "invert": true to gate on not being legcuffed. +/datum/bt_node/decorator/target_legcuffed + /// Blackboard key holding the mob to check. + var/key = BB_CURRENT_TARGET + +/datum/bt_node/decorator/target_legcuffed/check_condition(datum/ai_controller/controller) + var/mob/living/carbon/target = controller.blackboard[key] + if(!iscarbon(target)) + return FALSE + return !isnull(target.legcuffed) diff --git a/code/datums/ai/generic_decorators/target_on_ground.dm b/code/datums/ai/generic_decorators/target_on_ground.dm new file mode 100644 index 000000000000..bba87d5d5318 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_on_ground.dm @@ -0,0 +1,45 @@ +///Check fi target is still on a turf +/datum/bt_node/decorator/validate_target_on_turf + var/key + + var/atom/observed_target = null + +/datum/bt_node/decorator/validate_target_on_turf/register_observe_signals(atom/pawn) + var/atom/target = owning_controller?.blackboard[key] + if(target) + observed_target = target + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_key_changed)) + return TRUE + +/datum/bt_node/decorator/validate_target_on_turf/unregister_observe_signals(atom/pawn) + if(observed_target) + UnregisterSignal(observed_target, COMSIG_MOVABLE_MOVED) + observed_target = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/datum/bt_node/decorator/validate_target_on_turf/proc/on_key_changed(atom/pawn, ...) + SIGNAL_HANDLER + var/atom/target = owning_controller?.blackboard[key] + if(target == observed_target) + return + if(observed_target) + UnregisterSignal(observed_target, COMSIG_MOVABLE_MOVED) + observed_target = null + if(target) + observed_target = target + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/validate_target_on_turf/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target) || !isturf(target.loc)) + controller.clear_blackboard_key(key) + return FALSE + return TRUE + +/// Check without side effects for observer path. +/datum/bt_node/decorator/validate_target_on_turf/evaluate_for_observer(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + return !QDELETED(target) && isturf(target.loc) diff --git a/code/datums/ai/generic_decorators/true_for_time.dm b/code/datums/ai/generic_decorators/true_for_time.dm new file mode 100644 index 000000000000..c04383a1835c --- /dev/null +++ b/code/datums/ai/generic_decorators/true_for_time.dm @@ -0,0 +1,31 @@ +/** + * Passes while its duration has not elapsed, then fails reactively via observer abort. + * Starts timing on the first tick; resets when the tree resets. + * + * duration: how long to pass, in deciseconds (e.g. "10 SECONDS"). + * Set observer_abort to BT_ABORT_BOTH (or BT_ABORT_SELF) to abort the child when time expires. + */ +/datum/bt_node/decorator/true_for_time + var/duration = 0 + var/timer_id = null + var/timed_out = FALSE + +/datum/bt_node/decorator/true_for_time/register_observe_signals(atom/pawn) + timer_id = addtimer(CALLBACK(src, PROC_REF(on_timeout)), duration, TIMER_STOPPABLE|TIMER_DELETE_ME) + return TRUE + +/datum/bt_node/decorator/true_for_time/unregister_observe_signals(atom/pawn) + if(timer_id) + deltimer(timer_id) + timer_id = null + timed_out = FALSE + +/datum/bt_node/decorator/true_for_time/proc/on_timeout() + SIGNAL_HANDLER + timer_id = null + timed_out = TRUE + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/true_for_time/check_condition(datum/ai_controller/controller) + return !timed_out diff --git a/code/datums/ai/generic_hunger.bt.json b/code/datums/ai/generic_hunger.bt.json new file mode 100644 index 000000000000..905de0d57b3f --- /dev/null +++ b/code/datums/ai/generic_hunger.bt.json @@ -0,0 +1,43 @@ +{ + "dm_type": "/datum/bt_node/subtree/generic_hunger", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "bb_food_target" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/consume", + "vars": { + "target_key": "bb_food_target", + "hunger_timer_key": "BB_NEXT_HUNGRY" + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "bb_food_target", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up", + "vars": { + "target_key": "bb_food_target", + "drop_held": true + } + } + ] + } + ] + } +} diff --git a/code/datums/ai/generic_play_instrument.bt.json b/code/datums/ai/generic_play_instrument.bt.json new file mode 100644 index 000000000000..5da0a146839e --- /dev/null +++ b/code/datums/ai/generic_play_instrument.bt.json @@ -0,0 +1,83 @@ +{ + "dm_type": "/datum/bt_node/subtree/generic_play_instrument", + "bindings": { + "bulu13xf": { + "label": "volume", + "default": "50" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SONG_INSTRUMENT" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_INSTRUMENT_UP_ASS" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_holding_target", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": true, + "key": "BB_SONG_INSTRUMENT" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SONG_INSTRUMENT" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up", + "vars": { + "target_key": "BB_SONG_INSTRUMENT" + } + } + ] + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/keep_playing_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT" + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/setup_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT", + "song_lines_key": "song_lines" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/play_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT", + "volume": "$bulu13xf" + } + } + ] + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/basic_find_target.bt.json b/code/datums/ai/generic_subtrees/basic_find_target.bt.json new file mode 100644 index 000000000000..e6dfedebcbb4 --- /dev/null +++ b/code/datums/ai/generic_subtrees/basic_find_target.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/bt_node/subtree/basic_find_target", + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } +} diff --git a/code/datums/ai/generic_subtrees/basic_find_target.dm b/code/datums/ai/generic_subtrees/basic_find_target.dm new file mode 100644 index 000000000000..989688e5a06f --- /dev/null +++ b/code/datums/ai/generic_subtrees/basic_find_target.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/basic_find_target + behavior_tree_json = "code/datums/ai/generic_subtrees/basic_find_target.bt.json" diff --git a/code/datums/ai/generic_subtrees/capricious_pick_target.bt.json b/code/datums/ai/generic_subtrees/capricious_pick_target.bt.json new file mode 100644 index 000000000000..33549460fbb8 --- /dev/null +++ b/code/datums/ai/generic_subtrees/capricious_pick_target.bt.json @@ -0,0 +1,18 @@ +{ + "dm_type": "/datum/bt_node/subtree/capricious_pick_target", + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/capricious_retaliate", + "vars": { + "targeting_strategy": "BB_TARGETING_STRATEGY", + "ignore_faction": true + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pick_retaliate_target" + } + ] +} diff --git a/code/datums/ai/generic_subtrees/capricious_pick_target.dm b/code/datums/ai/generic_subtrees/capricious_pick_target.dm new file mode 100644 index 000000000000..91cd88264b63 --- /dev/null +++ b/code/datums/ai/generic_subtrees/capricious_pick_target.dm @@ -0,0 +1,4 @@ +/// Like [/datum/bt_node/subtree/pick_retaliate_target], but first rolls to randomly aggro on or +/// forgive a nearby mob. Used by animals that pick fights for no reason. +/datum/bt_node/subtree/capricious_pick_target + behavior_tree_json = "code/datums/ai/generic_subtrees/capricious_pick_target.bt.json" diff --git a/code/datums/ai/generic_subtrees/climb_tree.bt.json b/code/datums/ai/generic_subtrees/climb_tree.bt.json new file mode 100644 index 000000000000..0f11ef37f357 --- /dev/null +++ b/code/datums/ai/generic_subtrees/climb_tree.bt.json @@ -0,0 +1,50 @@ +{ + "dm_type": "/datum/bt_node/subtree/climb_tree", + "bindings": { + "bb0lcfol": { + "label": "tree_climbing_cooldown", + "default": "30 SECONDS" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_TREE_CLIMBING_COOLDOWN", + "cooldown_duration": "$bb0lcfol" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CLIMBED_TREE" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CLIMBED_TREE" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_CLIMBED_TREE" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_CLIMBED_TREE" + } + } + ] + } + } +} diff --git a/code/datums/ai/generic_subtrees/climb_tree.dm b/code/datums/ai/generic_subtrees/climb_tree.dm new file mode 100644 index 000000000000..7160d455982d --- /dev/null +++ b/code/datums/ai/generic_subtrees/climb_tree.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/climb_tree + behavior_tree_json = "code/datums/ai/generic_subtrees/climb_tree.bt.json" diff --git a/code/datums/ai/generic_subtrees/find_food.bt.json b/code/datums/ai/generic_subtrees/find_food.bt.json new file mode 100644 index 000000000000..a67032a285fd --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_food.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/bt_node/subtree/find_food", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "cooldown_key": "BB_NEXT_FOOD_EAT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_TARGET_FOOD", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_typed/from_bb_key/basic_foods", + "revalidation_mode": "TARGET_REVALIDATE" + } + } +} diff --git a/code/datums/ai/generic_subtrees/find_food.dm b/code/datums/ai/generic_subtrees/find_food.dm new file mode 100644 index 000000000000..74b2d386f77b --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_food.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/find_food + behavior_tree_json = "code/datums/ai/generic_subtrees/find_food.bt.json" diff --git a/code/datums/ai/generic_subtrees/find_partner.bt.json b/code/datums/ai/generic_subtrees/find_partner.bt.json new file mode 100644 index 000000000000..d48387851e17 --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_partner.bt.json @@ -0,0 +1,32 @@ +{ + "dm_type": "/datum/bt_node/subtree/find_partner", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_FUCKS" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BREED_READY" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_PARTNER_SEARCH_TIMEOUT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_partner", + "vars": { + "target_key": "BB_BABIES_TARGET", + "partner_types_key": "BB_BABIES_PARTNER_TYPES", + "child_types_key": "BB_BABIES_CHILD_TYPES" + } + } + } + } +} diff --git a/code/datums/ai/generic_subtrees/find_partner.dm b/code/datums/ai/generic_subtrees/find_partner.dm new file mode 100644 index 000000000000..a808c199fded --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_partner.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/find_partner + behavior_tree_json = "code/datums/ai/generic_subtrees/find_partner.bt.json" diff --git a/code/datums/ai/generic_subtrees/find_stealable_object.bt.json b/code/datums/ai/generic_subtrees/find_stealable_object.bt.json new file mode 100644 index 000000000000..0176eedb0eb6 --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_stealable_object.bt.json @@ -0,0 +1,88 @@ +{ + "dm_type": "/datum/bt_node/subtree/find_stealable_object", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_dragging", + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance_from_key", + "vars": { + "chance_key": "BB_GUILTY_CONSCIOUS_CHANCE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/stop_dragging" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_WANTS_TO_COMMIT_THEFT" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance_from_key", + "vars": { + "chance_key": "BB_STEAL_CHANCE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_WANTS_TO_COMMIT_THEFT", + "value": true + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_WANTS_TO_COMMIT_THEFT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_ITEM_TO_STEAL", + "targeting_strategy": "/datum/targeting_strategy/pickup_item/stealable_item", + "target_source": "/datum/target_source/oview", + "must_be_reachable": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_WANTS_TO_COMMIT_THEFT", + "value": false + } + } + ] + } + ] +} diff --git a/code/datums/ai/generic_subtrees/find_stealable_object.dm b/code/datums/ai/generic_subtrees/find_stealable_object.dm new file mode 100644 index 000000000000..4811a5207df2 --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_stealable_object.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/find_stealable_object + behavior_tree_json = "code/datums/ai/generic_subtrees/find_stealable_object.bt.json" diff --git a/code/datums/ai/generic_subtrees/forage_and_retaliate.bt.json b/code/datums/ai/generic_subtrees/forage_and_retaliate.bt.json new file mode 100644 index 000000000000..17a52c4d35d2 --- /dev/null +++ b/code/datums/ai/generic_subtrees/forage_and_retaliate.bt.json @@ -0,0 +1,92 @@ +{ + "dm_type": "/datum/bt_node/subtree/forage_and_retaliate", + "bindings": { + "q8w3rtv1": { + "label": "Food Finder", + "default": "/datum/bt_node/subtree/find_food" + }, + "z4n9bk7p": { + "label": "Retaliate Manager", + "default": "/datum/bt_node/subtree/capricious_pick_target" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_RETALIATE_LIST", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_DISABLE_IDLE", + "invert": true + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + } + ] + }, + { + "type": "subtree", + "subtype": "$z4n9bk7p" + }, + { + "type": "subtree", + "subtype": "$q8w3rtv1" + } + ] +} diff --git a/code/datums/ai/generic_subtrees/forage_and_retaliate.dm b/code/datums/ai/generic_subtrees/forage_and_retaliate.dm new file mode 100644 index 000000000000..291fa033090c --- /dev/null +++ b/code/datums/ai/generic_subtrees/forage_and_retaliate.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/forage_and_retaliate + behavior_tree_json = "code/datums/ai/generic_subtrees/forage_and_retaliate.bt.json" diff --git a/code/datums/ai/generic_subtrees/make_babies.dm b/code/datums/ai/generic_subtrees/make_babies.dm new file mode 100644 index 000000000000..9ed043fb2977 --- /dev/null +++ b/code/datums/ai/generic_subtrees/make_babies.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/make_babies + behavior_tree_json = "code/datums/ai/babies/make_babies.bt.json" diff --git a/code/datums/ai/generic_subtrees/move_to_and_eat.bt.json b/code/datums/ai/generic_subtrees/move_to_and_eat.bt.json new file mode 100644 index 000000000000..652b9f8d376d --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_and_eat.bt.json @@ -0,0 +1,36 @@ +{ + "dm_type": "/datum/bt_node/subtree/move_to_and_eat", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_TARGET_FOOD" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_FOOD", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "eats up happily!" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_TARGET_FOOD" + } + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/move_to_and_eat.dm b/code/datums/ai/generic_subtrees/move_to_and_eat.dm new file mode 100644 index 000000000000..d3b051bd1e03 --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_and_eat.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/move_to_and_eat + behavior_tree_json = "code/datums/ai/generic_subtrees/move_to_and_eat.bt.json" diff --git a/code/datums/ai/generic_subtrees/move_to_and_hunt.bt.json b/code/datums/ai/generic_subtrees/move_to_and_hunt.bt.json new file mode 100644 index 000000000000..b1e027d0dee0 --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_and_hunt.bt.json @@ -0,0 +1,63 @@ +{ + "dm_type": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bvtz06kb": { + "label": "hunting_target", + "default": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "b3y599q4": { + "label": "hunting_target", + "default": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "brrasnah": { + "label": "hunting_target", + "default": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "bqwjf4id": { + "label": "hunt_cooldown", + "default": "5 SECONDS" + }, + "bm3y5m55": { + "label": "cooldown_key", + "default": "" + }, + "bd1towgc": { + "label": "behavior_combat_mode", + "default": "TRUE" + }, + "b3cnse9r": { + "label": "always_reset_target", + "default": "TRUE" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "$bvtz06kb" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "$b3y599q4", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "$brrasnah", + "always_reset_target": "$b3cnse9r", + "hunt_cooldown": "$bqwjf4id", + "cooldown_key": "$bm3y5m55", + "behavior_combat_mode": "$bd1towgc" + } + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/move_to_and_hunt.dm b/code/datums/ai/generic_subtrees/move_to_and_hunt.dm new file mode 100644 index 000000000000..428ca9db2357 --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_and_hunt.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/move_to_and_hunt + behavior_tree_json = "code/datums/ai/generic_subtrees/move_to_and_hunt.bt.json" diff --git a/code/datums/ai/generic_subtrees/move_to_reinforce.bt.json b/code/datums/ai/generic_subtrees/move_to_reinforce.bt.json new file mode 100644 index 000000000000..08bad36b3f4b --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_reinforce.bt.json @@ -0,0 +1,30 @@ +{ + "dm_type": "/datum/bt_node/subtree/move_to_reinforce", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_BASIC_MOB_REINFORCEMENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BASIC_MOB_REINFORCEMENT_TARGET", + "required_dist": 0, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_BASIC_MOB_REINFORCEMENT_TARGET" + } + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/move_to_reinforce.dm b/code/datums/ai/generic_subtrees/move_to_reinforce.dm new file mode 100644 index 000000000000..3a3a95c47eff --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_reinforce.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/move_to_reinforce + behavior_tree_json = "code/datums/ai/generic_subtrees/move_to_reinforce.bt.json" diff --git a/code/datums/ai/generic_subtrees/pick_retaliate_target.bt.json b/code/datums/ai/generic_subtrees/pick_retaliate_target.bt.json new file mode 100644 index 000000000000..c1bef09b791f --- /dev/null +++ b/code/datums/ai/generic_subtrees/pick_retaliate_target.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/bt_node/subtree/pick_retaliate_target", + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } +} diff --git a/code/datums/ai/generic_subtrees/pick_retaliate_target.dm b/code/datums/ai/generic_subtrees/pick_retaliate_target.dm new file mode 100644 index 000000000000..e4e52f3d10ca --- /dev/null +++ b/code/datums/ai/generic_subtrees/pick_retaliate_target.dm @@ -0,0 +1,3 @@ +/// Picks a target from our retaliate list (real attackers only). +/datum/bt_node/subtree/pick_retaliate_target + behavior_tree_json = "code/datums/ai/generic_subtrees/pick_retaliate_target.bt.json" diff --git a/code/datums/ai/generic_subtrees/random_walk.bt.json b/code/datums/ai/generic_subtrees/random_walk.bt.json new file mode 100644 index 000000000000..fb0446306c38 --- /dev/null +++ b/code/datums/ai/generic_subtrees/random_walk.bt.json @@ -0,0 +1,29 @@ +{ + "dm_type": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": { + "label": "walk_chance", + "default": "25" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/true_for_time", + "vars": { + "duration": "5 SECONDS" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk", + "vars": { + "walk_chance": "$bf07i8ep" + } + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/random_walk.dm b/code/datums/ai/generic_subtrees/random_walk.dm new file mode 100644 index 000000000000..f4160f87ed0c --- /dev/null +++ b/code/datums/ai/generic_subtrees/random_walk.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/random_walk + behavior_tree_json = "code/datums/ai/generic_subtrees/random_walk.bt.json" diff --git a/code/datums/ai/generic_subtrees/skittish_and_speak.bt.json b/code/datums/ai/generic_subtrees/skittish_and_speak.bt.json new file mode 100644 index 000000000000..9899a48b230a --- /dev/null +++ b/code/datums/ai/generic_subtrees/skittish_and_speak.bt.json @@ -0,0 +1,32 @@ +{ + "dm_type": "/datum/bt_node/subtree/skittish_and_speak", + "bindings": { + "f17iiafz": { + "label": "Speech Behavior", + "default": "/datum/bt_node/ai_behavior/random_speech_blackboard" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_skittish_combat" + } + ] + }, + { + "type": "leaf", + "behavior": "$f17iiafz" + } + ] +} diff --git a/code/datums/ai/generic_subtrees/skittish_and_speak.dm b/code/datums/ai/generic_subtrees/skittish_and_speak.dm new file mode 100644 index 000000000000..6bef54057e63 --- /dev/null +++ b/code/datums/ai/generic_subtrees/skittish_and_speak.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/skittish_and_speak + behavior_tree_json = "code/datums/ai/generic_subtrees/skittish_and_speak.bt.json" diff --git a/code/datums/ai/generic_subtrees/steal_and_flee.bt.json b/code/datums/ai/generic_subtrees/steal_and_flee.bt.json new file mode 100644 index 000000000000..922f09d1d090 --- /dev/null +++ b/code/datums/ai/generic_subtrees/steal_and_flee.bt.json @@ -0,0 +1,43 @@ +{ + "dm_type": "/datum/bt_node/subtree/steal_and_flee", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_ITEM_TO_STEAL" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ITEM_TO_STEAL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/drag_target", + "vars": { + "target_key": "BB_ITEM_TO_STEAL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/copy_bb_key", + "vars": { + "dest_key": "BB_LAST_STOLEN_ITEM", + "source_key": "BB_ITEM_TO_STEAL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_ITEM_TO_STEAL" + } + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/steal_and_flee.dm b/code/datums/ai/generic_subtrees/steal_and_flee.dm new file mode 100644 index 000000000000..e20201f1fdd4 --- /dev/null +++ b/code/datums/ai/generic_subtrees/steal_and_flee.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/steal_and_flee + behavior_tree_json = "code/datums/ai/generic_subtrees/steal_and_flee.bt.json" diff --git a/code/datums/ai/hauntium/haunted.bt.json b/code/datums/ai/hauntium/haunted.bt.json new file mode 100644 index 000000000000..88cad7cc215a --- /dev/null +++ b/code/datums/ai/hauntium/haunted.bt.json @@ -0,0 +1,107 @@ +{ + "dm_type": "/datum/ai_controller/haunted", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/item_being_held", + "vars": { + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LIKES_EQUIPPER", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": 0 + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/item_escape_grasp" + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_HAUNT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "2 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/throw_attack/haunted", + "vars": { + "target_key": "BB_HAUNT_TARGET", + "throw_count_key": "BB_HAUNTED_THROW_ATTEMPT_COUNT", + "haunt_list_key": "BB_TO_HAUNT_LIST" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HAUNT_TARGET", + "required_dist": 3, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_ghost_item" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_haunt_target", + "vars": { + "target_key": "BB_HAUNT_TARGET", + "haunt_list_key": "BB_TO_HAUNT_LIST" + } + } + ] + } + ] +} diff --git a/code/datums/ai/hauntium/haunted_bt_nodes.dm b/code/datums/ai/hauntium/haunted_bt_nodes.dm new file mode 100644 index 000000000000..576f37b8f386 --- /dev/null +++ b/code/datums/ai/hauntium/haunted_bt_nodes.dm @@ -0,0 +1,83 @@ +/// Gates on whether the haunted item pawn is currently inside a mob's inventory. +/datum/bt_node/decaorator/item_being_held + +/datum/bt_node/decorator/item_being_held/check_condition(datum/ai_controller/controller) + return ismob(controller.pawn.loc) + +/datum/bt_node/decorator/item_being_held/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_ITEM_ENTERED_HANDS, COMSIG_ITEM_DROPPED), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/item_being_held/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_ITEM_ENTERED_HANDS, COMSIG_ITEM_DROPPED)) + +/** + * Attempts to slip out of the holder's hands with a per-tick probability. + * Returns RUNNING while waiting for the chance to fire; SUCCEEDED after escaping. + */ +/datum/bt_node/ai_behavior/item_escape_grasp + +/datum/bt_node/ai_behavior/item_escape_grasp/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(HAUNTED_ITEM_ESCAPE_GRASP_CHANCE, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT + var/obj/item/item_pawn = controller.pawn + var/mob/item_holder = item_pawn.loc + if(!ismob(item_holder)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + item_pawn.visible_message(span_warning("[item_pawn] slips out of the hands of [item_holder]!")) + item_holder.dropItemToGround(item_pawn, TRUE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +///Find someone to haunt +/datum/bt_node/ai_behavior/pick_haunt_target + var/target_key + var/haunt_list_key + +/datum/bt_node/ai_behavior/pick_haunt_target/perform(seconds_per_tick, datum/ai_controller/controller) + if(!prob(HAUNTED_ITEM_ATTACK_HAUNT_CHANCE)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/obj/item/item_pawn = controller.pawn + var/list/to_haunt = controller.blackboard[haunt_list_key] + for(var/mob/living/candidate as anything in to_haunt) + if(QDELETED(candidate) || to_haunt[candidate] <= 0) + controller.remove_thing_from_blackboard_key(haunt_list_key, candidate) + continue + if(get_dist(candidate, item_pawn) <= CURSED_VIEW_RANGE) + controller.set_blackboard_key(target_key, candidate) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + +/// Haunted variant: decrements haunt list aggro when throws are exhausted. +/// BT args: target_key, throw_count_key, haunt_list_key +/datum/bt_node/ai_behavior/throw_attack/haunted + var/haunt_list_key + max_attempts = HAUNTED_MAX_THROW_ATTEMPTS + /// Cached from perform args so on_throws_exhausted can access haunt_list_key. + var/active_haunt_list_key + +/datum/bt_node/ai_behavior/throw_attack/haunted/perform(seconds_per_tick, datum/ai_controller/controller) + active_haunt_list_key = haunt_list_key + return ..() + +/datum/bt_node/ai_behavior/throw_attack/haunted/on_throws_exhausted(datum/ai_controller/controller, atom/throw_target, target_key, throw_count_key) + controller.add_blackboard_key_assoc(active_haunt_list_key, throw_target, -1) + return ..() + + +///Teleport every now and then +/datum/bt_node/ai_behavior/idle_ghost_item + ///Chance for item to teleport somewhere else + var/teleport_chance = 4 + time_between_perform = 1 SECONDS + +/datum/bt_node/ai_behavior/idle_ghost_item/perform(seconds_per_tick, datum/ai_controller/controller) + . = ..() + var/obj/item/item_pawn = controller.pawn + if(ismob(item_pawn.loc)) //Being held. dont teleport + return AI_BEHAVIOR_INSTANT + if(SPT_PROB(teleport_chance, seconds_per_tick)) + playsound(item_pawn.loc, 'sound/items/haunted/ghostitemattack.ogg', 100, TRUE) + #ifndef UNIT_TESTS // hauntium teleports can cause mapping nearstation tests to fail if it teleports outside an area + do_teleport(item_pawn, get_turf(item_pawn), 4, channel = TELEPORT_CHANNEL_MAGIC) + #endif + return AI_BEHAVIOR_INSTANT diff --git a/code/datums/ai/hauntium/haunted_controller.dm b/code/datums/ai/hauntium/haunted_controller.dm index b2863a310c16..45388702fc74 100644 --- a/code/datums/ai/hauntium/haunted_controller.dm +++ b/code/datums/ai/hauntium/haunted_controller.dm @@ -1,5 +1,6 @@ /datum/ai_controller/haunted + behavior_tree_json = "code/datums/ai/hauntium/haunted.bt.json" movement_delay = 0.4 SECONDS blackboard = list( BB_TO_HAUNT_LIST = list(), @@ -7,8 +8,6 @@ BB_HAUNT_TARGET, BB_HAUNTED_THROW_ATTEMPT_COUNT, ) - planning_subtrees = list(/datum/ai_planning_subtree/haunted) - idle_behavior = /datum/idle_behavior/idle_ghost_item /datum/ai_controller/haunted/TryPossessPawn(atom/new_pawn) if(!isitem(new_pawn)) diff --git a/code/datums/ai/hauntium/hauntium_subtrees.dm b/code/datums/ai/hauntium/hauntium_subtrees.dm deleted file mode 100644 index 57b5d2ae7e07..000000000000 --- a/code/datums/ai/hauntium/hauntium_subtrees.dm +++ /dev/null @@ -1,24 +0,0 @@ -/datum/ai_planning_subtree/haunted/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/obj/item/item_pawn = controller.pawn - - if(ismob(item_pawn.loc)) //We're being held, maybe escape? - if(controller.blackboard[BB_LIKES_EQUIPPER])//don't unequip from people it's okay with - return - if(SPT_PROB(HAUNTED_ITEM_ESCAPE_GRASP_CHANCE, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/item_escape_grasp) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!SPT_PROB(HAUNTED_ITEM_ATTACK_HAUNT_CHANCE, seconds_per_tick)) - return - - var/list/to_haunt_list = controller.blackboard[BB_TO_HAUNT_LIST] - - for(var/mob/living/haunt_target as anything in to_haunt_list) - if(to_haunt_list[haunt_target] <= 0) - controller.remove_thing_from_blackboard_key(BB_TO_HAUNT_LIST, haunt_target) - continue - - if(get_dist(haunt_target, item_pawn) <= 7) - controller.set_blackboard_key(BB_HAUNT_TARGET, haunt_target) - controller.queue_behavior(/datum/ai_behavior/item_move_close_and_attack/ghostly/haunted, BB_HAUNT_TARGET, BB_HAUNTED_THROW_ATTEMPT_COUNT) - return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm deleted file mode 100644 index 1095919c058b..000000000000 --- a/code/datums/ai/hunting_behavior/hunting_behaviors.dm +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Tells the AI to find a certain target nearby to hunt. - * If a target has been found, we will start to move towards it, and eventually attack it. - */ -/datum/ai_planning_subtree/find_and_hunt_target - /// What key in the blacbkboard do we store our hunting target? - /// If you want to have multiple hunting behaviors on a controller be sure that this is unique - var/target_key = BB_CURRENT_HUNTING_TARGET - /// What behavior to execute if we have no target - var/finding_behavior = /datum/ai_behavior/find_hunt_target - /// What behavior to execute if we do have a target - var/hunting_behavior = /datum/ai_behavior/hunt_target - /// What targets we're hunting for - var/list/hunt_targets - /// In what radius will we hunt - var/hunt_range = 2 - /// What are the chances we hunt something at any given moment - var/hunt_chance = 100 - ///do we finish planning subtree - var/finish_planning = TRUE - -/datum/ai_planning_subtree/find_and_hunt_target/New() - . = ..() - hunt_targets = typecacheof(hunt_targets) - -/datum/ai_planning_subtree/find_and_hunt_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(hunt_chance, seconds_per_tick)) - return - - if(controller.blackboard[BB_HUNTING_COOLDOWN(type)] >= world.time) - return - - if(!controller.blackboard_key_exists(target_key)) - controller.queue_behavior(finding_behavior, target_key, hunt_targets, hunt_range) - return - - // We ARE hunting something, execute the hunt. - // Note that if our AI controller has multiple hunting subtrees set, - // we may accidentally be executing another tree's hunt - not ideal, - // try to set a unique target key if you have multiple - - controller.queue_behavior(hunting_behavior, target_key, BB_HUNTING_COOLDOWN(type)) - if(finish_planning) - return SUBTREE_RETURN_FINISH_PLANNING //If we're hunting we're too busy for anything else - -/// Finds a specific atom type to hunt. -/datum/ai_behavior/find_hunt_target - ///is this only meant to search for turf types? - var/search_turf_types = FALSE - -/datum/ai_behavior/find_hunt_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) - var/mob/living/living_mob = controller.pawn - var/list/interesting_objects = search_turf_types ? RANGE_TURFS(hunt_range, living_mob) : oview(hunt_range, living_mob) - for(var/atom/possible_dinner as anything in typecache_filter_list(interesting_objects, types_to_hunt)) - if(!valid_dinner(living_mob, possible_dinner, hunt_range, controller, seconds_per_tick)) - continue - controller.set_blackboard_key(hunting_target_key, possible_dinner) - EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [possible_dinner] as a target for blackboard key [hunting_target_key]! Behavior: [src]", get_turf(possible_dinner), "Target: [possible_dinner]") - EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(possible_dinner)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/find_hunt_target/proc/valid_dinner(mob/living/source, atom/dinner, radius, datum/ai_controller/controller, seconds_per_tick) - if(isliving(dinner)) - var/mob/living/living_target = dinner - if(living_target.stat == DEAD) //bitch is dead - return FALSE - - return can_see(source, dinner, radius) - -/datum/ai_behavior/find_hunt_target/search_turf_types - search_turf_types = TRUE - -/// Hunts down a specific atom type. -/datum/ai_behavior/hunt_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - /// How long do we have to wait after a successful hunt? - var/hunt_cooldown = 5 SECONDS - /// Do we reset the target after attacking something, so we can check for status changes. - var/always_reset_target = FALSE - - -/datum/ai_behavior/hunt_target/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) - . = ..() - var/atom/hunt_target = controller.blackboard[hunting_target_key] - if (isnull(hunt_target)) - return FALSE - set_movement_target(controller, hunt_target) - -/datum/ai_behavior/hunt_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) - var/mob/living/hunter = controller.pawn - var/atom/hunted = controller.blackboard[hunting_target_key] - - if(QDELETED(hunted)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - target_caught(hunter, hunted) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/hunt_target/proc/target_caught(mob/living/hunter, atom/hunted) - if(isliving(hunted)) // Are we hunting a living mob? - var/mob/living/living_target = hunted - hunter.manual_emote("chomps [living_target]!") - living_target.investigate_log("has been killed by [key_name(hunter)].", INVESTIGATE_DEATHS) - living_target.death() - - else if(IS_EDIBLE(hunted)) - hunted.attack_animal(hunter) - - else // We're hunting an object, and should delete it instead of killing it. Mostly useful for decal bugs like ants or spider webs. - hunter.manual_emote("chomps [hunted]!") - qdel(hunted) - -/datum/ai_behavior/hunt_target/finish_action(datum/ai_controller/controller, succeeded, hunting_target_key, hunting_cooldown_key) - . = ..() - if(succeeded && hunting_cooldown_key) - controller.set_blackboard_key(hunting_cooldown_key, world.time + hunt_cooldown) - else if(hunting_target_key) - controller.clear_blackboard_key(hunting_target_key) - if(always_reset_target && hunting_target_key) - controller.clear_blackboard_key(hunting_target_key) - -/datum/ai_behavior/hunt_target/interact_with_target - ///what combat mode should we use to interact with - var/behavior_combat_mode = TRUE - -/datum/ai_behavior/hunt_target/interact_with_target/target_caught(mob/living/hunter, obj/structure/cable/hunted) - var/datum/ai_controller/controller = hunter.ai_controller - controller.ai_interact(target = hunted, combat_mode = behavior_combat_mode) - -/datum/ai_behavior/hunt_target/interact_with_target/combat_mode_off - behavior_combat_mode = FALSE - -/datum/ai_behavior/hunt_target/interact_with_target/reset_target - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off - always_reset_target = TRUE - behavior_combat_mode = FALSE - -/datum/ai_behavior/hunt_target/use_ability_on_target - always_reset_target = TRUE - ///the ability we will use - var/ability_key - -/datum/ai_behavior/hunt_target/use_ability_on_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) - var/datum/action/cooldown/ability = controller.blackboard[ability_key] - if(!ability?.IsAvailable()) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - return ..() - -/datum/ai_behavior/hunt_target/use_ability_on_target/target_caught(mob/living/hunter, atom/hunted) - var/datum/action/cooldown/ability = hunter.ai_controller.blackboard[ability_key] - ability.InterceptClickOn(hunter, null, hunted) - - -/datum/ai_behavior/hunt_target/latch_onto - -/datum/ai_behavior/hunt_target/latch_onto/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) - . = ..() - var/mob/living/living_pawn = controller.pawn - if(living_pawn.buckled) - return FALSE - -/datum/ai_behavior/hunt_target/latch_onto/target_caught(mob/living/hunter, obj/hunted) - if(hunter.buckled) - return FALSE - if(!hunted.buckle_mob(hunter, force = TRUE)) - return FALSE - hunted.visible_message(span_notice("[hunted] has been latched onto by [hunter]!")) - return TRUE diff --git a/code/datums/ai/hunting_behavior/hunting_cockroach.dm b/code/datums/ai/hunting_behavior/hunting_cockroach.dm deleted file mode 100644 index a786342bfa0c..000000000000 --- a/code/datums/ai/hunting_behavior/hunting_cockroach.dm +++ /dev/null @@ -1,2 +0,0 @@ -/datum/ai_planning_subtree/find_and_hunt_target/roach - hunt_targets = list(/obj/effect/decal/cleanable/ants) diff --git a/code/datums/ai/hunting_behavior/hunting_corpses.dm b/code/datums/ai/hunting_behavior/hunting_corpses.dm deleted file mode 100644 index 89d100263fb1..000000000000 --- a/code/datums/ai/hunting_behavior/hunting_corpses.dm +++ /dev/null @@ -1,17 +0,0 @@ -/// Find and attack corpses -/datum/ai_planning_subtree/find_and_hunt_target/corpses - finding_behavior = /datum/ai_behavior/find_hunt_target/corpses - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target - hunt_targets = list(/mob/living) - -/// Find nearby dead mobs -/datum/ai_behavior/find_hunt_target/corpses - -/datum/ai_behavior/find_hunt_target/corpses/valid_dinner(mob/living/source, mob/living/dinner, radius) - if (!isliving(dinner) || dinner.stat != DEAD) - return FALSE - return can_see(source, dinner, radius) - -/// Find and attack specifically human corpses -/datum/ai_planning_subtree/find_and_hunt_target/corpses/human - hunt_targets = list(/mob/living/carbon/human) diff --git a/code/datums/ai/hunting_behavior/hunting_lights.dm b/code/datums/ai/hunting_behavior/hunting_lights.dm deleted file mode 100644 index 5062a8aaf929..000000000000 --- a/code/datums/ai/hunting_behavior/hunting_lights.dm +++ /dev/null @@ -1,18 +0,0 @@ -/datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/light_fixtures - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/light_fixtures - hunt_targets = list(/obj/machinery/light) - hunt_range = 7 - -/datum/ai_behavior/hunt_target/interact_with_target/light_fixtures - hunt_cooldown = 10 SECONDS - always_reset_target = TRUE - -/datum/ai_behavior/find_hunt_target/light_fixtures - -/datum/ai_behavior/find_hunt_target/light_fixtures/valid_dinner(mob/living/source, obj/machinery/light/dinner, radius) - if(dinner.status == LIGHT_BROKEN) //light is already broken - return FALSE - - return can_see(source, dinner, radius) diff --git a/code/datums/ai/hunting_behavior/hunting_mouse.dm b/code/datums/ai/hunting_behavior/hunting_mouse.dm deleted file mode 100644 index 3bcee20b79ea..000000000000 --- a/code/datums/ai/hunting_behavior/hunting_mouse.dm +++ /dev/null @@ -1,55 +0,0 @@ -// Mouse subtree to hunt down delicious cheese. -/datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/mouse - hunt_targets = list(/obj/item/food/cheese) - hunt_range = 1 - -// Mouse subtree to hunt down ... delicious cabling? -/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/mouse - finding_behavior = /datum/ai_behavior/find_hunt_target/mouse_cable - hunt_targets = list(/obj/structure/cable) - hunt_range = 0 // Only look below us - hunt_chance = 1 - -// When looking for a cable, we can only bite things we can reach. -/datum/ai_behavior/find_hunt_target/mouse_cable - -/datum/ai_behavior/find_hunt_target/mouse_cable/valid_dinner(mob/living/source, obj/structure/cable/dinner, radius) - . = ..() - if(!.) - return - - var/turf/open/floor/below_the_cable = get_turf(dinner) - if(!istype(below_the_cable)) - return FALSE - - return below_the_cable.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE - -// Our hunts have a decent cooldown. -/datum/ai_behavior/hunt_target/interact_with_target/mouse - hunt_cooldown = 20 SECONDS - -/datum/ai_planning_subtree/approach_synthesizer - -/datum/ai_planning_subtree/approach_synthesizer/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/pawn = controller.pawn - var/atom/instrument = controller.blackboard[BB_SONG_INSTRUMENT] - if(!isnull(instrument)) - if (!isturf(instrument.loc) || !can_see(pawn, instrument)) - controller.clear_blackboard_key(BB_SONG_INSTRUMENT) - return - if (instrument.IsReachableBy(pawn)) - return - controller.queue_behavior(/datum/ai_behavior/travel_towards/adjacent, BB_SONG_INSTRUMENT) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/piano_synth, BB_SONG_INSTRUMENT, /obj/item/instrument/piano_synth) - -/datum/ai_behavior/find_and_set/piano_synth // Disinclude subtypes - -/datum/ai_behavior/find_and_set/piano_synth/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/obj/item/instrument/piano_synth/synth in oview(search_range, controller.pawn)) - if(synth.type == /obj/item/instrument/piano_synth) - return synth diff --git a/code/datums/ai/idle_behaviors/_idle_behavior.dm b/code/datums/ai/idle_behaviors/_idle_behavior.dm deleted file mode 100644 index bacb8e7cdf37..000000000000 --- a/code/datums/ai/idle_behaviors/_idle_behavior.dm +++ /dev/null @@ -1,5 +0,0 @@ -/datum/idle_behavior - -/datum/idle_behavior/proc/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - set waitfor = FALSE - SHOULD_CALL_PARENT(TRUE) diff --git a/code/datums/ai/idle_behaviors/idle_dog.dm b/code/datums/ai/idle_behaviors/idle_dog.dm deleted file mode 100644 index 4d036e9a7a5d..000000000000 --- a/code/datums/ai/idle_behaviors/idle_dog.dm +++ /dev/null @@ -1,21 +0,0 @@ -///Dog specific idle behavior. -/datum/idle_behavior/idle_dog/perform_idle_behavior(seconds_per_tick, datum/ai_controller/basic_controller/dog/controller) - . = ..() - var/mob/living/living_pawn = controller.pawn - if(!isturf(living_pawn.loc) || living_pawn.pulledby) - return - - var/obj/item/carry_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM] - // if we're just ditzing around carrying something, occasionally print a message so people know we have something - if(carry_item && SPT_PROB(5, seconds_per_tick)) - living_pawn.visible_message(span_notice("[living_pawn] gently teethes on \the [carry_item] in [living_pawn.p_their()] mouth."), vision_distance=COMBAT_MESSAGE_RANGE) - - // Custom movement rate, for old corgis, etc. - var/move_chance = controller.blackboard[BB_DOG_IS_SLOW] ? 2.5 : 5 - - if(SPT_PROB(move_chance, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE)) - var/move_dir = pick(GLOB.alldirs) - living_pawn.Move(get_step(living_pawn, move_dir), move_dir) - else if(SPT_PROB(2, seconds_per_tick)) - living_pawn.manual_emote(pick("dances around.", "chases [living_pawn.p_their()] tail!")) - living_pawn.AddComponent(/datum/component/spinny) diff --git a/code/datums/ai/idle_behaviors/idle_haunted.dm b/code/datums/ai/idle_behaviors/idle_haunted.dm deleted file mode 100644 index 756adae93134..000000000000 --- a/code/datums/ai/idle_behaviors/idle_haunted.dm +++ /dev/null @@ -1,15 +0,0 @@ -///If not held, teleport somewhere else -/datum/idle_behavior/idle_ghost_item - ///Chance for item to teleport somewhere else - var/teleport_chance = 4 - -/datum/idle_behavior/idle_ghost_item/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - var/obj/item/item_pawn = controller.pawn - if(ismob(item_pawn.loc)) //Being held. dont teleport - return - if(SPT_PROB(teleport_chance, seconds_per_tick)) - playsound(item_pawn.loc, 'sound/items/haunted/ghostitemattack.ogg', 100, TRUE) - #ifndef UNIT_TESTS // hauntium teleports can cause mapping nearstation tests to fail if it teleports outside an area - do_teleport(item_pawn, get_turf(item_pawn), 4, channel = TELEPORT_CHANNEL_MAGIC) - #endif diff --git a/code/datums/ai/idle_behaviors/idle_monkey.dm b/code/datums/ai/idle_behaviors/idle_monkey.dm deleted file mode 100644 index c32534dce529..000000000000 --- a/code/datums/ai/idle_behaviors/idle_monkey.dm +++ /dev/null @@ -1,37 +0,0 @@ -/datum/idle_behavior/idle_monkey - ///Emotes that will be played commonly during idle behavior. - var/list/common_emotes = list( - "screech", - "roar", - ) - ///Emotes that will be played rarely during idle behavior. - var/list/rare_emotes = list( - "scratch", - "jump", - "roll", - "tail", - ) - -/datum/idle_behavior/idle_monkey/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - var/mob/living/living_pawn = controller.pawn - - if(SPT_PROB(25, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) - var/move_dir = pick(GLOB.alldirs) - living_pawn.Move(get_step(living_pawn, move_dir), move_dir) - else if(SPT_PROB(5, seconds_per_tick)) - INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(common_emotes)) - else if(SPT_PROB(1, seconds_per_tick)) - INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(rare_emotes)) - -/datum/idle_behavior/idle_monkey/pun_pun - common_emotes = list( - "tunesing", - "dance", - "bow", - ) - rare_emotes = list( - "clear", - "sign", - "tail", - ) diff --git a/code/datums/ai/idle_behaviors/idle_random_walk.dm b/code/datums/ai/idle_behaviors/idle_random_walk.dm deleted file mode 100644 index bedcb12e5c72..000000000000 --- a/code/datums/ai/idle_behaviors/idle_random_walk.dm +++ /dev/null @@ -1,86 +0,0 @@ -/datum/idle_behavior/idle_random_walk - ///Chance that the mob random walks per second - var/walk_chance = 25 - -/datum/idle_behavior/idle_random_walk/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - var/mob/living/living_pawn = controller.pawn - if(LAZYLEN(living_pawn.do_afters)) - return FALSE - - var/actual_chance = controller.blackboard[BB_BASIC_MOB_IDLE_WALK_CHANCE] || walk_chance - if(SPT_PROB(actual_chance, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) - var/move_dir = pick(GLOB.alldirs) - var/turf/destination_turf = get_step(living_pawn, move_dir) - if(!destination_turf?.can_cross_safely(living_pawn)) - return FALSE - living_pawn.Move(destination_turf, move_dir) - return TRUE - -/datum/idle_behavior/idle_random_walk/less_walking - walk_chance = 10 - -/// Only walk if we don't have a target -/datum/idle_behavior/idle_random_walk/no_target - /// Where do we look for a target? - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - -/datum/idle_behavior/idle_random_walk/no_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - if (!controller.blackboard_key_exists(target_key)) - return - return ..() - -/// Only walk if we are not on the target's location -/datum/idle_behavior/idle_random_walk/not_while_on_target - ///What is the spot we have to stand on? - var/target_key - -/datum/idle_behavior/idle_random_walk/not_while_on_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - var/atom/target = controller.blackboard[target_key] - - //Don't move, if we are are already standing on it - if(!QDELETED(target) && ((isturf(target) && controller.pawn.loc == target) || (target.loc == controller.pawn.loc))) - return - - return ..() - -/// walk randomly however stick near a target -/datum/idle_behavior/walk_near_target - /// chance to walk - var/walk_chance = 25 - /// distance we are to target - var/minimum_distance = 20 - /// key that holds target - var/target_key - -/datum/idle_behavior/walk_near_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - var/mob/living/living_pawn = controller.pawn - if(LAZYLEN(living_pawn.do_afters)) - return - - if(!SPT_PROB(walk_chance, seconds_per_tick) || !(living_pawn.mobility_flags & MOBILITY_MOVE) || !isturf(living_pawn.loc) || living_pawn.pulledby) - return - - var/atom/target = controller.blackboard[target_key] - var/distance = get_dist(target, living_pawn) - if(isnull(target) || distance > minimum_distance) //if we are too far away from target, just walk randomly - var/move_dir = pick(GLOB.alldirs) - living_pawn.Move(get_step(living_pawn, move_dir), move_dir) - return - - var/list/possible_turfs = list() - for(var/direction in GLOB.alldirs) - var/turf/possible_step = get_step(living_pawn, direction) - if(get_dist(possible_step, target) > minimum_distance) - continue - if(possible_step.is_blocked_turf() || !possible_step.can_cross_safely(living_pawn)) - continue - possible_turfs += possible_step - - if(!length(possible_turfs)) - return - - var/turf/picked_turf = pick(possible_turfs) - - living_pawn.Move(picked_turf, get_dir(living_pawn, picked_turf)) diff --git a/code/datums/ai/learn_ai.md b/code/datums/ai/learn_ai.md index 5fe38924489a..4da35d60000e 100644 --- a/code/datums/ai/learn_ai.md +++ b/code/datums/ai/learn_ai.md @@ -1,139 +1,246 @@ -# Learn AI +# Learn Behavior Tree AI -In ye olde days, we designed mob AI, and we built it into simple animals as they were the "non player controlled" mobs. Made sense at the time. But by coding AI directly into the mob, there was so little ability to make unique or complicated AI, and even when it was pulled off the code was hacky and non-reusable. the datum AI system was made to rectify these problems, and expand AI beyond just mobs. +This file covers how the behavior tree system works in /tg/station, and how to use it in practice. -## AI Controllers Attach +## Disclaimer -Any atom can have an AI controller, I'm choosing a basic mob for this guide, because basic mobs stand as a nice "blank canvas" for AI on mobs. Simple animals come with AI built into the mob, basic mobs don't, which is great for us adding AI on top of it. +I strongly reccomend you install the "BehaviorTreeG" extension before continueing, as it is the intended way of editing behavior trees. In theory you can manually edit them, but this is strongly reccomended against due to the nature of the structure of behavior trees. -Anyways, we just define the type of AI this mob has on the ai_controller var. It starts as a type, but is turned into an instance once the mob is instantiated. +## What are behavior trees? -```dm -/mob/living/basic/butterfly - name = "butterfly" - desc = "A colorful butterfly, how'd it get up here?" - // a lot more variables defining for us what a butterfly is +Behavior trees are a common pattern in game AI in which you build your AI out of a tree made out of nodes. These nodes structure and conditionalize AI behavior in a readable way. - ai_controller = /datum/ai_controller/basic/butterfly -``` +The behavior tree runs left to right, allowing you to put high priority behavior first, and only running low priority behavior later. It also lets you sequence a series of behaviors after each other to create sensible sequences (e.g. grab key, unlock door, move through door) -## Controllers Themselves +## Blackboard First -First, let's look at the blackboard. +The blackboard is the controller's shared memory. It makes use of an associative list to allow arbitrary keys to be assigned values. -```dm -/datum/ai_controller/basic/cow - blackboard = list( - BB_TARGETING_STRATEGY = new /datum/targeting_strategy/basic/allow_items(), - BB_BASIC_MOB_TIP_REACTING = FALSE, - BB_BASIC_MOB_TIPPER = null, - ) -``` +- Nodes read keys from it to make decisions. +- Nodes write keys to it so later nodes can use those results. +- Keys are usually things like "current target", "current objective", "cooldowns", or "flags". -Think of the blackboard as the unique format for variables. They are set initially, or by behaviors, **but never in subtrees.** Because we check `blackboard[BB_SOME_KEY]` instead of a variable, we can wipe out variables and slap new ones onto the AI as it runs. For example, this cow uses BB_BASIC_MOB_TIP_REACTING and BB_BASIC_MOB_TIPPER because cows can get tipped, and the AI needs to know that in the subtrees when it plans behavior. And in fact, those two keys aren't required to be defined initially, it's just for clarity that they are. +In essence, these are just variables. But by storing them in an associative list we do not have to have define actual variables, and our AI code can be made out of modular pieces and does not need to rely on hardcoded variables on the controller. -Speaking of subtrees, let's look at that now. +The keys for the blackboard are simple string defines: -```dm -/datum/ai_controller/basic/cow +example: +`#define BB_CURRENT_TARGET "Current Target"` - planning_subtrees = list( - /datum/ai_planning_subtree/tip_reaction, //<- goes first - /datum/ai_planning_subtree/find_and_eat_food, //<- goes second - /datum/ai_planning_subtree/random_speech/cow, //<- goes last! But at any point, a previous subtree can end the chain. If a cow is tipped over, it shouldn't make random noises or try finding food! - ) - //and by the end for however many subtrees ran, each one that did may have planned behavior for the AI to act on. -``` +After this is done, you can start using the key in your behavior tree; by setting it somewhere in the tree it will automatically be added to the blackboard. -AI's work by planning specific behaviors, and subtrees are datums that bundle the planning of behavior together. From top to bottom they run, and they can cancel future subtrees. As an example, cows have their very first consideration be tip_reaction, a subtree that prevents further subtrees like eating food and random speech, as well as planning out how the cow reacts (looking sad at the person who tipped it). +Example flow (in psuedocode): -```dm -/datum/ai_controller/basic/cow - ai_traits = null - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = null +```text +FIND TARGET LEAF: + set BB_CURRENT_TARGET +ATTACK BRANCH: + if BB_CURRENT_TARGET is set + move toward target + attack target ``` -Finally, we have some more minor things. +## Behavior Tree Node -- ai_traits are flags for the AI, things like "STOP_MOVING_WHEN_PULLED" slightly modifying how the AI acts under some situations. -- ai_movement is how the mob moves to its movement target. ranges from simple behaviors like ai_movement/dumb that awlays move in the direction of the target and hope there's nothing in the way, all the way to ai_movement/jps that plans and occasionally recalcuates more complicated paths, at the cost of more lag. -- idle_behavior is just some simpler behavior to perform when nothing has been planned at all, like idle_behavior/idle_random_walk making a mob wander passively. +This is the parent type of all other nodes in this guide. And when active they will return one of three values: -## Subtrees and Behaviors +1. BT_SUCCESS - This node succeeded, and will report this to its parent node +2. BT_FAILURE - This node failed, and will report this to its parent node +3. BT_RUNNING - This node is still running, and will report this to its parent node. -Okay, so we have blackboard variables, which are considered by subtrees to plan behaviors. Let's actually look at a subtree planning behaviors, and behaviors themselves. +This is important for the flow of the behavior tree, as different nodes will behave differently depending on whether their children succeed or fail. -```dm -/// this subtree checks if the mob has a target. if it doesn't, it plans looking for food. if it does, it tries to eat the food via attacking it. -/datum/ai_planning_subtree/find_and_eat_food/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - //get things out of blackboard - var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - var/atom/target = weak_target?.resolve() - var/list/wanted = controller.blackboard[BB_BASIC_FOODS] +## Leaf / ai_behavior - //we see if we have a target (remember, anything can be in that blackboard, it's not a hard reference) - if(!target || QDELETED(target)) - //we need to find some food - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_BASIC_MOB_CURRENT_TARGET, wanted) - return //this allows further subtrees to plan since we're doing a non-invasive behavior like checking the viscinity for food. +A leaf is the behavior node, and it is what performs actual actions. - //now we know we have a target but should let a hostile subtree plan attacking humans. let's check if it's actually food - if(target in wanted) - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING //this prevents further subtrees from planning since we want to focus on eating the food -``` +It is defined as /datum/bt_node/ai_behavior + +Common leaf jobs: + +- Find a target. +- Move toward something. +- Attack. +- Use an ability. +- Execute job logic (clean, heal, arrest, etc). + +Example: + +![Behavior example](learn_ai_images/behavior_example.png) + +This moves to the target set on the selected target key, and finishes execution when arrived + +## Decorator + +Decorators are essentially condition checks that gate nodes behind them. + +Basic use case: +Only execute combat branch if a target key is present. + +Example: + +![Behavior example](learn_ai_images/decorator_example.png) + +This gates the attack behavior behind the BB_CURRENT_TARGET_KEY being set. + +Outside of this, decorators serve a second important function, which is being able to observe the condition they are checking to see if its still valid while behavior is running in other nodes. There are two use cases for this: + +1. Stop performing behavior of my children because the conditions has become FALSE (e.g. I lost my target, stop attacking!) +2. Stop performing lower priority behavior, because the condition has become TRUE (e.g. Hey I found a target! stop idling!) + +You can also run observers for both of these cases at the same time. + +For most decorators, we can register for signals to observe when a potential condition change might have happend, for example in the case of the target, we register on the target key being changed somewhere. + +If for some reason we cannot have signals check the condition, you can also make it check every ai_controller process(), this is less efficient so use signals when possible. + +## What Composites Are + +Composites are structural nodes that control how child nodes run. + +- They never perform behavior themselves, and exist purely to determine how nodes below them are ran. +- The composite nodes are Selector, Sequence, Parallel, and Subplan + +## BT Node Types + +### 1) Selector + +What it does: +Tries children in order until one returns BT_SUCCEED. + +To be clear; this means until the first child returns BT_FAILURE, the second child will never start. + +Basic use case: +Try attacking, if you cant, perform idle behavior. + +![Behavior example](learn_ai_images/selector_example.png) + +In the above example, we try to run monkey combat behavior, if for some reason this doesn't succeed (usually due to lack of target), we run idle behavior. + +### 2) Sequence + +What it does: +Runs children in order and stops on first failure. Basically the opposite of selector. And allows for chaining behavior that needs to happen in sequence. + +Basic use case: +Move to a target, then perform work once in range. + +If you fail to moving to the target, then performing work would make no sense. So a sequence is best here. + +![Behavior example](learn_ai_images/sequence_example.png) + +In the above example, we move to a target, and then give them our currently held item. If we fail to move to them, we also dont try to give the item + +### 3) Parallel + +What it does: +Runs multiple children in parallel. + +This also has the option to loop the non-primary nodes (e.g. the nodes 2nd and up). This allows for behavior such as continiously checking whether we can find a target. + +You also have the option to stop any secondary behavior once the primary behavior is finished. This is useful in examples where you do not wish to wait until the secondary behavior finishes. + +Basic use case: +Finding targets while also performing all other behavior -And one of those behaviors, `basic_melee_attack`. As I have been doing so far, I've dumped in a bunch of comments explaining how this one behavior gets mobs to chase a target and slap it if in range. - -```dm -///this behavior makes an AI get close to their movement target, and attack every time perform() is called. -/datum/ai_behavior/basic_melee_attack - action_cooldown = 0.6 SECONDS - //flag tells the AI it needs to have a movement target to work, and since it doesn't have "AI_BEHAVIOR_MOVE_AND_PERFORM", it won't call perform() every 0.6 seconds until it is in melee range. Smart! - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - -/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - //all this is doing in setup is setting the movement target. setup is called once when the behavior is first planned, and returning FALSE can cancel the behavior if something isn't right. - - //Hiding location is priority - var/datum/weakref/weak_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] - var/atom/target = weak_target?.resolve() - if(!target) - return FALSE - //now the AI_BEHAVIOR_REQUIRE_MOVEMENT flag will be happy, we have a target to always be moving towards. - controller.current_movement_target = target - -///perform will run every "action_cooldown" deciseconds as long as the conditions are good for it to do so (we set "AI_BEHAVIOR_REQUIRE_MOVEMENT", so it won't perform until in range). -/datum/ai_behavior/basic_melee_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - var/mob/living/basic/basic_mob = controller.pawn - //targeting strategy will kill the action if not real anymore - var/datum/weakref/weak_target = controller.blackboard[target_key] - var/atom/target = weak_target?.resolve() - var/datum/targeting_strategy/targeting_strategy = controller.blackboard[targeting_strategy_key] - - if(!targeting_strategy.can_attack(basic_mob, target)) - ///We have a target that is no longer valid to attack. Remember that returning doesn't end the behavior, JUST this single performance. So we call "finish_action" with whether it succeeded in doing what it wanted to do (it didn't, so FALSE) and the blackboard keys passed into this behavior. - finish_action(controller, FALSE, target_key) - return //don't forget to end the performance too - - var/hiding_target = targeting_strategy.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! - - controller.blackboard[hiding_location_key] = hiding_target - - ///and finally, we're in range, we have a valid target, we can attack. When they fall into crit, they will no longer be a valid target, to the melee behavior will end. - if(hiding_target) //Slap it! - basic_mob.melee_attack(hiding_target) - else - basic_mob.melee_attack(target) - -///and so the action has ended. we can now clean up the AI's blackboard based on the success of the action, and the keys passed in. -/datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - ///if the behavior failed, the target is no longer valid, so we should lose aggro of them. We remove the target_key (which could be anything, it's whatever key was passed into the behavior by the subtree) from the blackboard. Couldn't do THAT with normal variables! - if(!succeeded) - controller.blackboard -= target_key +![Behavior example](learn_ai_images/parallel_example.png) + +Parallels also have a repeat_secondary_delay, which is a cooldown between the looping of secondary children. Ideal to cap how often certain behaviors fire (e.g. idle behavior) + +### 4) Subplan + +What it does: +Runs a child branch with a loop policy behavior. + +This essentially allows us to prevent the child from returning its return value to its parent, and instead try to loop. + +This is useful for things like combat and prevents redundant replanning of the entire plan. You can basically do "Hey, if you fail to hit someone this time, try again next time until you succeed". + +If doing this, its important to make use of observers on decorators to make sure you can exit the subplan, else you can get stuck in an endless cycle of behavior. + +Basic use case: +In combat, keep retrying attack logic instead of ending after one attempt. + +![Behavior example](learn_ai_images/subplan_example.png) + +In the above example, we are running a combat behavior where we first check if our target is set, (and have an observer that cancels if the condition changes). + +Then, we run a parallel; on the left side (primary) we have a subplan that runs a looping attack behavior; if this attack behavior fails (We're not close or some other issue), the sub-plan will just try again next tick. + +In the secondary branch, we try to move to the target. (and keep trying this as well, due to the parallels looping rule) + +Due to us having the observer, if for some reason our target changes due to being changed by another node, the decorators observer will re-evaluate and cancel the plan. Without an observer, this behavior would be stuck in an endless loop. + +Subplans also expose `loop_delay`, which when set causes a delay between each loop of the behavior, essentially a cooldown. It can be useful to gate things like idle behavior without triggering re-planning + +## Quick Node Selection Guide + +- Use leaf when you want to do one concrete action. +- Use selector when branches are alternatives. +- Use sequence when steps are ordered dependencies. +- Use parallel when you need concurrent actions +- Use decorator when a branch should only run behind a condition or should react to a condition changing. +- Use subplan when a child branch should loop on succes and/or failure. + +## Subtrees + +Subtrees are essentially modularized pieces of tree that can be re-used in different trees. This allows for patternizing common behaviors into a re-useable tree. + +One feature of subtrees is that if you are making a subtree, and want to change something when using the subtree depending on the AI, you can assign any field in the subtree as a binding; this makes it editable in any controller (or subtree) that the subtree is used. + +You can also re-assign subtrees by setting an "Override ID". By doing this you can call `controller.set_behavior_tree_override()` with the same ID to change what subtree is used in this node. Used in for example pet commands to tell a dog to go fetch dynamically. + +## Editor + +The behavior tree editor can be opened via opening one of our .bt.json files. These .bt.json files are specialized json files that format our (sub)trees. In theory these jsons are human-readable, but the editor makes it much easier to parse them. The editor only edits the .bt.json file itself. + +When opening the editor, you should press "Refresh Types" at the top, this will make the editor parse through all the relevant .dm files to find defined behaviors, decorators, subtrees and ai controllers. You should also run this if you modify or add new nodes, else your cache will be out of date. + +Now, you should be able to see the tree in front of you. In this view you can re-order nodes, add new ones, and change parameters on individual nodes. + +On the left, you can find a palette of all the nodes you have. The compsoite nodes are found in the top left, while the rest are distributed across 3 browsers: + +1. Behavior types (Leaf / ai_behavior nodes) +2. Decorator types (Decorator nodes) +3. Browser (All ai_controllers and subtrees) + +![Behavior example](learn_ai_images/bt_editor_example1.png) + +You can drag these nodes into the node-graph to place them. + +You can also change the nodes connections by dragging from either end to another node. This system is currently a bit fidgetty while I figure out how to become a better editor programmer. + +Once you are done with your tree, be sure to save the file. You can now compile and the JSON will be converted automatically + +## Targeting + +A huge amount of AI work boils down to finding a thing nearby and remembering it. Finding an enemy, finding food, finding a beacon to walk to. Instead of writing a brand new "find" leaf every time, we have one generic leaf that you configure with two helpers. You almost never need to write a new leaf for this. + +The three pieces: + +1. **The leaf** is `update_interaction_target` or `update_targets`. Its job is to look around, find something, and write it to a blackboard key. +2. **A target source** answers the question "what candidates exist?". It gathers a list of nearby things, such as everything in view, only things of a certain type, or items in your hands. +3. **A targeting strategy** answers the question "is this specific candidate valid?". It looks at one candidate at a time and says yes or no. + +So the flow is: the **source** hands the leaf a list of candidates, the leaf runs each one past the **strategy**, and the first one that passes gets written to your target key. + +You configure all of this on the leaf node. + +```text +LEAF: update_interaction_target + target_key: BB_TARGET_FOOD (where to store what we found) + target_source: .../held_items_then_oview/basic_foods (what to look at) + targeting_strategy: .../anything (how to decide it's valid) + vision_range: 7 (how far to look, optional) ``` + +A few pitfalls: + +- **Finding nothing is just a FAILURE.** If the source returns an empty list, the leaf simply fails. +- **Use `/datum/targeting_strategy/anything` if you don't filter for specifics.** With this strategy the only thing we check is `get_dist`, which is enough when the source already gives you the specific candidates you want. +- **Reuse before you build.** There are many existing sources and strategies. Use these before adding new ones. + +Once the target key is set, the rest of your tree reacts to it the way you've already seen. A `decorator` gates the combat branch behind "is the target key set?". An observer on this decorator can cancel lower priority behavior when the target is set, or cancel its own behavior when the target is lost. + +> Note: combat target _searching_ during a fight is usually done by the `update_targets` leaf, which keeps `BB_CURRENT_TARGET` refreshed while you fight. `update_interaction_target` is the general-purpose "go find a thing" leaf for everything else. diff --git a/code/datums/ai/learn_ai_images/behavior_example.png b/code/datums/ai/learn_ai_images/behavior_example.png new file mode 100644 index 0000000000000000000000000000000000000000..4ab176cd651bb9eb27dc367d04343e2e924f2005 GIT binary patch literal 24461 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

o3Zvu(2%r8 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 literal 0 HcmV?d00001 diff --git a/code/datums/ai/learn_ai_images/decorator_example.png b/code/datums/ai/learn_ai_images/decorator_example.png new file mode 100644 index 0000000000000000000000000000000000000000..ef940acb08d170e0fcdcd8bb2f3b3b4603a71a88 GIT binary patch literal 27708 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 literal 0 HcmV?d00001 diff --git a/code/datums/ai/learn_ai_images/selector_example.png b/code/datums/ai/learn_ai_images/selector_example.png new file mode 100644 index 0000000000000000000000000000000000000000..f19e8f57a1073f0d99e9a4e950e002e14d6c2969 GIT binary patch literal 11099 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 literal 0 HcmV?d00001 diff --git a/code/datums/ai/learn_ai_images/sequence_example.png b/code/datums/ai/learn_ai_images/sequence_example.png new file mode 100644 index 0000000000000000000000000000000000000000..9eaca19c109a0c61714c584818df5039d7112e70 GIT binary patch literal 14203 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 literal 0 HcmV?d00001 diff --git a/code/datums/ai/learn_ai_images/subplan_example.png b/code/datums/ai/learn_ai_images/subplan_example.png new file mode 100644 index 0000000000000000000000000000000000000000..6e5b6ae13772fc39d253c2821e94bedb555b0e28 GIT binary patch literal 42695 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

Ry)p%T;LTPonbFpAhJn8tP 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 I$-fHzA1*1=3IG5A literal 0 HcmV?d00001 diff --git a/code/datums/ai/monkey/monkey.bt.json b/code/datums/ai/monkey/monkey.bt.json new file mode 100644 index 000000000000..0bf3ac811e6e --- /dev/null +++ b/code/datums/ai/monkey/monkey.bt.json @@ -0,0 +1,245 @@ +{ + "dm_type": "/datum/ai_controller/monkey", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/monkey_combat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/monkey_serve_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_hunger" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_play_instrument" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/monkey_shenanigans" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_idle" + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "invert": true + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_set_combat_target", + "vars": { + "attack_target_key": "BB_CURRENT_TARGET", + "enemies_key": "BB_monkey_enemies" + } + }, + { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_MONKEY_WANNA_PRESS_SOME_SHIT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_monkey_current_press_target", + "targeting_strategy": "/datum/targeting_strategy/anything", + "target_source": "/datum/target_source/monkey_press_target", + "vision_range": 2 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_MONKEY_WANNA_PRESS_SOME_SHIT" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.2 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_MONKEY_WANNA_PRESS_SOME_SHIT", + "value": true + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_holding_item", + "vars": { + "key": "BB_MY_PAWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_monkey_current_give_target", + "targeting_strategy": "/datum/targeting_strategy/anything", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "vision_range": 2 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_holding_item", + "vars": { + "invert": true, + "key": "BB_MY_PAWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_monkey_current_give_target" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_holding_target", + "vars": { + "key": "BB_SONG_INSTRUMENT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_tamed" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SONG_INSTRUMENT", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/held_items_typed/instrument" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_nutrition_below", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_NEXT_HUNGRY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "bb_food_target", + "targeting_strategy": "/datum/targeting_strategy/pickup_item/food_or_drink", + "target_source": "/datum/target_source/held_items_then_oview", + "vision_range": 2 + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_monkey_tamed" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon", + "vars": { + "target_key": "BB_monkey_pickuptarget", + "target_source": "/datum/target_source/monkey_weapon_upgrade", + "targeting_strategy": "/datum/targeting_strategy/monkey_weapon_upgrade", + "vision_range": 5 + } + } + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon", + "vars": { + "target_key": "BB_monkey_pickuptarget", + "target_source": "/datum/target_source/monkey_weapon_upgrade", + "targeting_strategy": "/datum/targeting_strategy/monkey_weapon_upgrade", + "vision_range": 5 + } + } + ] +} diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm deleted file mode 100644 index 865f6a069eaa..000000000000 --- a/code/datums/ai/monkey/monkey_behaviors.dm +++ /dev/null @@ -1,305 +0,0 @@ -/datum/ai_behavior/battle_screech/monkey - screeches = list("roar","screech") - -/datum/ai_behavior/monkey_equip - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/monkey_equip/setup(datum/ai_controller/controller, target_key) - . = ..() - var/obj/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/monkey_equip/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - - if(!success) //Don't try again on this item if we failed - controller.set_blackboard_key_assoc(BB_MONKEY_BLACKLISTITEMS, controller.blackboard[target_key], TRUE) - - controller.clear_blackboard_key(BB_MONKEY_PICKUPTARGET) - -/// Equips an item on the monkey -/// Returns TRUE if it works out, FALSE otherwise -/datum/ai_behavior/monkey_equip/proc/equip_item(datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - - var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET] - var/best_force = controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND] - if(!isturf(living_pawn.loc)) - return FALSE - - if(!target) - return FALSE - - if(target.anchored) //Can't pick it up, so stop trying. - return FALSE - - // Strong weapon - else if(target.force > best_force) - living_pawn.drop_all_held_items() - living_pawn.put_in_hands(target) - controller.set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, target.force) - return TRUE - - else if(target.slot_flags) //Clothing == top priority - living_pawn.dropItemToGround(target, TRUE) - living_pawn.update_icons() - if(!living_pawn.equip_to_appropriate_slot(target)) - return FALSE //Already wearing something, in the future this should probably replace the current item but the code didn't actually do that, and I dont want to support it right now. - return TRUE - - // EVERYTHING ELSE - else if(living_pawn.get_empty_held_indexes()) - living_pawn.put_in_hands(target) - return TRUE - - return FALSE - -/datum/ai_behavior/monkey_equip/ground - -/datum/ai_behavior/monkey_equip/ground/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - . = ..() - if(equip_item(controller)) - return . | AI_BEHAVIOR_SUCCEEDED - return . | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/monkey_equip/pickpocket - -/datum/ai_behavior/monkey_equip/pickpocket/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - . = ..() - if(controller.blackboard[BB_MONKEY_PICKPOCKETING]) //We are pickpocketing, don't do ANYTHING!!!! - return - INVOKE_ASYNC(src, PROC_REF(attempt_pickpocket), controller) - -/datum/ai_behavior/monkey_equip/pickpocket/proc/attempt_pickpocket(datum/ai_controller/controller) - var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET] - var/mob/living/victim = target.loc - var/mob/living/living_pawn = controller.pawn - - if(!istype(victim)) - finish_action(controller, FALSE) - return - - victim.visible_message(span_warning("[living_pawn] starts trying to take [target] from [victim]!"), span_danger("[living_pawn] tries to take [target]!")) - - controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, TRUE) - - var/success = FALSE - - if(do_after(living_pawn, MONKEY_ITEM_SNATCH_DELAY, victim) && target && victim.IsReachableBy(living_pawn)) - - for(var/obj/item/I in victim.held_items) - if(I == target) - victim.visible_message(span_danger("[living_pawn] snatches [target] from [victim]."), span_userdanger("[living_pawn] snatched [target]!")) - if(victim.temporarilyRemoveItemFromInventory(target)) - if(!QDELETED(target) && !equip_item(controller)) - target.forceMove(living_pawn.drop_location()) - success = TRUE - break - else - victim.visible_message(span_danger("[living_pawn] tried to snatch [target] from [victim], but failed!"), span_userdanger("[living_pawn] tried to grab [target]!")) - - finish_action(controller, success) //We either fucked up or got the item. - -/datum/ai_behavior/monkey_equip/pickpocket/finish_action(datum/ai_controller/controller, success) - . = ..() - controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, FALSE) - controller.clear_blackboard_key(BB_MONKEY_PICKUPTARGET) - -/datum/ai_behavior/monkey_flee - -/datum/ai_behavior/monkey_flee/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - - if(living_pawn.health >= MONKEY_FLEE_HEALTH) //we're back in bussiness - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - var/mob/living/target = null - - // flee from anyone who attacked us and we didn't beat down - for(var/mob/living/L in view(living_pawn, MONKEY_FLEE_VISION)) - if(controller.blackboard[BB_MONKEY_ENEMIES][L] && L.stat == CONSCIOUS) - target = L - break - - if(target) - GLOB.move_manager.move_away(living_pawn, target, max_dist=MONKEY_ENEMY_VISION, delay=5) - return AI_BEHAVIOR_DELAY - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/monkey_attack_mob - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION //performs to increase frustration - -/datum/ai_behavior/monkey_attack_mob/setup(datum/ai_controller/controller, target_key) - . = ..() - set_movement_target(controller, controller.blackboard[target_key]) - -/datum/ai_behavior/monkey_attack_mob/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/target = controller.blackboard[target_key] - var/mob/living/living_pawn = controller.pawn - var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[BB_TARGETING_STRATEGY]) - - if(QDELETED(target) || !strategy.can_attack(living_pawn, target)) //Target == owned - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - // check if target has a weapon - var/holding_weapon - for(var/obj/item/potential_weapon in target.held_items) - if(!(potential_weapon.item_flags & ABSTRACT)) - holding_weapon = potential_weapon - break - - var/attack_results = monkey_attack(controller, target, seconds_per_tick, holding_weapon && SPT_PROB(MONKEY_ATTACK_DISARM_PROB, seconds_per_tick), holding_weapon) - - if(!attack_results || controller.blackboard[BB_MONKEY_AGGRESSIVE]) - return AI_BEHAVIOR_DELAY - - //check if we can de-aggro on the enemy... - var/hatred_value = controller.blackboard[BB_MONKEY_ENEMIES][target] - - if(isnull(hatred_value)) - hatred_value = 1 - controller.set_blackboard_key_assoc(BB_MONKEY_ENEMIES, target, hatred_value) - - if(!SPT_PROB(MONKEY_HATRED_REDUCTION_PROB, seconds_per_tick)) - return AI_BEHAVIOR_DELAY - - //we decrease our hatred value to them by 1 - hatred_value-- - if(hatred_value <= 0) - controller.remove_thing_from_blackboard_key(BB_MONKEY_ENEMIES, target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - controller.set_blackboard_key_assoc(BB_MONKEY_ENEMIES, target, hatred_value) - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/monkey_attack_mob/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/// attack using a held weapon otherwise bite the enemy, then if we are angry there is a chance we might calm down a little -/datum/ai_behavior/monkey_attack_mob/proc/monkey_attack(datum/ai_controller/controller, mob/living/target, seconds_per_tick, disarm, holding_weapon) - var/mob/living/living_pawn = controller.pawn - - if(living_pawn.next_move > world.time) - return FALSE - - //are we holding a gun? can we shoot it? if so, FIRE - var/obj/item/gun/gun_to_shoot = locate() in living_pawn.held_items - if(gun_to_shoot?.can_shoot()) - if(gun_to_shoot != living_pawn.get_active_held_item()) - living_pawn.swap_hand(living_pawn.get_inactive_hand_index()) - controller.ai_interact(target = target, combat_mode = TRUE) - return TRUE - - //look for any potential weapons we're holding - var/obj/item/potential_weapon = locate() in living_pawn.held_items - if(!target.IsReachableBy(living_pawn, potential_weapon?.reach)) - return FALSE - - if(isnull(potential_weapon)) - controller.ai_interact(target = target, modifiers = disarm ? list(RIGHT_CLICK = TRUE) : null, combat_mode = TRUE) - if(disarm && !isnull(holding_weapon) && controller.blackboard[BB_MONKEY_BLACKLISTITEMS][holding_weapon]) - controller.remove_thing_from_blackboard_key(BB_MONKEY_BLACKLISTITEMS, holding_weapon) //lets try to pickpocket it again! - return TRUE - - if(potential_weapon != living_pawn.get_active_held_item()) - living_pawn.swap_hand(living_pawn.get_inactive_hand_index()) - controller.ai_interact(target = target, combat_mode = TRUE) - return TRUE - -/datum/ai_behavior/disposal_mob - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM //performs to increase frustration - -/datum/ai_behavior/disposal_mob/setup(datum/ai_controller/controller, attack_target_key, disposal_target_key) - . = ..() - set_movement_target(controller, controller.blackboard[attack_target_key]) - -/datum/ai_behavior/disposal_mob/finish_action(datum/ai_controller/controller, succeeded, attack_target_key, disposal_target_key) - . = ..() - controller.clear_blackboard_key(attack_target_key) //Reset attack target - controller.set_blackboard_key(BB_MONKEY_DISPOSING, FALSE) //No longer disposing - controller.clear_blackboard_key(disposal_target_key) //No target disposal - -/datum/ai_behavior/disposal_mob/perform(seconds_per_tick, datum/ai_controller/controller, attack_target_key, disposal_target_key) - if(controller.blackboard[BB_MONKEY_DISPOSING]) //We are disposing, don't do ANYTHING!!!! - return AI_BEHAVIOR_DELAY - - var/mob/living/target = controller.blackboard[attack_target_key] - var/mob/living/living_pawn = controller.pawn - - set_movement_target(controller, target) - - if(!target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(target.pulledby, /datum/ai_controller/monkey)) //Dont steal from my fellow monkeys. - if(living_pawn.Adjacent(target) && isturf(target.loc)) - target.grabbedby(living_pawn) - return AI_BEHAVIOR_DELAY //Do the rest next turn - - var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key] - set_movement_target(controller, disposal) - - if(!disposal) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(living_pawn.Adjacent(disposal)) - INVOKE_ASYNC(src, PROC_REF(try_disposal_mob), controller, attack_target_key, disposal_target_key) //put him in! - return AI_BEHAVIOR_DELAY - //This means we might be getting pissed! - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/disposal_mob/proc/try_disposal_mob(datum/ai_controller/controller, attack_target_key, disposal_target_key) - var/mob/living/living_pawn = controller.pawn - var/mob/living/target = controller.blackboard[attack_target_key] - var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key] - - controller.set_blackboard_key(BB_MONKEY_DISPOSING, TRUE) - - if(target && disposal?.stuff_mob_in(target, living_pawn)) - disposal.flush() - finish_action(controller, TRUE, attack_target_key, disposal_target_key) - - -/datum/ai_behavior/recruit_monkeys/perform(seconds_per_tick, datum/ai_controller/controller) - controller.set_blackboard_key(BB_MONKEY_RECRUIT_COOLDOWN, world.time + MONKEY_RECRUIT_COOLDOWN) - var/mob/living/living_pawn = controller.pawn - - for(var/mob/living/nearby_monkey in view(living_pawn, MONKEY_ENEMY_VISION)) - if(QDELETED(nearby_monkey) || !HAS_AI_CONTROLLER_TYPE(nearby_monkey, /datum/ai_controller/monkey)) - continue - if(!SPT_PROB(MONKEY_RECRUIT_PROB, seconds_per_tick)) - continue - // Recruited a monkey to our side - controller.set_blackboard_key(BB_MONKEY_RECRUIT_COOLDOWN, world.time + MONKEY_RECRUIT_COOLDOWN) - // Other monkeys now also hate the guy we're currently targeting - nearby_monkey.ai_controller.add_blackboard_key_assoc(BB_MONKEY_ENEMIES, controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET], MONKEY_RECRUIT_HATED_AMOUNT) - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/monkey_set_combat_target/perform(seconds_per_tick, datum/ai_controller/controller, set_key, enemies_key) - var/list/enemies = controller.blackboard[enemies_key] - var/list/valids = list() - for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, controller.pawn)) - if(possible_enemy == controller.pawn) - continue // don't target ourselves - if(!enemies[possible_enemy]) //We don't hate this creature! But we might still attack it! - if(!controller.blackboard[BB_MONKEY_AGGRESSIVE]) //We are not aggressive either, so we won't attack! - continue - if(possible_enemy.has_faction(list(FACTION_MONKEY, FACTION_JUNGLE)) && !controller.blackboard[BB_MONKEY_TARGET_MONKEYS]) // do not target your team. includes monkys gorillas etc. - continue - // Weighted list, so the closer they are the more likely they are to be chosen as the enemy - valids[possible_enemy] = CEILING(100 / (get_dist(controller.pawn, possible_enemy) || 1), 1) - - if(!length(valids)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/mob/living/target = pick_weight(valids) - - EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [target] as a target for blackboard key [set_key]! Behavior: [src]", get_turf(target), "Target: [target]") - EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(target)) - - controller.set_blackboard_key(set_key, target) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/monkey/monkey_bt_nodes.dm b/code/datums/ai/monkey/monkey_bt_nodes.dm new file mode 100644 index 000000000000..44b898a7b5b3 --- /dev/null +++ b/code/datums/ai/monkey/monkey_bt_nodes.dm @@ -0,0 +1,402 @@ + +/// Monkey's battle screech variant louder and on a 5 second cooldown +/datum/bt_node/ai_behavior/battle_screech/monkey + time_between_perform = 5 SECONDS + screeches = list("roar", "screech") + + +/// Base equip behavior; handles blacklist updates and key cleanup on finish +/datum/bt_node/ai_behavior/monkey_equip + var/target_key + +/datum/bt_node/ai_behavior/monkey_equip/finish_action(datum/ai_controller/controller, success) + . = ..() + if(!success) // Don't try to pick this item up again + controller.set_blackboard_key_assoc(BB_MONKEY_BLACKLISTITEMS, controller.blackboard[target_key], TRUE) + controller.clear_blackboard_key(BB_MONKEY_PICKUPTARGET) + controller.clear_blackboard_key(BB_MONKEY_PICKUP_IS_PICKPOCKET) + +/// Equip a weapon off the ground +/datum/bt_node/ai_behavior/monkey_equip/ground + +/datum/bt_node/ai_behavior/monkey_equip/ground/setup(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/monkey_equip/ground/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + return start_async() + +/datum/bt_node/ai_behavior/monkey_equip/ground/perform_async(datum/ai_controller/controller) + var/result = equip_item(controller) + if(!async_still_valid()) + return + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/// Pickpocket a weapon from a mob +/datum/bt_node/ai_behavior/monkey_equip/pickpocket + +/datum/bt_node/ai_behavior/monkey_equip/pickpocket/setup(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/monkey_equip/pickpocket/perform(seconds_per_tick, datum/ai_controller/controller) + if(controller.blackboard[BB_MONKEY_PICKPOCKETING]) // mid-snatch; wait + return AI_BEHAVIOR_DELAY + INVOKE_ASYNC(src, PROC_REF(attempt_pickpocket), controller) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/monkey_equip/pickpocket/proc/attempt_pickpocket(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET] + var/mob/living/victim = target?.loc + var/mob/living/living_pawn = controller.pawn + + if(!istype(victim)) + finish_action(controller, FALSE) + return + + victim.visible_message(span_warning("[living_pawn] starts trying to take [target] from [victim]!"), span_danger("[living_pawn] tries to take [target]!")) + controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, TRUE) + + var/success = FALSE + + if(do_after(living_pawn, MONKEY_ITEM_SNATCH_DELAY, victim) && target && victim.IsReachableBy(living_pawn)) + for(var/obj/item/I in victim.held_items) + if(I == target) + victim.visible_message(span_danger("[living_pawn] snatches [target] from [victim]."), span_userdanger("[living_pawn] snatched [target]!")) + if(victim.temporarilyRemoveItemFromInventory(target)) + if(!QDELETED(target) && !equip_item(controller)) + target.forceMove(living_pawn.drop_location()) + success = TRUE + break + else + victim.visible_message(span_danger("[living_pawn] tried to snatch [target] from [victim], but failed!"), span_userdanger("[living_pawn] tried to grab [target]!")) + + finish_action(controller, success) + +/datum/bt_node/ai_behavior/monkey_equip/pickpocket/finish_action(datum/ai_controller/controller, success) + . = ..() + controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, FALSE) + +/// Shared item equip proc +/datum/bt_node/ai_behavior/monkey_equip/proc/equip_item(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET] + var/best_force = controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND] + + if(!isturf(living_pawn.loc)) + return FALSE + if(!target) + return FALSE + if(target.anchored) + return FALSE + + if(target.force > best_force) // better weapon + living_pawn.drop_all_held_items() + living_pawn.put_in_hands(target) + controller.set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, target.force) + return TRUE + + if(target.slot_flags) // wearable + living_pawn.dropItemToGround(target, TRUE) + living_pawn.update_icons() + if(!living_pawn.equip_to_appropriate_slot(target)) + return FALSE + return TRUE + + if(living_pawn.get_empty_held_indexes()) // any free hand + living_pawn.put_in_hands(target) + return TRUE + + return FALSE + +/// Gathers weapon upgrade candidates: nearby ground items, then held items of nearby humans. +/datum/target_source/monkey_weapon_upgrade + +/datum/target_source/monkey_weapon_upgrade/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = list() + for(var/obj/item/ground_item in oview(range, pawn)) + candidates += ground_item + for(var/mob/living/carbon/human/nearby_human in oview(range, pawn)) + candidates += nearby_human.held_items + return candidates + +/// Weapon upgrade candidate: not two-handed, not blacklisted, and hits harder than our bite. +/datum/targeting_strategy/monkey_weapon_upgrade + +/datum/targeting_strategy/monkey_weapon_upgrade/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/item/candidate = target + if(HAS_TRAIT(candidate, TRAIT_NEEDS_TWO_HANDS) || controller.blackboard[BB_MONKEY_BLACKLISTITEMS][candidate]) + return FALSE + if(candidate.force < 2) // our bite already does ~2 damage + return FALSE + return TRUE + +/// Scans nearby items and mobs for a better weapon and sets BB_MONKEY_PICKUPTARGET +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon + +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon/can_search(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!(locate(/obj/item) in living_pawn.held_items)) + controller.set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, 0) + if(controller.blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED] && (locate(/obj/item/gun) in living_pawn.held_items)) + return FALSE // already have a gun + return ..() + +/// Prefers any gun once gun neurons are activated, else the strongest candidate that beats our current best held item. +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/mob/living/living_pawn = controller.pawn + + if(controller.blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED]) + for(var/obj/item/candidate as anything in filtered_targets) + if(isgun(candidate)) + return candidate + + var/top_force = 0 + for(var/obj/item/held in living_pawn.held_items) + if(HAS_TRAIT(held, TRAIT_NEEDS_TWO_HANDS) || controller.blackboard[BB_MONKEY_BLACKLISTITEMS][held]) + continue + top_force = max(top_force, held.force) + + var/obj/item/best + for(var/obj/item/candidate as anything in filtered_targets) + if(candidate.force <= top_force) + continue + best = candidate + top_force = candidate.force + return best + +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + controller.set_blackboard_key(BB_MONKEY_PICKUP_IS_PICKPOCKET, ismob(target.loc) ? TRUE : null) + + +/// Selects a target from BB_MONKEY_ENEMIES or picks any visible mob if aggressive. This should be ported to new targetting but its so fkn bespoke +/datum/bt_node/ai_behavior/monkey_set_combat_target + var/attack_target_key + var/enemies_key + +/datum/bt_node/ai_behavior/monkey_set_combat_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/enemies = controller.blackboard[enemies_key] + + if(HAS_TRAIT(living_pawn, TRAIT_PACIFISM) || (!length(enemies) && !controller.blackboard[BB_MONKEY_AGGRESSIVE])) + living_pawn.set_combat_mode(FALSE) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/list/valids = list() + for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, living_pawn)) + if(possible_enemy == living_pawn) + continue + if(!enemies[possible_enemy]) + if(!controller.blackboard[BB_MONKEY_AGGRESSIVE]) + continue + if(possible_enemy.has_faction(list(FACTION_MONKEY, FACTION_JUNGLE)) && !controller.blackboard[BB_MONKEY_TARGET_MONKEYS]) + continue + if(IS_DEAD_OR_INCAP(possible_enemy)) // Dont bother, theyre fucked. + continue + valids[possible_enemy] = CEILING(100 / (get_dist(living_pawn, possible_enemy) || 1), 1) + + if(!length(valids)) + living_pawn.set_combat_mode(FALSE) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/mob/living/target = pick_weight(valids) + + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[living_pawn] has selected [target] as a target for blackboard key [attack_target_key]! Behavior: [src]", get_turf(target), "Target: [target]") + EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(living_pawn), get_turf(target)) + + living_pawn.set_combat_mode(TRUE) + controller.set_blackboard_key(attack_target_key, target) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +/// Attacks the target mob; SUCCEEDED when target is gone, FAILED when target goes down (Which lets us flush the fucker instead) +/datum/bt_node/ai_behavior/monkey_attack_mob + var/target_key + time_between_perform = CLICK_CD_MELEE + /// seconds_per_tick from the perform() that kicked off the current async attack. + VAR_PRIVATE/attack_seconds_per_tick = 0 + /// Weapon snapshot from the perform() that kicked off the current async attack. + VAR_PRIVATE/obj/item/attack_holding_weapon + +/datum/bt_node/ai_behavior/monkey_attack_mob/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/mob/living/target = controller.blackboard[target_key] + var/mob/living/living_pawn = controller.pawn + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[BB_TARGETING_STRATEGY]) + + if(QDELETED(target) || !strategy.is_valid_target(living_pawn, target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/obj/item/holding_weapon + for(var/obj/item/potential_weapon in target.held_items) + if(!(potential_weapon.item_flags & ABSTRACT)) + holding_weapon = potential_weapon + break + + attack_seconds_per_tick = seconds_per_tick + attack_holding_weapon = holding_weapon + return start_async() + +/datum/bt_node/ai_behavior/monkey_attack_mob/perform_async(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + var/seconds_per_tick = attack_seconds_per_tick + var/obj/item/holding_weapon = attack_holding_weapon + var/attack_results = monkey_attack(controller, target, seconds_per_tick, holding_weapon && SPT_PROB(MONKEY_ATTACK_DISARM_PROB, seconds_per_tick), holding_weapon) + if(!async_still_valid()) + return + var/succeeded = FALSE + if(attack_results && !controller.blackboard[BB_MONKEY_AGGRESSIVE]) + succeeded = TRUE + if(prob(MONKEY_HATRED_REDUCTION_PROB)) + var/hatred_value = controller.blackboard[BB_MONKEY_ENEMIES][target] - 1 + if(hatred_value <= 0) + controller.remove_thing_from_blackboard_key(BB_MONKEY_ENEMIES, target) + else + controller.set_blackboard_key_assoc(BB_MONKEY_ENEMIES, target, hatred_value) + finish_async(succeeded ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/datum/bt_node/ai_behavior/monkey_attack_mob/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + attack_seconds_per_tick = 0 + attack_holding_weapon = null + +/// Attack with held weapon or bite; try to disarm if target is holding something +/datum/bt_node/ai_behavior/monkey_attack_mob/proc/monkey_attack(datum/ai_controller/controller, mob/living/target, seconds_per_tick, disarm, holding_weapon) + var/mob/living/living_pawn = controller.pawn + + if(living_pawn.next_move > world.time) + return FALSE + + living_pawn.face_atom(target) + + var/obj/item/potential_weapon = locate(/obj/item) in living_pawn.held_items + + if(target.IsReachableBy(living_pawn, potential_weapon?.reach)) + if(isnull(potential_weapon)) + controller.ai_interact(target = target, modifiers = disarm ? list(RIGHT_CLICK = TRUE) : null, combat_mode = TRUE) + if(disarm && !isnull(holding_weapon) && controller.blackboard[BB_MONKEY_BLACKLISTITEMS][holding_weapon]) + controller.remove_thing_from_blackboard_key(BB_MONKEY_BLACKLISTITEMS, holding_weapon) + else + if(potential_weapon != living_pawn.get_active_held_item()) + living_pawn.swap_hand(living_pawn.get_inactive_hand_index()) + controller.ai_interact(target = target, combat_mode = TRUE) + controller.override_blackboard_key(BB_MONKEY_GUN_WORKED, TRUE) + return TRUE + + if(!potential_weapon) + return FALSE + + // target out of melee reach — try ranged or throw + var/atom/real_target = target + if(prob(10)) // artificial miss + real_target = pick(oview(2, target)) + + var/obj/item/gun/gun = locate(/obj/item/gun) in living_pawn.held_items + var/can_shoot = gun?.can_shoot() || FALSE + if(gun && controller.blackboard[BB_MONKEY_GUN_WORKED] && prob(95)) + if(gun != living_pawn.get_active_held_item()) + living_pawn.swap_hand(living_pawn.get_inactive_hand_index()) + controller.ai_interact(target = real_target, combat_mode = TRUE) + controller.override_blackboard_key(BB_MONKEY_GUN_WORKED, can_shoot ? TRUE : prob(80)) + if(can_shoot) + controller.override_blackboard_key(BB_MONKEY_GUN_NEURONS_ACTIVATED, TRUE) + else + living_pawn.throw_item(real_target) + controller.override_blackboard_key(BB_MONKEY_GUN_WORKED, TRUE) + return TRUE + +/// Rallies nearby monkeys against the current attack target +/datum/bt_node/ai_behavior/recruit_monkeys + var/target_key + +/datum/bt_node/ai_behavior/recruit_monkeys/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/mob/living/attack_target = controller.blackboard[target_key] + + for(var/mob/living/nearby_monkey in view(living_pawn, MONKEY_ENEMY_VISION)) + if(QDELETED(nearby_monkey) || !HAS_AI_CONTROLLER_TYPE(nearby_monkey, /datum/ai_controller/monkey)) + continue + if(prob(MONKEY_RECRUIT_PROB)) + continue + nearby_monkey.ai_controller.add_blackboard_key_assoc(BB_MONKEY_ENEMIES, attack_target, MONKEY_RECRUIT_HATED_AMOUNT) + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + +/// Scans nearby humans for patrons to serve; fails if bartender present or fewer than 1 patron found +/datum/bt_node/ai_behavior/monkey_find_patrons + var/patrons_key + var/give_target_key + +/datum/bt_node/ai_behavior/monkey_find_patrons/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/nearby_patrons = list() + + for(var/mob/living/carbon/human/human_mob in oview(5, living_pawn)) + if(istype(human_mob.mind?.assigned_role, /datum/job/bartender)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED // my boss is on duty! + if(human_mob.stat != CONSCIOUS || ismonkey(human_mob)) + continue + if(!human_mob.get_empty_held_indexes()) + continue + nearby_patrons += human_mob + + if(!length(nearby_patrons)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.override_blackboard_key(patrons_key, nearby_patrons) + controller.blackboard[give_target_key] ||= pick(nearby_patrons) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + +/// Gathers press targets: filtered to BB_MONKEY_PRESS_TYPEPATH's type if set, else any nearby obj. +/datum/target_source/monkey_press_target + +/datum/target_source/monkey_press_target/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/locate_path = controller.blackboard[BB_MONKEY_PRESS_TYPEPATH] + var/list/candidates = list() + for(var/obj/potential_candidate in oview(range, pawn)) + if(locate_path) + if(istype(potential_candidate, locate_path)) + candidates += potential_candidate + break //found a bell fuck everything else + candidates += potential_candidate + return candidates + +/// Idle wander/emote behavior. Reads emote lists from BB_MONKEY_IDLE_COMMON_EMOTES and BB_MONKEY_IDLE_RARE_EMOTES. +/datum/bt_node/ai_behavior/monkey_idle + +/datum/bt_node/ai_behavior/monkey_idle/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(SPT_PROB(25, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) + var/move_dir = pick(GLOB.alldirs) + living_pawn.Move(get_step(living_pawn, move_dir), move_dir) + else if(SPT_PROB(5, seconds_per_tick)) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(controller.blackboard[BB_MONKEY_IDLE_COMMON_EMOTES])) + else if(SPT_PROB(1, seconds_per_tick)) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(controller.blackboard[BB_MONKEY_IDLE_RARE_EMOTES])) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +///monkey trees + +/datum/bt_node/subtree/monkey_combat + behavior_tree_json = "code/datums/ai/monkey/monkey_combat.bt.json" + +/datum/bt_node/subtree/monkey_find_weapon + behavior_tree_json = "code/datums/ai/monkey/monkey_find_weapon.bt.json" + +/datum/bt_node/subtree/monkey_shenanigans + behavior_tree_json = "code/datums/ai/monkey/monkey_shenanigans.bt.json" + +/datum/bt_node/subtree/monkey_serve_food + behavior_tree_json = "code/datums/ai/monkey/monkey_serve_food.bt.json" diff --git a/code/datums/ai/monkey/monkey_combat.bt.json b/code/datums/ai/monkey/monkey_combat.bt.json new file mode 100644 index 000000000000..697210653e0f --- /dev/null +++ b/code/datums/ai/monkey/monkey_combat.bt.json @@ -0,0 +1,228 @@ +{ + "dm_type": "/datum/bt_node/subtree/monkey_combat", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "0.2 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": true, + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 10, + "require_reach": false + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_health_below", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "health_threshold": 40 + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/monkey_find_weapon" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/mob_stat_at_least", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_CURRENT_TARGET", + "min_stat": "SOFT_CRIT" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_monkey_target_disposal", + "target_source": "/datum/target_source/oview_single_type/disposal_unit", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/grab_target", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_target_disposal", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/stuff_in_disposal", + "vars": { + "attack_target_key": "BB_CURRENT_TARGET", + "disposal_target_key": "BB_monkey_target_disposal" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_attack_mob", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "5 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_monkey_recruit_cooldown", + "cooldown_duration": 10 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/recruit_monkeys", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + } + ] + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BATTLE_SCREECH_COOLDOWN", + "cooldown_duration": 5 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.25 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/battle_screech/monkey" + } + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon", + "vars": { + "time_between_perform": 0, + "target_key": "BB_monkey_pickuptarget", + "targeting_strategy": "/datum/targeting_strategy/monkey_weapon_upgrade", + "target_source": "/datum/target_source/monkey_weapon_upgrade", + "vision_range": 5 + } + } + ] + } +} diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 18bdb0be26b0..ede9341e88d7 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -7,13 +7,7 @@ have ways of interacting with a specific mob and control it. /datum/ai_controller/monkey ai_movement = /datum/ai_movement/basic_avoidance movement_delay = 0.4 SECONDS - planning_subtrees = list( - /datum/ai_planning_subtree/generic_resist, - /datum/ai_planning_subtree/monkey_combat, - /datum/ai_planning_subtree/generic_hunger, - /datum/ai_planning_subtree/generic_play_instrument, - /datum/ai_planning_subtree/monkey_shenanigans, - ) + behavior_tree_json = "code/datums/ai/monkey/monkey.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/monkey, BB_MONKEY_AGGRESSIVE = FALSE, @@ -23,35 +17,26 @@ have ways of interacting with a specific mob and control it. BB_MONKEY_PICKPOCKETING = FALSE, BB_MONKEY_DISPOSING = FALSE, BB_MONKEY_GUN_NEURONS_ACTIVATED = FALSE, + BB_MONKEY_GUN_WORKED = TRUE, BB_SONG_LINES = MONKEY_SONG, - BB_RESISTING = FALSE, BB_MONKEY_GIVE_CHANCE = 5, + BB_MONKEY_PICKUP_IS_PICKPOCKET = null, + BB_MONKEY_IDLE_COMMON_EMOTES = list("screech", "roar"), + BB_MONKEY_IDLE_RARE_EMOTES = list("scratch", "jump", "roll", "tail"), ) - idle_behavior = /datum/idle_behavior/idle_monkey /datum/targeting_strategy/basic/monkey + custom_faction_check = TRUE /datum/targeting_strategy/basic/monkey/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) // if they wronged us, all bets are off if(controller.blackboard[BB_MONKEY_ENEMIES][the_target]) return FALSE // target was forcibly set, all bets are off again - if(controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] == the_target) + if(controller.blackboard[BB_CURRENT_TARGET] == the_target) return FALSE return ..() -/datum/ai_controller/monkey/process(seconds_per_tick) - - var/mob/living/living_pawn = src.pawn - - if(!length(living_pawn.do_afters) && living_pawn.ai_controller.blackboard[BB_RESISTING]) - living_pawn.ai_controller.set_blackboard_key(BB_RESISTING, FALSE) - - if(living_pawn.ai_controller.blackboard[BB_RESISTING]) - return - - . = ..() - /datum/ai_controller/monkey/New(atom/new_pawn) var/static/list/control_examine = list( ORGAN_SLOT_EYES = span_monkey("%PRONOUN_They stare%PRONOUN_s around with wild, primal eyes."), @@ -59,18 +44,6 @@ have ways of interacting with a specific mob and control it. AddElement(/datum/element/ai_control_examine, control_examine) return ..() -/datum/ai_controller/monkey/pun_pun - movement_delay = 0.7 SECONDS //pun pun moves slower so the bartender can keep track of them - planning_subtrees = list( - /datum/ai_planning_subtree/generic_resist, - /datum/ai_planning_subtree/monkey_combat, - /datum/ai_planning_subtree/serve_food, - /datum/ai_planning_subtree/generic_hunger, - /datum/ai_planning_subtree/generic_play_instrument, - /datum/ai_planning_subtree/monkey_shenanigans, - ) - idle_behavior = /datum/idle_behavior/idle_monkey/pun_pun - /datum/ai_controller/monkey/pun_pun/TryPossessPawn(atom/new_pawn) . = ..() if(. & AI_CONTROLLER_INCOMPATIBLE) @@ -80,6 +53,8 @@ have ways of interacting with a specific mob and control it. set_blackboard_key(BB_MONKEY_TAMED, TRUE) set_blackboard_key(BB_MONKEY_GIVE_CHANCE, 30) set_blackboard_key(BB_MONKEY_PRESS_TYPEPATH, /obj/structure/desk_bell) + override_blackboard_key(BB_MONKEY_IDLE_COMMON_EMOTES, list("tunesing", "dance", "bow")) + override_blackboard_key(BB_MONKEY_IDLE_RARE_EMOTES, list("clear", "sign", "tail")) set_trip_mode(mode = FALSE) /datum/ai_controller/monkey/angry @@ -151,49 +126,6 @@ have ways of interacting with a specific mob and control it. var/obj/item/organ/brain/primate/monkeybrain = brain monkeybrain.tripping = mode -///re-used behavior pattern by monkeys for finding a weapon -/datum/ai_controller/monkey/proc/TryFindWeapon() - var/mob/living/living_pawn = pawn - - if(!(locate(/obj/item) in living_pawn.held_items)) - set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, 0) - - if(blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED] && (locate(/obj/item/gun) in living_pawn.held_items)) - // We have a gun, what could we possibly want? - return FALSE - - var/obj/item/weapon - var/list/nearby_items = list() - for(var/obj/item/item in oview(2, living_pawn)) - nearby_items += item - - for(var/obj/item/item in living_pawn.held_items) // If we've got some garbage in out hands that's going to stop us from effectively attacking, we should get rid of it. - if(item.force < 2) - living_pawn.dropItemToGround(item) - - weapon = GetBestWeapon(src, nearby_items, living_pawn.held_items) - - var/pickpocket = FALSE - for(var/mob/living/carbon/human/human in oview(5, living_pawn)) - var/obj/item/held_weapon = GetBestWeapon(src, human.held_items + weapon, living_pawn.held_items) - if(held_weapon == weapon) // It's just the same one, not a held one - continue - pickpocket = TRUE - weapon = held_weapon - - if(!weapon || (weapon in living_pawn.held_items)) - return FALSE - - if(weapon.force < 2) // our bite does 2 damage on average, no point in settling for anything less - return FALSE - - set_blackboard_key(BB_MONKEY_PICKUPTARGET, weapon) - if(pickpocket) - queue_behavior(/datum/ai_behavior/monkey_equip/pickpocket, BB_MONKEY_PICKUPTARGET) - else - queue_behavior(/datum/ai_behavior/monkey_equip/ground, BB_MONKEY_PICKUPTARGET) - return TRUE - ///Reactive events to being hit /datum/ai_controller/monkey/proc/retaliate(mob/living/living_mob) // just to be safe diff --git a/code/datums/ai/monkey/monkey_find_weapon.bt.json b/code/datums/ai/monkey/monkey_find_weapon.bt.json new file mode 100644 index 000000000000..4d8970916fed --- /dev/null +++ b/code/datums/ai/monkey/monkey_find_weapon.bt.json @@ -0,0 +1,50 @@ +{ + "dm_type": "/datum/bt_node/subtree/monkey_find_weapon", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_monkey_pickuptarget" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_pickuptarget", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_pickup_is_pickpocket" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_equip/pickpocket", + "vars": { + "target_key": "BB_monkey_pickuptarget" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_equip/ground", + "vars": { + "target_key": "BB_monkey_pickuptarget" + } + } + ] + } + ] + } +} diff --git a/code/datums/ai/monkey/monkey_serve_food.bt.json b/code/datums/ai/monkey/monkey_serve_food.bt.json new file mode 100644 index 000000000000..4706b73c4bea --- /dev/null +++ b/code/datums/ai/monkey/monkey_serve_food.bt.json @@ -0,0 +1,132 @@ +{ + "dm_type": "/datum/bt_node/subtree/monkey_serve_food", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_tamed" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_MONKEY_PATRON_FIND_COOLDOWN", + "cooldown_duration": "1 SECONDS", + "lock_on_succeed": false + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_find_patrons", + "vars": { + "patrons_key": "BB_monkey_patrons_nearby", + "give_target_key": "BB_monkey_current_give_target" + } + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_holding_target", + "vars": { + "key": "BB_monkey_current_served_item" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_current_give_target", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/give", + "vars": { + "target_key": "BB_monkey_current_give_target" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_monkey_current_served_item", + "targeting_strategy": "/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks", + "target_source": "/datum/target_source/held_items_then_oview", + "vision_range": 2, + "must_be_reachable": true + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": false, + "key": "BB_monkey_current_served_item" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_current_served_item", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up", + "vars": { + "target_key": "BB_monkey_current_served_item", + "drop_held": true + } + } + ] + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_monkey_current_served_item", + "targeting_strategy": "/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks", + "target_source": "/datum/target_source/held_items_then_oview", + "vision_range": 2, + "must_be_reachable": true + } + } + ] + } + ] + } + ] + } +} diff --git a/code/datums/ai/monkey/monkey_shenanigans.bt.json b/code/datums/ai/monkey/monkey_shenanigans.bt.json new file mode 100644 index 000000000000..b4c7ea92e395 --- /dev/null +++ b/code/datums/ai/monkey/monkey_shenanigans.bt.json @@ -0,0 +1,114 @@ +{ + "dm_type": "/datum/bt_node/subtree/monkey_shenanigans", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_holding_item", + "vars": { + "key": "BB_MY_PAWN" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_in_hand" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_current_press_target" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_current_press_target", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_on_object", + "vars": { + "target_key": "BB_monkey_current_press_target" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_monkey_current_press_target" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_MONKEY_WANNA_PRESS_SOME_SHIT" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_current_give_target" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance_from_key", + "vars": { + "chance_key": "BB_monkey_give_chance" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_current_give_target", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/give", + "vars": { + "target_key": "BB_monkey_current_give_target" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_tamed" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/monkey_find_weapon" + } + } + ] +} diff --git a/code/datums/ai/monkey/monkey_subtrees.dm b/code/datums/ai/monkey/monkey_subtrees.dm index dc8793ffdfed..8b137891791f 100644 --- a/code/datums/ai/monkey/monkey_subtrees.dm +++ b/code/datums/ai/monkey/monkey_subtrees.dm @@ -1,112 +1 @@ -/datum/ai_planning_subtree/monkey_shenanigans/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) - if(prob(5)) - controller.queue_behavior(/datum/ai_behavior/use_in_hand) - - if(!SPT_PROB(MONKEY_SHENANIGAN_PROB, seconds_per_tick)) - return - - if(!controller.blackboard[BB_MONKEY_CURRENT_PRESS_TARGET]) - if(controller.blackboard[BB_MONKEY_PRESS_TYPEPATH]) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_MONKEY_CURRENT_PRESS_TARGET, controller.blackboard[BB_MONKEY_PRESS_TYPEPATH], 2) - else - controller.queue_behavior(/datum/ai_behavior/find_nearby, BB_MONKEY_CURRENT_PRESS_TARGET) - else if(prob(50)) - controller.queue_behavior(/datum/ai_behavior/use_on_object, BB_MONKEY_CURRENT_PRESS_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!controller.blackboard[BB_MONKEY_CURRENT_GIVE_TARGET]) - controller.queue_behavior(/datum/ai_behavior/find_and_set/pawn_must_hold_item, BB_MONKEY_CURRENT_GIVE_TARGET, /mob/living/carbon/human, 2) - else if(prob(controller.blackboard[BB_MONKEY_GIVE_CHANCE])) - controller.queue_behavior(/datum/ai_behavior/give, BB_MONKEY_CURRENT_GIVE_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!controller.blackboard[BB_MONKEY_TAMED]) - controller.TryFindWeapon() - -///monkey combat subtree. -/datum/ai_planning_subtree/monkey_combat/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/list/enemies = controller.blackboard[BB_MONKEY_ENEMIES] - - if((HAS_TRAIT(controller.pawn, TRAIT_PACIFISM)) || (!length(enemies) && !controller.blackboard[BB_MONKEY_AGGRESSIVE])) //Pacifist, or we have no enemies and we're not pissed - living_pawn.set_combat_mode(FALSE) - return - - if(!controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]) - controller.queue_behavior(/datum/ai_behavior/monkey_set_combat_target, BB_MONKEY_CURRENT_ATTACK_TARGET, BB_MONKEY_ENEMIES) - living_pawn.set_combat_mode(FALSE) - return SUBTREE_RETURN_FINISH_PLANNING - - var/mob/living/selected_enemy = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] - - if(QDELETED(selected_enemy)) - living_pawn.set_combat_mode(FALSE) - return - - if(!selected_enemy.stat) //He's up, get him! - if(living_pawn.health < MONKEY_FLEE_HEALTH) //Time to skeddadle - controller.queue_behavior(/datum/ai_behavior/monkey_flee) - return SUBTREE_RETURN_FINISH_PLANNING //I'm running fuck you guys - - if(controller.TryFindWeapon()) //Getting a weapon is higher priority if im not fleeing. - return SUBTREE_RETURN_FINISH_PLANNING - - if(controller.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] < world.time) - controller.queue_behavior(/datum/ai_behavior/recruit_monkeys, BB_MONKEY_CURRENT_ATTACK_TARGET) - return - - if(SPT_PROB(ismonkey(living_pawn) ? 25 : 10, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/battle_screech/monkey) - controller.queue_behavior(/datum/ai_behavior/monkey_attack_mob, BB_MONKEY_CURRENT_ATTACK_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - //by this point we have a target but they're down, let's try dumpstering this loser - - living_pawn.set_combat_mode(FALSE) - - if(!controller.blackboard[BB_MONKEY_TARGET_DISPOSAL]) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_MONKEY_TARGET_DISPOSAL, /obj/machinery/disposal, MONKEY_ENEMY_VISION) - return - - controller.queue_behavior(/datum/ai_behavior/disposal_mob, BB_MONKEY_CURRENT_ATTACK_TARGET, BB_MONKEY_TARGET_DISPOSAL) - return SUBTREE_RETURN_FINISH_PLANNING - -/// Finds food or drinks, picks them up, then gives them to nearby humans -/datum/ai_planning_subtree/serve_food - -/datum/ai_planning_subtree/serve_food/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/list/nearby_patrons = list() - for(var/mob/living/carbon/human/human_mob in oview(5, living_pawn)) - if(istype(human_mob.mind?.assigned_role, /datum/job/bartender)) - return // my boss is on duty! - if(human_mob.stat != CONSCIOUS || ismonkey(human_mob)) - continue - if(!human_mob.get_empty_held_indexes()) - continue - nearby_patrons += human_mob - - // Need at least 2 patrons to bother serving (bearing in mind the - if(length(nearby_patrons) < 1) - return - - var/obj/item/serving = controller.blackboard[BB_MONKEY_CURRENT_SERVED_ITEM] - if(QDELETED(serving) || serving.reagents.total_volume <= 0) - controller.queue_behavior(/datum/ai_behavior/find_and_set/food_or_drink/to_serve, BB_MONKEY_CURRENT_SERVED_ITEM, /obj/item, 2) - return - - // we have something to serve, pick a patron and go hand it over - if(living_pawn.is_holding(serving)) - controller.blackboard[BB_MONKEY_CURRENT_GIVE_TARGET] ||= pick(nearby_patrons) - controller.queue_behavior(/datum/ai_behavior/give, BB_MONKEY_CURRENT_GIVE_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - // we have something to serve but aren't holding it yet - if(isturf(serving.loc)) - // fetch the drink - controller.queue_behavior(/datum/ai_behavior/navigate_to_and_pick_up, BB_MONKEY_CURRENT_SERVED_ITEM, TRUE) - else - // give up on the dream - controller.clear_blackboard_key(BB_MONKEY_CURRENT_SERVED_ITEM) - return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/movement/_ai_movement.dm b/code/datums/ai/movement/_ai_movement.dm index 7c4df64e6e6e..dcf3947e859e 100644 --- a/code/datums/ai/movement/_ai_movement.dm +++ b/code/datums/ai/movement/_ai_movement.dm @@ -6,24 +6,42 @@ var/max_pathing_attempts //Override this to setup the moveloop you want to use -/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) SHOULD_CALL_PARENT(TRUE) + var/old_movement_target = moving_controllers[controller] + if(old_movement_target) + if(old_movement_target == current_movement_target) + return FALSE + else + update_movement_target(controller, current_movement_target) + return FALSE controller.consecutive_pathing_attempts = 0 controller.set_blackboard_key(BB_CURRENT_MIN_MOVE_DISTANCE, min_distance) moving_controllers[controller] = current_movement_target SEND_SIGNAL(controller.pawn, COMSIG_MOB_AI_MOVEMENT_STARTED, current_movement_target) + controller.set_blackboard_key(BB_CURRENT_MOVEMENT_TARGET, current_movement_target) + return TRUE + +/datum/ai_movement/proc/update_movement_target(datum/ai_controller/controller, atom/new_target) + moving_controllers[controller] = new_target + controller.set_blackboard_key(BB_CURRENT_MOVEMENT_TARGET, new_target) /datum/ai_movement/proc/stop_moving_towards(datum/ai_controller/controller) controller.consecutive_pathing_attempts = 0 moving_controllers -= controller // We got deleted as we finished an action + controller.clear_blackboard_key(BB_CURRENT_MOVEMENT_TARGET) if(!QDELETED(controller.pawn)) GLOB.move_manager.stop_looping(controller.pawn, SSai_movement) /datum/ai_movement/proc/increment_pathing_failures(datum/ai_controller/controller) controller.consecutive_pathing_attempts++ if(controller.consecutive_pathing_attempts >= max_pathing_attempts) - controller.CancelActions() + fail_movement(controller) + +/datum/ai_movement/proc/fail_movement(datum/ai_controller/controller) + stop_moving_towards(controller) + SEND_SIGNAL(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED, moving_controllers[controller]) /datum/ai_movement/proc/reset_pathing_failures(datum/ai_controller/controller) controller.consecutive_pathing_attempts = 0 @@ -67,7 +85,6 @@ // Check if this controller can actually run, so we don't chase people with corpses if(!controller.able_to_run) - controller.CancelActions() qdel(source) //stop moving return MOVELOOP_SKIP_STEP diff --git a/code/datums/ai/movement/ai_movement_basic_avoidance.dm b/code/datums/ai/movement/ai_movement_basic_avoidance.dm index 169301f70470..37cbfb9ebb65 100644 --- a/code/datums/ai/movement/ai_movement_basic_avoidance.dm +++ b/code/datums/ai/movement/ai_movement_basic_avoidance.dm @@ -4,15 +4,24 @@ /// Movement flags to pass to the loop var/move_flags = NONE -/datum/ai_movement/basic_avoidance/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/basic_avoidance/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) . = ..() + if(!.) + return FALSE var/atom/movable/moving = controller.pawn var/min_dist = controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE] - var/delay = controller.movement_delay + var/delay = delay_override || controller.movement_delay var/datum/move_loop/loop = GLOB.move_manager.move_to(moving, current_movement_target, min_dist, delay, flags = move_flags, subsystem = SSai_movement, extra_info = controller) RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) +/datum/ai_movement/basic_avoidance/update_movement_target(datum/ai_controller/controller, atom/new_target) + . = ..() + + var/datum/move_loop/has_target/loop = GLOB.move_manager.processing_on(controller.pawn, SSai_movement) + if(loop) + loop.target = new_target + /datum/ai_movement/basic_avoidance/allowed_to_move(datum/move_loop/has_target/dist_bound/source) var/turf/target_turf = get_step_towards(source.moving, source.target) if(!target_turf?.can_cross_safely(source.moving)) @@ -21,4 +30,4 @@ /// Move immediately and don't update our facing /datum/ai_movement/basic_avoidance/backstep - move_flags = MOVEMENT_LOOP_START_INSTANT | MOVEMENT_LOOP_NO_DIR_UPDATE + move_flags = MOVEMENT_LOOP_NO_DIR_UPDATE diff --git a/code/datums/ai/movement/ai_movement_complete_stop.dm b/code/datums/ai/movement/ai_movement_complete_stop.dm index e9609a30dda5..17787b86d958 100644 --- a/code/datums/ai/movement/ai_movement_complete_stop.dm +++ b/code/datums/ai/movement/ai_movement_complete_stop.dm @@ -2,8 +2,10 @@ /datum/ai_movement/complete_stop max_pathing_attempts = INFINITY // path all you want, you can not escape your fate -/datum/ai_movement/complete_stop/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/complete_stop/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) . = ..() + if(!.) + return FALSE var/atom/movable/moving = controller.pawn var/stopping_time = controller.blackboard[BB_STATIONARY_SECONDS] var/delay_time = (stopping_time * 0.5) // no real reason to fire any more often than this really @@ -11,5 +13,6 @@ var/datum/move_loop/loop = GLOB.move_manager.freeze(moving, current_movement_target, delay = delay_time, timeout = stopping_time, subsystem = SSai_movement, extra_info = controller) RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) + /datum/ai_movement/complete_stop/allowed_to_move(datum/move_loop/source) return FALSE diff --git a/code/datums/ai/movement/ai_movement_dumb.dm b/code/datums/ai/movement/ai_movement_dumb.dm index 001501bcbd93..f5ed83e2d881 100644 --- a/code/datums/ai/movement/ai_movement_dumb.dm +++ b/code/datums/ai/movement/ai_movement_dumb.dm @@ -3,14 +3,23 @@ max_pathing_attempts = 16 ///Put your movement behavior in here! -/datum/ai_movement/dumb/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/dumb/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) . = ..() + if(.) + return FALSE var/atom/movable/moving = controller.pawn - var/delay = controller.movement_delay + var/delay = delay_override || controller.movement_delay var/datum/move_loop/loop = GLOB.move_manager.move_towards_legacy(moving, current_movement_target, delay, subsystem = SSai_movement, extra_info = controller) RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) + +/datum/ai_movement/dumb/update_movement_target(datum/ai_controller/controller, atom/new_target) + . = ..() + var/datum/move_loop/has_target/loop = GLOB.move_manager.processing_on(controller.pawn, SSai_movement) + if(loop) + loop.target = new_target + /datum/ai_movement/dumb/allowed_to_move(datum/move_loop/has_target/source) var/turf/target_turf = get_step_towards(source.moving, source.target) if(!target_turf?.can_cross_safely(source.moving)) diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index a8980d15b851..3e7e4b6c725a 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -7,12 +7,14 @@ ///how we deal with diagonal movement, whether we try to avoid them or follow through with them var/diagonal_flags = DIAGONAL_REMOVE_CLUNKY -/datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) . = ..() + if(!.) + return FALSE var/atom/movable/moving = controller.pawn - var/delay = controller.movement_delay + var/delay = delay_override || controller.movement_delay - var/datum/move_loop/loop = setup_moveloop(controller, current_movement_target, moving, delay) + var/datum/move_loop/loop = setup_moveloop(controller, current_movement_target, moving, delay, min_distance) RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) @@ -21,14 +23,14 @@ return loop -/datum/ai_movement/jps/proc/setup_moveloop(datum/ai_controller/controller, atom/current_movement_target, atom/movable/moving, delay) +/datum/ai_movement/jps/proc/setup_moveloop(datum/ai_controller/controller, atom/current_movement_target, atom/movable/moving, delay, min_distance) var/datum/move_loop/has_target/jps/loop = GLOB.move_manager.jps_move(moving, current_movement_target, delay, repath_delay = 0.5 SECONDS, simulated_only = !HAS_TRAIT(controller.pawn, TRAIT_SPACEWALK), max_path_length = maximum_length, - minimum_distance = controller.get_minimum_distance(), + minimum_distance = min_distance, access = controller.get_access(), subsystem = SSai_movement, diagonal_handling = diagonal_flags, @@ -36,19 +38,25 @@ ) return loop +/datum/ai_movement/jps/update_movement_target(datum/ai_controller/controller, atom/new_target) + . = ..() + var/datum/move_loop/has_target/jps/loop = GLOB.move_manager.processing_on(controller.pawn, SSai_movement) + if(loop) + INVOKE_ASYNC(loop, TYPE_PROC_REF(/datum/move_loop/has_target/jps, recalculate_path)) + /datum/ai_movement/jps/proc/repath_incoming(datum/move_loop/has_target/jps/source) SIGNAL_HANDLER var/datum/ai_controller/controller = source.extra_info source.access = controller.get_access() - source.minimum_distance = controller.get_minimum_distance() + // minimum_distance was set at loop creation; no need to update it on repath /datum/ai_movement/jps/bot max_pathing_attempts = 8 maximum_length = 25 diagonal_flags = DIAGONAL_REMOVE_ALL -/datum/ai_movement/jps/bot/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/jps/bot/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) var/datum/move_loop/loop = ..() var/atom/our_pawn = controller.pawn if(isnull(our_pawn)) @@ -65,14 +73,14 @@ max_pathing_attempts = 10 maximum_length = AI_MULEBOT_PATH_LENGTH -/datum/ai_movement/jps/bot/mulebot/setup_moveloop(datum/ai_controller/controller, atom/current_movement_target, atom/movable/moving, delay) +/datum/ai_movement/jps/bot/mulebot/setup_moveloop(datum/ai_controller/controller, atom/current_movement_target, atom/movable/moving, delay, min_distance) var/datum/move_loop/has_target/jps/frustrations/loop = GLOB.move_manager.frustrations_move(moving, current_movement_target, delay, repath_delay = 0.5 SECONDS, simulated_only = !HAS_TRAIT(controller.pawn, TRAIT_SPACEWALK), max_path_length = maximum_length, - minimum_distance = controller.get_minimum_distance(), + minimum_distance = min_distance, access = controller.get_access(), subsystem = SSai_movement, diagonal_handling = diagonal_flags, diff --git a/code/datums/ai/objects/vending_machines/vending_machine.bt.json b/code/datums/ai/objects/vending_machines/vending_machine.bt.json new file mode 100644 index 000000000000..725e206a5841 --- /dev/null +++ b/code/datums/ai/objects/vending_machines/vending_machine.bt.json @@ -0,0 +1,64 @@ +{ + "dm_type": "/datum/ai_controller/vending_machine", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/vending_is_tilted", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_VENDING_UNTILT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/vendor_rise_up" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/vending_is_tilted", + "vars": { + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_VENDING_TILT_COOLDOWN" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_vendor_target", + "vars": { + "target_key": "BB_VENDING_CURRENT_TARGET", + "vision_range": 7 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_VENDING_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/vendor_crush", + "vars": { + "target_key": "BB_VENDING_CURRENT_TARGET" + } + } + ] + } + } + } + ] +} diff --git a/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm b/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm index 1aa936e124d4..6bfc8ec9fc49 100644 --- a/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm +++ b/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm @@ -1,28 +1,51 @@ -/datum/ai_behavior/vendor_crush - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - ///Time before machine can untilt itself after tilting - var/untilt_cooldown = 1 SECONDS - ///Time to telegraph and tilt over - var/time_to_tilt = 0.8 SECONDS +/// Returns TRUE if the vending machine pawn is currently tilted. +/datum/bt_node/decorator/vending_is_tilted/check_condition(datum/ai_controller/controller) + var/obj/machinery/vending/vendor_pawn = controller.pawn + return vendor_pawn.tilted -/datum/ai_behavior/vendor_crush/setup(datum/ai_controller/controller, target_key) - . = ..() - set_movement_target(controller, controller.blackboard[target_key]) +/// Searches nearby tiles for a valid living target and sets the given BB key. Sets tilt cooldown and fails if none found. +/datum/bt_node/ai_behavior/find_vendor_target + var/target_key + var/vision_range + /// Cooldown applied to BB_VENDING_TILT_COOLDOWN when no valid target is in range + var/search_cooldown = 2 SECONDS -/datum/ai_behavior/vendor_crush/perform(seconds_per_tick, datum/ai_controller/controller) +/datum/bt_node/ai_behavior/find_vendor_target/perform(seconds_per_tick, datum/ai_controller/controller) + for(var/mob/living/living_target in oview(vision_range, controller.pawn)) + if(living_target.stat || living_target.incorporeal_move) + continue + controller.set_blackboard_key(target_key, living_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + controller.clear_blackboard_key(target_key) + controller.set_blackboard_key(BB_VENDING_TILT_COOLDOWN, world.time + search_cooldown) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Telegraphs and tilts onto the target. Returns success once the machine is tilted. +/datum/bt_node/ai_behavior/vendor_crush + var/target_key + /// Time to telegraph before tilting + var/time_to_tilt = 0.8 SECONDS + /// Time before machine can untilt after a tilt attempt + var/untilt_cooldown = 1 SECONDS + +/datum/bt_node/ai_behavior/vendor_crush/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/machinery/vending/vendor_pawn = controller.pawn + if(vendor_pawn.tilted) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED if(controller.blackboard[BB_VENDING_BUSY_TILTING]) return AI_BEHAVIOR_DELAY - controller.ai_movement.stop_moving_towards(controller) controller.set_blackboard_key(BB_VENDING_BUSY_TILTING, TRUE) - var/turf/target_turf = get_turf(controller.blackboard[BB_VENDING_CURRENT_TARGET]) + var/turf/target_turf = get_turf(controller.blackboard[target_key]) new /obj/effect/temp_visual/telegraphing/vending_machine_tilt(target_turf) addtimer(CALLBACK(src, PROC_REF(tiltonmob), controller, target_turf), time_to_tilt) return AI_BEHAVIOR_DELAY -/datum/ai_behavior/vendor_crush/proc/tiltonmob(datum/ai_controller/controller, turf/target_turf) +/datum/bt_node/ai_behavior/vendor_crush/proc/tiltonmob(datum/ai_controller/controller, turf/target_turf) + if(QDELETED(controller) || QDELETED(controller.pawn)) + return var/obj/machinery/vending/vendor_pawn = controller.pawn - if(vendor_pawn.tilt(target_turf, 0) & SUCCESSFULLY_CRUSHED_MOB) //We hit something + if(vendor_pawn.tilt(target_turf, 0) & SUCCESSFULLY_CRUSHED_MOB) vendor_pawn.say(pick("Supersize this!", "Eat my shiny metal ass!", "Want to consume some of my products?", "SMASH!", "Don't you love these smashing prices!")) controller.set_blackboard_key(BB_VENDING_LAST_HIT_SUCCESSFUL, TRUE) else @@ -30,21 +53,22 @@ flick(vendor_pawn.icon_deny, vendor_pawn) vendor_pawn.say(pick("Get back here!", "Don't you want my well priced love?")) controller.set_blackboard_key(BB_VENDING_LAST_HIT_SUCCESSFUL, FALSE) - finish_action(controller, TRUE) + controller.set_blackboard_key(BB_VENDING_UNTILT_COOLDOWN, world.time + untilt_cooldown) + controller.set_blackboard_key(BB_VENDING_BUSY_TILTING, FALSE) -/datum/ai_behavior/vendor_crush/finish_action(datum/ai_controller/controller, succeeded) +/datum/bt_node/ai_behavior/vendor_crush/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.set_blackboard_key(BB_VENDING_BUSY_TILTING, FALSE) - controller.set_blackboard_key(BB_VENDING_UNTILT_COOLDOWN, world.time + untilt_cooldown) -/datum/ai_behavior/vendor_rise_up //what a gamer - ///Time before machine can tilt again after untilting if last hit was a success - var/succes_tilt_cooldown = 5 SECONDS +/// Untilts the machine. Sets a tilt cooldown if the previous hit was successful. +/datum/bt_node/ai_behavior/vendor_rise_up + /// Time before machine can tilt again after untilting if the last hit landed + var/success_tilt_cooldown = 5 SECONDS -/datum/ai_behavior/vendor_rise_up/perform(seconds_per_tick, datum/ai_controller/controller) +/datum/bt_node/ai_behavior/vendor_rise_up/perform(seconds_per_tick, datum/ai_controller/controller) var/obj/machinery/vending/vendor_pawn = controller.pawn vendor_pawn.visible_message(span_warning("[vendor_pawn] untilts itself!")) if(controller.blackboard[BB_VENDING_LAST_HIT_SUCCESSFUL]) - controller.set_blackboard_key(BB_VENDING_TILT_COOLDOWN, world.time + succes_tilt_cooldown) + controller.set_blackboard_key(BB_VENDING_TILT_COOLDOWN, world.time + success_tilt_cooldown) vendor_pawn.untilt() return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/objects/vending_machines/vending_machine_controller.dm b/code/datums/ai/objects/vending_machines/vending_machine_controller.dm index ab888040fb51..5e73a9d74607 100644 --- a/code/datums/ai/objects/vending_machines/vending_machine_controller.dm +++ b/code/datums/ai/objects/vending_machines/vending_machine_controller.dm @@ -1,6 +1,7 @@ ///AI controller for vending machine gone rogue, Don't try using this on anything else, it wont work. /datum/ai_controller/vending_machine movement_delay = 0.4 SECONDS + behavior_tree_json = "code/datums/ai/objects/vending_machines/vending_machine.bt.json" blackboard = list( BB_VENDING_CURRENT_TARGET = null, BB_VENDING_TILT_COOLDOWN = 0, @@ -10,10 +11,6 @@ ) /// If TRUE stops mobs from buying things from active machines var/block_usage = FALSE - /// Range to search for mobs to crunch - var/vision_range = 7 - /// Seconds between attempts to find a new mob to crunch - var/search_for_enemy_cooldown = 2 SECONDS /datum/ai_controller/vending_machine/TryPossessPawn(atom/new_pawn) if(!istype(new_pawn, /obj/machinery/vending)) @@ -35,26 +32,6 @@ UnregisterSignal(vendor_pawn, COMSIG_VENDING_UI_INTERACT) return ..() //Run parent at end -/datum/ai_controller/vending_machine/SelectBehaviors(seconds_per_tick) - current_behaviors = list() - var/obj/machinery/vending/vendor_pawn = pawn - - if(vendor_pawn.tilted) //We're tilted, try to untilt - if(blackboard[BB_VENDING_UNTILT_COOLDOWN] > world.time) - return - queue_behavior(/datum/ai_behavior/vendor_rise_up) - return - else //Not tilted, try to find target to tilt onto. - if(blackboard[BB_VENDING_TILT_COOLDOWN] > world.time) - return - for(var/mob/living/living_target in oview(vision_range, pawn)) - if(living_target.stat || living_target.incorporeal_move) //They're already fucked up or incorporeal - continue - set_blackboard_key(BB_VENDING_CURRENT_TARGET, living_target) - queue_behavior(/datum/ai_behavior/vendor_crush, BB_VENDING_CURRENT_TARGET) - return - set_blackboard_key(BB_VENDING_TILT_COOLDOWN, world.time + search_for_enemy_cooldown) - /datum/ai_controller/vending_machine/proc/deny_vending_interact(obj/machinery/vending/vending_machine, mob/user, datum/tgui/ui) SIGNAL_HANDLER if(!block_usage) diff --git a/code/datums/ai/robot_customer/robot_customer.bt.json b/code/datums/ai/robot_customer/robot_customer.bt.json new file mode 100644 index 000000000000..e055a409302b --- /dev/null +++ b/code/datums/ai/robot_customer/robot_customer.bt.json @@ -0,0 +1,125 @@ +{ + "dm_type": "/datum/ai_controller/robot_customer", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CUSTOMER_LEAVING", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/robot_customer/find_exit_portal" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CUSTOMER_EXIT_PORTAL", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/robot_customer/leave_venue" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CUSTOMER_CURRENT_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CUSTOMER_CURRENT_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_spine", + "vars": { + "target_key": "BB_CUSTOMER_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CUSTOMER_MY_SEAT", + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_CUSTOMER_FIND_SEAT_COOLDOWN", + "cooldown_duration": "8 SECONDS", + "lock_on_succeed": false + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/robot_customer/find_seat" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CUSTOMER_MY_SEAT" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CUSTOMER_MY_SEAT", + "required_dist": 0 + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CUSTOMER_CURRENT_ORDER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/robot_customer/order_food" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/robot_customer/wait_for_food" + } + ] + } + ] + } + } + ] +} diff --git a/code/datums/ai/robot_customer/robot_customer_behaviors.dm b/code/datums/ai/robot_customer/robot_customer_behaviors.dm index 42c2188bc4cb..45b087a58e06 100644 --- a/code/datums/ai/robot_customer/robot_customer_behaviors.dm +++ b/code/datums/ai/robot_customer/robot_customer_behaviors.dm @@ -1,86 +1,94 @@ -/datum/ai_behavior/find_seat - action_cooldown = 8 SECONDS +/// Searches nearby for an unclaimed seat belonging to the customer's venue. +/// Sets BB_CUSTOMER_MY_SEAT and claims it on success. Returns FAILURE if none found. +/datum/bt_node/ai_behavior/robot_customer/find_seat -/datum/ai_behavior/find_seat/perform(seconds_per_tick, datum/ai_controller/controller) +/datum/bt_node/ai_behavior/robot_customer/find_seat/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/robot_customer/customer_pawn = controller.pawn var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/datum/venue/attending_venue = controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] var/obj/structure/holosign/robot_seat/found_seat - - for(var/obj/structure/holosign/robot_seat/potential_seat in oview(7, controller.pawn)) - - if(potential_seat.linked_venue != attending_venue) //Incorrect venue + for(var/obj/structure/holosign/robot_seat/potential_seat in oview(7, customer_pawn)) + if(potential_seat.linked_venue != attending_venue) continue - - if(attending_venue.linked_seats[potential_seat]) //Someone called dibs + if(attending_venue.linked_seats[potential_seat]) continue var/turf/seat_turf = get_turf(potential_seat) - - if(seat_turf.is_blocked_turf()) //Someone called dibsies + if(seat_turf.is_blocked_turf()) continue - found_seat = potential_seat break if(found_seat) - customer_pawn.say(pick(customer_data.found_seat_lines)) + INVOKE_ASYNC(customer_pawn, TYPE_PROC_REF(/atom/movable, say), pick(customer_data.found_seat_lines)) controller.set_blackboard_key(BB_CUSTOMER_MY_SEAT, found_seat) attending_venue.linked_seats[found_seat] = customer_pawn - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - // SPT_PROB 1.5 is about a 60% chance that the tourist will have vocalised at least once every minute. if(!controller.blackboard[BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE] || SPT_PROB(1.5, seconds_per_tick)) - customer_pawn.say(pick(customer_data.cant_find_seat_lines)) + INVOKE_ASYNC(customer_pawn, TYPE_PROC_REF(/atom/movable, say), pick(customer_data.cant_find_seat_lines)) controller.set_blackboard_key(BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE, TRUE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + +/// Places the customer's food order once they are at their seat. +/datum/bt_node/ai_behavior/robot_customer/order_food -/datum/ai_behavior/order_food - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - required_distance = 0 +/datum/bt_node/ai_behavior/robot_customer/order_food/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags -/datum/ai_behavior/order_food/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/robot_customer/customer_pawn = controller.pawn - var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/obj/structure/holosign/robot_seat/seat_marker = controller.blackboard[BB_CUSTOMER_MY_SEAT] + if(get_turf(seat_marker) == get_turf(customer_pawn)) var/obj/structure/chair/my_seat = locate(/obj/structure/chair) in get_turf(customer_pawn) if(my_seat) - controller.pawn.setDir(my_seat.dir) //Sit in your seat + customer_pawn.setDir(my_seat.dir) + return start_async() + +/datum/bt_node/ai_behavior/robot_customer/order_food/perform_async(datum/ai_controller/controller) + var/mob/living/basic/robot_customer/customer_pawn = controller.pawn + var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/datum/venue/attending_venue = controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] + var/order + if(!QDELETED(customer_pawn) && !QDELETED(attending_venue)) + order = attending_venue.order_food(customer_pawn, customer_data) + if(!async_still_valid()) + return + if(!isnull(order)) + controller.set_blackboard_key(BB_CUSTOMER_CURRENT_ORDER, order) + finish_async(AI_BEHAVIOR_SUCCEEDED) - controller.set_blackboard_key(BB_CUSTOMER_CURRENT_ORDER, attending_venue.order_food(customer_pawn, customer_data)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/wait_for_food - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - required_distance = 0 +/// Waits at the seat for food to arrive. Ticks down patience and checks for food placed in front. +/// Succeeds when BB_CUSTOMER_EATING is set; fails when patience runs out or BB_CUSTOMER_LEAVING is set. +/// On finish, sets BB_CUSTOMER_LEAVING and says the appropriate departure line. +/datum/bt_node/ai_behavior/robot_customer/wait_for_food -/datum/ai_behavior/wait_for_food/perform(seconds_per_tick, datum/ai_controller/controller) +/datum/bt_node/ai_behavior/robot_customer/wait_for_food/perform(seconds_per_tick, datum/ai_controller/controller) if(controller.blackboard[BB_CUSTOMER_EATING]) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - controller.add_blackboard_key(BB_CUSTOMER_PATIENCE, seconds_per_tick * -1 SECONDS) // Convert seconds_per_tick to a SECONDS equivalent. - if(controller.blackboard[BB_CUSTOMER_PATIENCE] < 0 || controller.blackboard[BB_CUSTOMER_LEAVING]) // Check if we're leaving because something might've forced us to + controller.add_blackboard_key(BB_CUSTOMER_PATIENCE, seconds_per_tick * -1 SECONDS) + if(controller.blackboard[BB_CUSTOMER_PATIENCE] < 0 || controller.blackboard[BB_CUSTOMER_LEAVING]) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - // SPT_PROB 1.5 is about a 40% chance that the tourist will have vocalised at least once every minute. if(SPT_PROB(0.85, seconds_per_tick)) var/mob/living/basic/robot_customer/customer_pawn = controller.pawn var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] - customer_pawn.say(pick(customer_data.wait_for_food_lines)) + INVOKE_ASYNC(customer_pawn, TYPE_PROC_REF(/atom/movable, say), pick(customer_data.wait_for_food_lines)) var/obj/structure/holosign/robot_seat/seat_marker = controller.blackboard[BB_CUSTOMER_MY_SEAT] if(get_turf(seat_marker) == get_turf(controller.pawn)) var/obj/structure/chair/my_seat = locate(/obj/structure/chair) in get_turf(controller.pawn) if(my_seat) - controller.pawn.setDir(my_seat.dir) //Sit in your seat + controller.pawn.setDir(my_seat.dir) - ///Now check if there's a meal infront of us. var/datum/venue/attending_venue = controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] - var/turf/infront_turf = get_step(controller.pawn, controller.pawn.dir) for(var/obj/item/I in infront_turf.contents) if(attending_venue.is_correct_order(I, controller.blackboard[BB_CUSTOMER_CURRENT_ORDER])) @@ -90,31 +98,42 @@ return AI_BEHAVIOR_DELAY -/datum/ai_behavior/wait_for_food/finish_action(datum/ai_controller/controller, succeeded) +/datum/bt_node/ai_behavior/robot_customer/wait_for_food/finish_action(datum/ai_controller/controller, succeeded) . = ..() var/mob/living/basic/robot_customer/customer_pawn = controller.pawn var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/mob/living/greytider = controller.blackboard[BB_CUSTOMER_CURRENT_TARGET] - //usually if we stop waiting, it's because we're done with the venue. but here we're either beating some dude up - //or are being qdeleted and don't want runtime errors, so don't switch to leaving + // Don't switch to leaving if we're heading to beat someone up or if we're being deleted. if(greytider || QDELETED(src) || QDELETED(customer_pawn)) return controller.set_blackboard_key(BB_CUSTOMER_LEAVING, TRUE) - customer_pawn.update_icon() //They might have a special leaving accessory (French flag) + customer_pawn.update_icon() if(succeeded) - customer_pawn.say(pick(customer_data.leave_happy_lines)) + INVOKE_ASYNC(customer_pawn, TYPE_PROC_REF(/atom/movable, say), pick(customer_data.leave_happy_lines)) else - customer_pawn.say(pick(customer_data.leave_mad_lines)) + INVOKE_ASYNC(customer_pawn, TYPE_PROC_REF(/atom/movable, say), pick(customer_data.leave_mad_lines)) -/datum/ai_behavior/leave_venue - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH +/// Resolves the venue exit portal from attending_venue.current_visitors and stores it in BB_CUSTOMER_EXIT_PORTAL. +/datum/bt_node/ai_behavior/robot_customer/find_exit_portal -/datum/ai_behavior/leave_venue/setup(datum/ai_controller/controller, venue_key) - . = ..() - var/datum/venue/attending_venue = controller.blackboard[venue_key] +/datum/bt_node/ai_behavior/robot_customer/find_exit_portal/perform(seconds_per_tick, datum/ai_controller/controller) + if(!isnull(controller.blackboard[BB_CUSTOMER_EXIT_PORTAL])) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + var/datum/venue/attending_venue = controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] + if(isnull(attending_venue)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/datum/weakref/portal_ref = attending_venue.current_visitors[controller.pawn] - set_movement_target(controller, portal_ref.resolve()) + var/atom/portal = portal_ref?.resolve() + if(isnull(portal) || QDELETED(portal)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + controller.set_blackboard_key(BB_CUSTOMER_EXIT_PORTAL, portal) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +/// Deletes the pawn once they have reached the exit portal. +/datum/bt_node/ai_behavior/robot_customer/leave_venue + +/datum/bt_node/ai_behavior/robot_customer/leave_venue/perform(seconds_per_tick, datum/ai_controller/controller) + qdel(controller.pawn) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/leave_venue/perform(seconds_per_tick, datum/ai_controller/controller, venue_key) - qdel(controller.pawn) //save the world, my final message, goodbye. - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/robot_customer/robot_customer_controller.dm b/code/datums/ai/robot_customer/robot_customer_controller.dm index 0fc9d27eb43f..2a28089fc289 100644 --- a/code/datums/ai/robot_customer/robot_customer_controller.dm +++ b/code/datums/ai/robot_customer/robot_customer_controller.dm @@ -1,4 +1,5 @@ /datum/ai_controller/robot_customer + behavior_tree_json = "code/datums/ai/robot_customer/robot_customer.bt.json" ai_movement = /datum/ai_movement/basic_avoidance movement_delay = 0.8 SECONDS blackboard = list( @@ -11,12 +12,11 @@ BB_CUSTOMER_PATIENCE = 999 SECONDS, BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE = FALSE, ) - planning_subtrees = list(/datum/ai_planning_subtree/robot_customer) /datum/ai_controller/robot_customer/Destroy() - // clear possible datum refs clear_blackboard_key(BB_CUSTOMER_CURRENT_ORDER) clear_blackboard_key(BB_CUSTOMER_CUSTOMERINFO) + clear_blackboard_key(BB_CUSTOMER_EXIT_PORTAL) return ..() /datum/ai_controller/robot_customer/TryPossessPawn(atom/new_pawn) @@ -111,7 +111,7 @@ customer.say(customer_data.self_defense_line) set_blackboard_key(BB_CUSTOMER_CURRENT_TARGET, greytider) - CancelActions() + cancel_current_plan() /datum/ai_controller/robot_customer/proc/on_get_punched(datum/source, mob/living/living_hitter) SIGNAL_HANDLER diff --git a/code/datums/ai/robot_customer/robot_customer_subtrees.dm b/code/datums/ai/robot_customer/robot_customer_subtrees.dm deleted file mode 100644 index 3fd2d8ed547c..000000000000 --- a/code/datums/ai/robot_customer/robot_customer_subtrees.dm +++ /dev/null @@ -1,23 +0,0 @@ -/datum/ai_planning_subtree/robot_customer/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_CUSTOMER_LEAVING]) - controller.queue_behavior(/datum/ai_behavior/leave_venue, BB_CUSTOMER_ATTENDING_VENUE) - return SUBTREE_RETURN_FINISH_PLANNING - - if(controller.blackboard[BB_CUSTOMER_CURRENT_TARGET]) - - controller.queue_behavior(/datum/ai_behavior/break_spine, BB_CUSTOMER_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - var/obj/structure/holosign/robot_seat/seat_marker = controller.blackboard[BB_CUSTOMER_MY_SEAT] - - if(!seat_marker) //We havn't got a seat yet! find one! - controller.queue_behavior(/datum/ai_behavior/find_seat) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.set_movement_target(type, seat_marker) - - if(!controller.blackboard[BB_CUSTOMER_CURRENT_ORDER]) //We haven't ordered yet even ordered yet. go on! go over there and go do it! - controller.queue_behavior(/datum/ai_behavior/order_food) - return SUBTREE_RETURN_FINISH_PLANNING - else - controller.queue_behavior(/datum/ai_behavior/wait_for_food) diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm index b4792fbd27c0..fbea9e767f2d 100644 --- a/code/datums/brain_damage/special.dm +++ b/code/datums/brain_damage/special.dm @@ -515,8 +515,8 @@ owner.ai_controller = new /datum/ai_controller/monkey(owner) owner.ai_controller.continue_processing_when_client = TRUE - owner.ai_controller.can_idle = FALSE - owner.ai_controller.set_ai_status(AI_STATUS_OFF) + owner.ai_controller.ai_traits |= RUN_WHILE_UNWATCHED + owner.ai_controller.force_ai_off() /datum/brain_trauma/special/primal_instincts/on_lose(silent) . = ..() @@ -539,14 +539,14 @@ owner.grant_language(/datum/language/monkey, UNDERSTOOD_LANGUAGE, TRAUMA_TRAIT) owner.ai_controller.set_blackboard_key(BB_MONKEY_AGGRESSIVE, prob(75)) if(owner.ai_controller.ai_status == AI_STATUS_OFF) - owner.ai_controller.set_ai_status(AI_STATUS_ON) + owner.ai_controller.clear_forced_off() owner.log_message("became controlled by monkey instincts ([owner.ai_controller.blackboard[BB_MONKEY_AGGRESSIVE] ? "aggressive" : "docile"])", LOG_ATTACK, color = "orange") to_chat(owner, span_warning("You feel the urge to act on your primal instincts...")) // extend original timer if we roll the effect while it's already ongoing addtimer(CALLBACK(src, PROC_REF(primal_instincts_off)), rand(20 SECONDS, 40 SECONDS), TIMER_UNIQUE|TIMER_NO_HASH_WAIT|TIMER_OVERRIDE|TIMER_DELETE_ME) /datum/brain_trauma/special/primal_instincts/proc/primal_instincts_off() - owner.ai_controller.set_ai_status(AI_STATUS_OFF) + owner.ai_controller.force_ai_off() owner.remove_language(/datum/language/monkey, UNDERSTOOD_LANGUAGE, TRAUMA_TRAIT) to_chat(owner, span_green("The urge subsides.")) diff --git a/code/datums/components/aggro_emote.dm b/code/datums/components/aggro_emote.dm index 0688d277aa43..b71d04bdbebb 100644 --- a/code/datums/components/aggro_emote.dm +++ b/code/datums/components/aggro_emote.dm @@ -14,7 +14,7 @@ var/minimum_chance /datum/component/aggro_emote/Initialize( - target_key = BB_BASIC_MOB_CURRENT_TARGET, + target_key = BB_CURRENT_TARGET, living_only = FALSE, list/emote_list, emote_chance = 30, diff --git a/code/datums/components/ai_has_target_timer.dm b/code/datums/components/ai_has_target_timer.dm index 5fdc07417f4d..5037b4d98158 100644 --- a/code/datums/components/ai_has_target_timer.dm +++ b/code/datums/components/ai_has_target_timer.dm @@ -11,7 +11,7 @@ /// Timer used to see if you var/reset_clock_timer -/datum/component/ai_target_timer/Initialize(increment_key = BB_BASIC_MOB_HAS_TARGET_TIME, target_key = BB_BASIC_MOB_CURRENT_TARGET) +/datum/component/ai_target_timer/Initialize(increment_key = BB_BASIC_MOB_HAS_TARGET_TIME, target_key = BB_CURRENT_TARGET) . = ..() if (!isliving(parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/ai_listen_to_weather.dm b/code/datums/components/ai_listen_to_weather.dm index 29bcda3839b5..758aaf8d3f5d 100644 --- a/code/datums/components/ai_listen_to_weather.dm +++ b/code/datums/components/ai_listen_to_weather.dm @@ -26,7 +26,7 @@ var/mob/living/basic/source = parent if(!source.ai_controller) return - source.ai_controller.CancelActions() + source.ai_controller.cancel_current_plan() source.ai_controller.set_blackboard_key(weather_key, TRUE) /datum/component/ai_listen_to_weather/proc/storm_end() diff --git a/code/datums/components/appearance_on_aggro.dm b/code/datums/components/appearance_on_aggro.dm index 1cc9ef4f2109..e78a13a34494 100644 --- a/code/datums/components/appearance_on_aggro.dm +++ b/code/datums/components/appearance_on_aggro.dm @@ -4,7 +4,7 @@ */ /datum/component/appearance_on_aggro /// Blackboardey to search for a target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET + var/target_key = BB_CURRENT_TARGET /// Icon state to use when we have a target var/aggro_state /// path of the overlay to apply diff --git a/code/datums/components/connect_range.dm b/code/datums/components/connect_range.dm index e001261ab128..cb19a98552ad 100644 --- a/code/datums/components/connect_range.dm +++ b/code/datums/components/connect_range.dm @@ -114,4 +114,6 @@ /datum/component/connect_range/proc/on_moved(atom/movable/movable, atom/old_loc) SIGNAL_HANDLER + if(QDELETED(src)) //Basically, if a mob moves it will trigger the movable signal on its own field. If the mob finds a target when moving it will qdel this, but because it also moved into the field this will run, crash, and burn. so we're checking qdeleted. + return update_signals(movable, old_loc) diff --git a/code/datums/components/pet_commands/fetch.dm b/code/datums/components/pet_commands/fetch.dm index f3675534fa95..9dfb196b1e7a 100644 --- a/code/datums/components/pet_commands/fetch.dm +++ b/code/datums/components/pet_commands/fetch.dm @@ -92,30 +92,6 @@ var/mob/living/parent = weak_parent.resolve() parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, pointing_friend) -// Finally, plan our actions +// Install the fetch BT subtree. The subtree itself handles all phases (seek > pick up > deliver). /datum/pet_command/fetch/execute_action(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/forget_failed_fetches) - - var/atom/target = controller.blackboard[BB_CURRENT_PET_TARGET] - // We got something to fetch so go fetch it - if (!QDELETED(target)) - if (get_dist(controller.pawn, target) > 1) // We're not there yet - controller.queue_behavior(/datum/ai_behavior/fetch_seek, BB_CURRENT_PET_TARGET, BB_FETCH_DELIVER_TO) - return SUBTREE_RETURN_FINISH_PLANNING - // If mobs could attack food you would branch here to call `eat_fetched_snack`, however that's a task for the future - controller.queue_behavior(/datum/ai_behavior/pick_up_item, BB_CURRENT_PET_TARGET, BB_SIMPLE_CARRY_ITEM) - return SUBTREE_RETURN_FINISH_PLANNING - - var/obj/item/carried_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM] - if (QDELETED(carried_item)) - return - - var/atom/delivery_target = controller.blackboard[BB_FETCH_DELIVER_TO] - if (QDELETED(delivery_target) || !can_see(controller.pawn, delivery_target, sense_radius)) - // We don't know where to return this to so we're just going to keep it - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return - - // We got something to deliver and someone to deliver it to - controller.queue_behavior(/datum/ai_behavior/deliver_fetched_item, BB_FETCH_DELIVER_TO, BB_SIMPLE_CARRY_ITEM) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/fetch) diff --git a/code/datums/components/pet_commands/pet_command.dm b/code/datums/components/pet_commands/pet_command.dm index 04dde32d5db8..5616de8a6ca5 100644 --- a/code/datums/components/pet_commands/pet_command.dm +++ b/code/datums/components/pet_commands/pet_command.dm @@ -148,7 +148,6 @@ if (!can_see(parent, potential_target, sense_radius)) return FALSE - parent.ai_controller.CancelActions() set_command_target(parent, potential_target) return TRUE @@ -156,8 +155,11 @@ /datum/pet_command/proc/set_command_active(mob/living/parent, mob/living/commander, radial_command = FALSE) parent.ai_controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) - parent.ai_controller.CancelActions() // Stop whatever you're doing and do this instead + var/datum/pet_command/previous = parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] + if(previous && previous != src) + previous.command_ended(parent.ai_controller) parent.ai_controller.set_blackboard_key(BB_ACTIVE_PET_COMMAND, src) + execute_action(parent.ai_controller) // Install the BT override subtree for this command if (command_feedback) parent.balloon_alert_to_viewers("[command_feedback]") // If we get a nicer runechat way to do this, refactor this if(!radial_command) @@ -170,6 +172,12 @@ commander.client?.mouse_override_icon = 'icons/effects/mouse_pointers/pet_paw.dmi' commander.update_mouse_pointer() + +/// Called when this command is replaced by another command or otherwise deactivated. Extend to add cleanup logic. +/datum/pet_command/proc/command_ended(datum/ai_controller/controller) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, null) + return + /datum/pet_command/proc/click_on_target(mob/living/source, atom/target, list/modifiers) SIGNAL_HANDLER if(!can_see(source, target, 9)) @@ -215,7 +223,7 @@ if (!parent) return FALSE - parent.ai_controller.CancelActions() + parent.ai_controller.cancel_current_plan() if(!look_for_target(friend, potential_target) || !set_command_target(parent, potential_target)) return FALSE parent.visible_message(span_warning("[parent] follows [friend]'s gesture towards [potential_target] [pointed_reaction]!")) diff --git a/code/datums/components/pet_commands/pet_commands_basic.dm b/code/datums/components/pet_commands/pet_commands_basic.dm index 2269f760ba45..ff1dcf007394 100644 --- a/code/datums/components/pet_commands/pet_commands_basic.dm +++ b/code/datums/components/pet_commands/pet_commands_basic.dm @@ -12,14 +12,14 @@ command_feedback = "sits" /datum/pet_command/idle/execute_action(datum/ai_controller/controller) - return SUBTREE_RETURN_FINISH_PLANNING // This cancels further AI planning + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/stay) /datum/pet_command/idle/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to stay idle!" /** * # Pet Command: Stop - * Tells a pet to exit command mode and resume its normal behaviour, which includes regular target-seeking and what have you + * Tells a pet to exit command mode and resume its normal behaviour */ /datum/pet_command/free command_name = "Loose" @@ -29,8 +29,8 @@ command_feedback = "relaxes" /datum/pet_command/free/execute_action(datum/ai_controller/controller) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, null) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return // Just move on to the next planning subtree. /datum/pet_command/free/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to go free!" @@ -45,10 +45,10 @@ radial_icon_state = "follow" speech_commands = list("heel", "follow") callout_type = /datum/callout_option/move - ///the behavior we use to follow - var/follow_behavior = /datum/ai_behavior/pet_follow_friend ///should we activate immediately if we're doing nothing else and gain a friend? var/activate_on_befriend = FALSE + /// BT subtree installed by execute_action; override to use a mob-specific follow tree. + var/follow_subtree = /datum/bt_node/subtree/pet_command/follow /datum/pet_command/follow/set_command_active(mob/living/parent, mob/living/commander) . = ..() @@ -58,8 +58,7 @@ return "signals [living_pet] to follow!" /datum/pet_command/follow/execute_action(datum/ai_controller/controller) - controller.queue_behavior(follow_behavior, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, follow_subtree) /datum/pet_command/follow/add_new_friend(mob/living/tamer) . = ..() @@ -84,8 +83,7 @@ speech_commands = list("play dead") // Don't get too creative here, people talk about dying pretty often /datum/pet_command/play_dead/execute_action(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/play_dead) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/play_dead) /datum/pet_command/play_dead/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to play dead!" @@ -112,14 +110,13 @@ // If we get past this point someone has finally added a non-binary dog /datum/pet_command/good_boy/execute_action(datum/ai_controller/controller) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, null) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) var/mob/living/parent = weak_parent.resolve() if (!parent) - return SUBTREE_RETURN_FINISH_PLANNING - + return new /obj/effect/temp_visual/heart(parent.loc) parent.emote("spin") - return SUBTREE_RETURN_FINISH_PLANNING /** * # Pet Command: Use ability @@ -133,9 +130,9 @@ var/datum/action/cooldown/ability = controller.blackboard[ability_key] if(!ability?.IsAvailable()) return - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, ability_key) + controller.set_blackboard_key(BB_PET_ACTIVE_ABILITY, ability) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/untargeted_ability) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING /datum/pet_command/untargeted_ability/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to use an ability!" @@ -155,8 +152,7 @@ pointed_reaction = "and growls" /// Balloon alert to display if providing an invalid target var/refuse_reaction = "shakes head" - /// Attack behaviour to use - var/attack_behaviour = /datum/ai_behavior/basic_melee_attack + var/datum/bt_node/subtree/attack_subtree = /datum/bt_node/subtree/pet_command/attack // Refuse to target things we can't target, chiefly other friends /datum/pet_command/attack/set_command_target(mob/living/parent, atom/target) @@ -168,7 +164,7 @@ var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(living_parent.ai_controller.blackboard[targeting_strategy_key]) if (!targeter) return FALSE - if (!targeter.can_attack(living_parent, target)) + if (!targeter.is_valid_target(living_parent, target)) refuse_target(parent, target) return FALSE return ..() @@ -183,8 +179,7 @@ living_parent.visible_message(span_notice("[living_parent] refuses to attack [target].")) /datum/pet_command/attack/execute_action(datum/ai_controller/controller) - controller.queue_behavior(attack_behaviour, BB_CURRENT_PET_TARGET, targeting_strategy_key) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, attack_subtree) /** * # Breed command. breed with a partner! @@ -195,8 +190,6 @@ requires_pointing = TRUE radial_icon_state = "breed" speech_commands = list("breed", "consummate") - ///the behavior we use to make babies - var/datum/ai_behavior/reproduce_behavior = /datum/ai_behavior/make_babies /datum/pet_command/breed/set_command_target(mob/living/parent, atom/target) if(isnull(target) || !isliving(target)) @@ -213,10 +206,7 @@ return ..() /datum/pet_command/breed/execute_action(datum/ai_controller/controller) - if(is_type_in_list(controller.blackboard[BB_CURRENT_PET_TARGET], controller.blackboard[BB_BABIES_PARTNER_TYPES])) - controller.queue_behavior(reproduce_behavior, BB_CURRENT_PET_TARGET) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/breed) /datum/pet_command/breed/retrieve_command_text(atom/living_pet, atom/target) return isnull(target) ? null : "signals [living_pet] to breed with [target]!" @@ -236,8 +226,6 @@ pointed_reaction = "and growls" /// Blackboard key where a reference to some kind of mob ability is stored var/pet_ability_key - /// The AI behavior to use for the ability - var/ability_behavior = /datum/ai_behavior/pet_use_ability /datum/pet_command/use_ability/execute_action(datum/ai_controller/controller) if (!pet_ability_key) @@ -245,10 +233,9 @@ var/datum/action/cooldown/using_action = controller.blackboard[pet_ability_key] if (QDELETED(using_action)) return - // We don't check if the target exists because we want to 'sit attentively' if we've been instructed to attack but not given one yet - // We also don't check if the cooldown is over because there's no way a pet owner can know that, the behaviour will handle it - controller.queue_behavior(ability_behavior, pet_ability_key, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + // Store the action datum in a fixed key so the targeted_ability subtree can find it + controller.override_blackboard_key(BB_TARGETED_ACTION, using_action) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/targeted_ability) /datum/pet_command/use_ability/retrieve_command_text(atom/living_pet, atom/target) return isnull(target) ? null : "signals [living_pet] to use an ability on [target]!" @@ -260,10 +247,9 @@ callout_type = /datum/callout_option/guard ///the range our owner needs to be in for us to protect him var/protect_range = 9 - ///the behavior we will use when he is attacked - var/protect_behavior = /datum/ai_behavior/basic_melee_attack ///message cooldown to prevent too many people from telling you not to commit suicide COOLDOWN_DECLARE(self_harm_message_cooldown) + var/datum/bt_node/subtree/protect_owner_subtree = /datum/bt_node/subtree/pet_command/protect_owner /datum/pet_command/protect_owner/add_new_friend(mob/living/tamer) RegisterSignal(tamer, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(set_attacking_target)) @@ -274,18 +260,7 @@ UnregisterSignal(unfriended, COMSIG_ATOM_WAS_ATTACKED) /datum/pet_command/protect_owner/execute_action(datum/ai_controller/controller) - var/mob/living/victim = controller.blackboard[BB_CURRENT_PET_TARGET] - if(QDELETED(victim)) - return - var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targeter.can_attack(controller.pawn, victim)) - return - // cancel the action if they're below our given crit stat, OR if we're trying to attack ourselves (this can happen on tamed mobs w/ protect subtree rarely) - if(victim.stat > controller.blackboard[BB_TARGET_MINIMUM_STAT] || victim == controller.pawn) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return - controller.queue_behavior(protect_behavior, BB_CURRENT_PET_TARGET, BB_PET_TARGETING_STRATEGY) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, protect_owner_subtree) /datum/pet_command/protect_owner/set_command_active(mob/living/parent, mob/living/victim) . = ..() @@ -314,7 +289,7 @@ set_command_active(owner, attacker) /** - * # Fish command: command the mob to fish at the next fishing spot you point at. Requires the profound fisher component + * # Fish command: command the mob to fish at the next fishing spot you point at. */ /datum/pet_command/fish command_name = "Fish" @@ -324,9 +299,10 @@ speech_commands = list("fish") /datum/pet_command/fish/execute_action(datum/ai_controller/controller) - if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) - controller.queue_behavior(/datum/ai_behavior/interact_with_target/fishing, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + if(!controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/stay) + return + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/fish) /datum/pet_command/fish/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to go fish!" @@ -337,8 +313,6 @@ requires_pointing = TRUE radial_icon_state = "move" speech_commands = list("move", "walk") - ///the behavior we use to walk towards targets - var/datum/ai_behavior/walk_behavior = /datum/ai_behavior/travel_towards /datum/pet_command/move/set_command_target(mob/living/parent, atom/target) if(isnull(target) || !can_see(parent, target, 9)) @@ -346,9 +320,7 @@ return ..() /datum/pet_command/move/execute_action(datum/ai_controller/controller) - if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) - controller.queue_behavior(walk_behavior, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/move_to) /datum/pet_command/move/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to move!" diff --git a/code/datums/components/proficient_miner.dm b/code/datums/components/proficient_miner.dm index b37153b38412..2c264dd3a189 100644 --- a/code/datums/components/proficient_miner.dm +++ b/code/datums/components/proficient_miner.dm @@ -15,14 +15,27 @@ /datum/component/proficient_miner/RegisterWithParent() RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(on_bump)) + RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_unarmed_attack)) /datum/component/proficient_miner/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_MOVABLE_BUMP) + UnregisterSignal(parent, list(COMSIG_MOVABLE_BUMP, COMSIG_LIVING_UNARMED_ATTACK)) + +/datum/component/proficient_miner/proc/on_unarmed_attack(mob/living/source, atom/target, proximity, modifiers) + SIGNAL_HANDLER + + if(!proximity) + return + try_mine(source, target) /datum/component/proficient_miner/proc/on_bump(atom/movable/source, atom/target) SIGNAL_HANDLER - if(!ismineralturf(target) || last_bumpmine_tick == world.time) + if(last_bumpmine_tick == world.time) + return + try_mine(source, target) + +/datum/component/proficient_miner/proc/try_mine(atom/movable/source, atom/target) + if(!ismineralturf(target)) return var/mob/living/user = null diff --git a/code/datums/components/revenge_ability.dm b/code/datums/components/revenge_ability.dm index 2c52b8cd31a4..aa0f81ded67b 100644 --- a/code/datums/components/revenge_ability.dm +++ b/code/datums/components/revenge_ability.dm @@ -50,7 +50,7 @@ var/distance = get_dist(ability_user, attacker) if (distance < min_range || distance > max_range) return - if (targeting && !targeting.can_attack(victim, attacker)) + if (targeting && !targeting.is_valid_target(victim, attacker)) return INVOKE_ASYNC(ability, TYPE_PROC_REF(/datum/action/cooldown, InterceptClickOn), ability_user, null, (target_self) ? ability_user : attacker) diff --git a/code/datums/components/tameable.dm b/code/datums/components/tameable.dm index 2153d54d51e7..9fcdeea36fd7 100644 --- a/code/datums/components/tameable.dm +++ b/code/datums/components/tameable.dm @@ -56,6 +56,9 @@ /datum/component/tameable/proc/on_tame(atom/source, mob/living/tamer, obj/item/food, inform_tamer = FALSE) SIGNAL_HANDLER source.tamed(tamer, food)//Run custom behavior if needed + if(isliving(parent)) + var/mob/living/living_parent = parent + living_parent.ai_controller?.set_blackboard_key(BB_TAMED, TRUE) if(isliving(parent) && isliving(tamer)) INVOKE_ASYNC(source, TYPE_PROC_REF(/mob/living, befriend), tamer) if(inform_tamer) diff --git a/code/datums/components/tree_climber.dm b/code/datums/components/tree_climber.dm index 9f506ae516f8..6ec21ac41a90 100644 --- a/code/datums/components/tree_climber.dm +++ b/code/datums/components/tree_climber.dm @@ -15,11 +15,11 @@ ADD_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) /datum/component/tree_climber/RegisterWithParent() - RegisterSignals(parent, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_CLIMB_TREE), PROC_REF(climb_tree)) + RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(climb_tree)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) /datum/component/tree_climber/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_CLIMB_TREE, COMSIG_ATOM_EXAMINE)) + UnregisterSignal(parent, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_ATOM_EXAMINE)) /datum/component/tree_climber/Destroy() if(current_tree) diff --git a/code/datums/diseases/verminous_plague.dm b/code/datums/diseases/verminous_plague.dm index 8e56f7fcc08b..981bfadea015 100644 --- a/code/datums/diseases/verminous_plague.dm +++ b/code/datums/diseases/verminous_plague.dm @@ -66,7 +66,7 @@ GLOBAL_LIST_INIT(cursed_vermin_by_stage, list( if (stage == 1 || !created.ai_controller) return - created.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, affected_mob) + created.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, affected_mob) created.ai_controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, affected_mob) /datum/disease/verminous_plague/update_stage(new_stage) diff --git a/code/datums/dog_fashion.dm b/code/datums/dog_fashion.dm index 1c27df608229..ef95b1bce7a8 100644 --- a/code/datums/dog_fashion.dm +++ b/code/datums/dog_fashion.dm @@ -45,13 +45,13 @@ dressup_doggy.speak_emote = string_list(speak_emote) ///Applies random speech modifiers to the dog -/datum/dog_fashion/proc/apply_to_speech(datum/ai_planning_subtree/random_speech/speech) +/datum/dog_fashion/proc/apply_to_speech(list/speech_data) if(LAZYLEN(emote_see)) - speech.emote_see = string_list(emote_see) + speech_data[BB_EMOTE_SEE] = string_list(emote_see) if(LAZYLEN(emote_hear)) - speech.emote_hear = string_list(emote_hear) + speech_data[BB_EMOTE_HEAR] = string_list(emote_hear) if(LAZYLEN(speak)) - speech.speak = string_list(speak) + speech_data[BB_EMOTE_SAY] = string_list(speak) /** * Generates the icon overlay for the equipped item diff --git a/code/datums/elements/ai_flee_while_injured.dm b/code/datums/elements/ai_flee_while_injured.dm index eca709dbee5d..c478181244ea 100644 --- a/code/datums/elements/ai_flee_while_injured.dm +++ b/code/datums/elements/ai_flee_while_injured.dm @@ -36,11 +36,9 @@ if (source.ai_controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) if (current_health_percentage > start_fleeing_below) return - source.ai_controller.CancelActions() source.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) return if (current_health_percentage < stop_fleeing_at) return - source.ai_controller.CancelActions() // Stop fleeing go back to whatever you were doing source.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) diff --git a/code/datums/elements/ai_retaliate.dm b/code/datums/elements/ai_retaliate.dm index 4fcb51581438..c4c4266e2a4e 100644 --- a/code/datums/elements/ai_retaliate.dm +++ b/code/datums/elements/ai_retaliate.dm @@ -18,7 +18,7 @@ . = ..() UnregisterSignal(source, COMSIG_ATOM_WAS_ATTACKED) -/// Add an attacking atom to a blackboard list of things which attacked us +/// Add an attacking atom to a blackboard list of things which attacked us. /datum/element/ai_retaliate/proc/on_attacked(mob/victim, atom/attacker) SIGNAL_HANDLER diff --git a/code/datums/elements/ai_target_damagesource.dm b/code/datums/elements/ai_target_damagesource.dm index a1f0ea8b2a51..7ef04b9e4cde 100644 --- a/code/datums/elements/ai_target_damagesource.dm +++ b/code/datums/elements/ai_target_damagesource.dm @@ -22,5 +22,5 @@ if (!victim.ai_controller) return - victim.ai_controller.CancelActions() - victim.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, attacker) + victim.ai_controller.cancel_current_plan() + victim.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, attacker) diff --git a/code/datums/proximity_monitor/fields/ai_target_tracking.dm b/code/datums/proximity_monitor/fields/ai_target_tracking.dm index 46cde22aaffc..5c596e1dfd75 100644 --- a/code/datums/proximity_monitor/fields/ai_target_tracking.dm +++ b/code/datums/proximity_monitor/fields/ai_target_tracking.dm @@ -2,13 +2,13 @@ /datum/proximity_monitor/advanced/ai_target_tracking edge_is_a_field = TRUE /// The ai behavior who owns us - var/datum/ai_behavior/find_potential_targets/owning_behavior + var/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/owning_behavior /// The ai controller we're using var/datum/ai_controller/controller /// The target key we're trying to fill var/target_key - /// The targeting strategy KEY we're using - var/targeting_strategy_key + /// The targeting_strategy var value from the owning behavior either a typepath or a BB key string + var/targeting_strategy /// The hiding location key we're using var/hiding_location_key @@ -20,30 +20,31 @@ // Initially, run the check manually // If that fails, set up a field and have it manage the behavior fully -/datum/proximity_monitor/advanced/ai_target_tracking/New(atom/_host, range, _ignore_if_not_on_turf = TRUE, datum/ai_behavior/find_potential_targets/owning_behavior, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) +/datum/proximity_monitor/advanced/ai_target_tracking/New(atom/_host, range, _ignore_if_not_on_turf = TRUE, datum/bt_node/ai_behavior/acquire_target/update_combat_targets/owning_behavior, datum/ai_controller/controller, target_key, targeting_strategy, hiding_location_key) . = ..() src.owning_behavior = owning_behavior src.controller = controller src.target_key = target_key - src.targeting_strategy_key = targeting_strategy_key + src.targeting_strategy = targeting_strategy src.hiding_location_key = hiding_location_key - src.filter = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + + if(ispath(targeting_strategy)) + src.filter = GET_TARGETING_STRATEGY(targeting_strategy) + else + src.filter = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy]) + RegisterSignal(controller, COMSIG_AI_BLACKBOARD_KEY_SET(targeting_strategy), PROC_REF(targeting_datum_changed)) + RegisterSignal(controller, COMSIG_AI_BLACKBOARD_KEY_CLEARED(targeting_strategy), PROC_REF(targeting_datum_cleared)) + RegisterSignal(controller, COMSIG_QDELETING, PROC_REF(controller_deleted)) - RegisterSignal(controller, COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS, PROC_REF(controller_think)) RegisterSignal(controller, COMSIG_AI_CONTROLLER_POSSESSED_PAWN, PROC_REF(pawn_changed)) - RegisterSignal(controller, AI_CONTROLLER_BEHAVIOR_QUEUED(owning_behavior.type), PROC_REF(behavior_requeued)) - RegisterSignal(controller, COMSIG_AI_BLACKBOARD_KEY_SET(targeting_strategy_key), PROC_REF(targeting_datum_changed)) - RegisterSignal(controller, COMSIG_AI_BLACKBOARD_KEY_CLEARED(targeting_strategy_key), PROC_REF(targeting_datum_cleared)) recalculate_field(full_recalc = TRUE) /datum/proximity_monitor/advanced/ai_target_tracking/Destroy() . = ..() - if(!QDELETED(controller) && owning_behavior) - controller.modify_cooldown(owning_behavior, owning_behavior.get_cooldown(controller)) owning_behavior = null controller = null target_key = null - targeting_strategy_key = null + targeting_strategy = null hiding_location_key = null filter = null @@ -75,24 +76,13 @@ SIGNAL_HANDLER qdel(src) -/// React to controller planning -/datum/proximity_monitor/advanced/ai_target_tracking/proc/controller_think(datum/ai_controller/source, list/datum/ai_behavior/old_behaviors, list/datum/ai_behavior/new_behaviors) - SIGNAL_HANDLER - // If our parent was forgotten, nuke ourselves - if(!new_behaviors[owning_behavior]) - qdel(src) - -/datum/proximity_monitor/advanced/ai_target_tracking/proc/behavior_requeued(datum/source, list/new_arguments) - SIGNAL_HANDLER - check_new_args(arglist(new_arguments)) - /// Ensure our args and locals are up to date -/datum/proximity_monitor/advanced/ai_target_tracking/proc/check_new_args(target_key, targeting_strategy_key, hiding_location_key) +/datum/proximity_monitor/advanced/ai_target_tracking/proc/check_new_args(target_key, targeting_strategy, hiding_location_key) var/update_filter = FALSE if(src.target_key != target_key) src.target_key = target_key - if(src.targeting_strategy_key != targeting_strategy_key) - src.targeting_strategy_key = targeting_strategy_key + if(src.targeting_strategy != targeting_strategy) + src.targeting_strategy = targeting_strategy update_filter = TRUE if(src.hiding_location_key != hiding_location_key) src.hiding_location_key = hiding_location_key @@ -101,7 +91,10 @@ /datum/proximity_monitor/advanced/ai_target_tracking/proc/targeting_datum_changed(datum/source) SIGNAL_HANDLER - filter = controller.blackboard[targeting_strategy_key] + if(ispath(targeting_strategy)) + filter = GET_TARGETING_STRATEGY(targeting_strategy) + else + filter = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy]) // Filter changed, need to do a full reparse // Fucking 9 * 9 out here I stg for(var/turf/in_field as anything in field_turfs + edge_turfs) diff --git a/code/datums/proximity_monitor/fields/timestop.dm b/code/datums/proximity_monitor/fields/timestop.dm index 8411e2c3bb3f..92fbf12356ef 100644 --- a/code/datums/proximity_monitor/fields/timestop.dm +++ b/code/datums/proximity_monitor/fields/timestop.dm @@ -223,7 +223,7 @@ hostile_victim.LoseTarget() else if(isbasicmob(victim)) var/mob/living/basic/basic_victim = victim - basic_victim.ai_controller?.set_ai_status(AI_STATUS_OFF) + basic_victim.ai_controller?.force_ai_off() /datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/victim) victim.AdjustStun(-20, ignore_canstun = TRUE) @@ -234,7 +234,7 @@ animal_victim.toggle_ai(initial(animal_victim.AIStatus)) else if(isbasicmob(victim)) var/mob/living/basic/basic_victim = victim - basic_victim.ai_controller?.reset_ai_status() + basic_victim.ai_controller?.clear_forced_off() //you don't look quite right, is something the matter? /datum/proximity_monitor/advanced/timestop/proc/into_the_negative_zone(atom/A) diff --git a/code/datums/status_effects/debuffs/slime/slime_leech.dm b/code/datums/status_effects/debuffs/slime/slime_leech.dm index d1125b2d25d5..85a8a898b14e 100644 --- a/code/datums/status_effects/debuffs/slime/slime_leech.dm +++ b/code/datums/status_effects/debuffs/slime/slime_leech.dm @@ -57,7 +57,8 @@ if(prob(60) && ishuman(owner) && owner.client && !our_slime.ai_controller.blackboard[BB_SLIME_RABID]) our_slime.ai_controller?.set_blackboard_key(BB_SLIME_RABID, TRUE) //we might go rabid after finishing to feed on a human with a client. - our_slime.stop_feeding() + if(our_slime) + our_slime.stop_feeding() return var/totaldamage = 0 //total damage done to this unfortunate soul diff --git a/code/game/machinery/computer/arcade/orion_event.dm b/code/game/machinery/computer/arcade/orion_event.dm index 7c834800f1dd..db5061183b59 100644 --- a/code/game/machinery/computer/arcade/orion_event.dm +++ b/code/game/machinery/computer/arcade/orion_event.dm @@ -526,7 +526,7 @@ playsound(game, 'sound/items/weeoo1.ogg', 100, FALSE) for(var/i in 1 to 3) var/mob/living/basic/trooper/syndicate/ranged/smg/orion/spaceport_security = new(get_turf(game)) - spaceport_security.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, usr) + spaceport_security.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, usr) game.fuel += fuel game.food += food diff --git a/code/game/machinery/dna_scanner.dm b/code/game/machinery/dna_scanner.dm index 769f357740c6..6ad610361ebf 100644 --- a/code/game/machinery/dna_scanner.dm +++ b/code/game/machinery/dna_scanner.dm @@ -15,7 +15,7 @@ var/scan_level var/precision_coeff = 1 var/message_cooldown - var/breakout_time = 1200 + var/breakout_time = 120 SECONDS var/obj/machinery/computer/dna_console/linked_console = null /obj/machinery/dna_scannernew/RefreshParts() @@ -70,16 +70,19 @@ open_machine() /obj/machinery/dna_scannernew/container_resist_act(mob/living/user) - if(!locked) + if(HAS_TRAIT(user, TRAIT_PRIMITIVE) || user.ai_controller) + if(locked) + return //Your primitive brain cant escape a dna scanner noob + else if(!locked) //Not locked and not primitive? escape immediately open_machine() - return + user.changeNext_move(CLICK_CD_BREAKOUT) user.last_special = world.time + CLICK_CD_BREAKOUT user.visible_message(span_notice("You see [user] kicking against the door of [src]!"), \ span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)"), \ span_hear("You hear a metallic creaking from [src].")) if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src || state_open || !locked) + if(!user || user.stat != CONSCIOUS || user.loc != src || state_open || !locked || HAS_TRAIT(user, TRAIT_PRIMITIVE) || user.ai_controller) return locked = FALSE user.visible_message(span_warning("[user] successfully broke out of [src]!"), \ @@ -109,6 +112,8 @@ /obj/machinery/dna_scannernew/open_machine(drop = TRUE, density_to_set = FALSE) if(state_open) return FALSE + if(locked) //haha bro u cant open it its locked xD + return FALSE ..() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 96a29cb10bad..53870d5556a4 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -765,109 +765,12 @@ ADMIN_VERB(clear_smart_asset_cache, R_DEBUG, "Clear Smart Asset Cache", "Clear t cleared++ to_chat(user, span_notice("Cleared [cleared] asset\s.")) -ADMIN_VERB(give_ai_speech, R_FUN, "Give Random AI Speech", ADMIN_VERB_NO_DESCRIPTION, ADMIN_CATEGORY_HIDDEN, mob/living/my_guy) - if (isnull(my_guy.ai_controller)) - var/create_controller = tgui_alert(user, "Target has no AI controller, add one?", "Give AI?", list("Yes", "No")) == "Yes" - if (!create_controller) - return - var/run_with_mind = tgui_alert(user, "Run AI controller while the target has a client?", "Override Client?", list("Yes", "No")) - if (isnull(run_with_mind)) - return - if (QDELETED(my_guy)) - to_chat(user, span_warning("Target ceased to exist.")) - return - my_guy.ai_controller = new /datum/ai_controller/basic_controller/talk(my_guy) - if (run_with_mind == "Yes") - var/datum/ai_controller/guy_controller = my_guy.ai_controller - guy_controller.continue_processing_when_client = TRUE - guy_controller.reset_ai_status() - - var/speech_chance - var/list/spoken_lines - var/list/audible_emotes - var/list/visible_emotes - var/list/sounds - - speech_chance = tgui_input_number(user, "Enter chance per second to say something", "Speech Chance", default = 2, min_value = 0, max_value = 100, round_value = FALSE) - if (isnull(speech_chance)) - return - - var/add_another - var/next_line - - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] spoken line?", "Spoken Lines", list("Yes", "No")) - while (add_another == "Yes") - next_line = tgui_input_text(user, "Enter [length(spoken_lines) ? "another" : "a"] thing spoken out loud.", "Spoken Lines") - if (isnull(next_line)) - return - LAZYADD(spoken_lines, next_line) - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] spoken line?", "Spoken Lines", list("Yes", "No")) - if (isnull(add_another)) - return - - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] emote which people can hear?", "Audible Emotes", list("Yes", "No")) - while (add_another == "Yes") - next_line = tgui_input_text(user, "Enter [length(spoken_lines) ? "another" : "an"] emote which people can hear.", "Audible Emotes") - if (isnull(next_line)) - return - LAZYADD(audible_emotes, next_line) - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] emote which people can hear?", "Audible Emotes", list("Yes", "No")) - if (isnull(add_another)) - return - - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] emote which people can see?", "Visible Emotes", list("Yes", "No")) - while (add_another == "Yes") - next_line = tgui_input_text(user, "Enter [length(spoken_lines) ? "another" : "an"] emote which people can see.", "Visible Emotes") - if (isnull(next_line)) - return - LAZYADD(visible_emotes, next_line) - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] emote which people can see?", "Visible Emotes", list("Yes", "No")) - if (isnull(add_another)) - return - - if (!length(spoken_lines) && !length(audible_emotes) && !length(visible_emotes)) - return // Well you didn't tell it to say anything... - - if (length(spoken_lines) || length(audible_emotes)) - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] sound to play when doing something audible?", "Sounds", list("Yes", "No")) - while (add_another == "Yes") - next_line = input("", "Select sound",) as null|sound - if (isnull(next_line)) - return - LAZYADD(sounds, next_line) - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] sound to play when doing something audible?", "Sounds", list("Yes", "No")) - if (isnull(add_another)) - return - - if (QDELETED(my_guy)) - to_chat(user, span_warning("Target stopped existing.")) - return - - var/datum/ai_controller/our_controller = my_guy.ai_controller - if (length(spoken_lines)) - spoken_lines = string_list(spoken_lines) - if (length(audible_emotes)) - audible_emotes = string_list(audible_emotes) - if (length(visible_emotes)) - visible_emotes = string_list(visible_emotes) - - var/list/emotes = list( - BB_EMOTE_SAY = spoken_lines, - BB_EMOTE_HEAR = audible_emotes, - BB_EMOTE_SEE = visible_emotes, - BB_EMOTE_SOUND = sounds, - BB_SPEAK_CHANCE = speech_chance, - ) - our_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, emotes) - - var/behaviour_exists = !!(locate(/datum/ai_planning_subtree/random_speech/blackboard) in our_controller.planning_subtrees) - if (behaviour_exists) - return - our_controller.planning_subtrees = list(GLOB.ai_subtrees[/datum/ai_planning_subtree/random_speech/blackboard]) + our_controller.planning_subtrees - ADMIN_VERB(open_event_logger, R_DEBUG, "Open Event Logger", "Open the event logger interface.", ADMIN_CATEGORY_DEBUG) GLOB.event_logger.ui_interact(user.mob) +ADMIN_VERB(view_behavior_tree, R_DEBUG, "View Behavior Tree", "Inspect the AI behavior tree of a mob.", ADMIN_CATEGORY_DEBUG) + GLOB.bt_viewer.ui_interact(user.mob) + ADMIN_VERB(new_blackmarket_item, R_BUILD, "Create Black Market Item", "Add an item to the black market for purchase.", ADMIN_CATEGORY_EVENTS, object as text) if(!object) to_chat(user, span_boldwarning("Failed! Provide a full or partial typepath!")) diff --git a/code/modules/antagonists/blob/powers.dm b/code/modules/antagonists/blob/powers.dm index b2cf3808eff1..6939c4d27c4c 100644 --- a/code/modules/antagonists/blob/powers.dm +++ b/code/modules/antagonists/blob/powers.dm @@ -353,7 +353,7 @@ for(var/mob/living/basic/blob_mob as anything in blob_mobs) if(!isturf(blob_mob.loc) || get_dist(blob_mob, tile) > 35 || blob_mob.key) continue - blob_mob.ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + blob_mob.ai_controller.clear_blackboard_key(BB_CURRENT_TARGET) blob_mob.ai_controller.set_blackboard_key(BB_TRAVEL_DESTINATION, pick(surrounding_turfs)) /** Opens the reroll menu to change strains */ diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 0c5efc1a9595..4fa24861d476 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -433,7 +433,7 @@ summoned = mob_to_summon else summoned = new mob_to_summon(loc) - summoned.ai_controller?.set_ai_status(AI_STATUS_OFF) + summoned.ai_controller?.force_ai_off() // Fade in the summon while the ghost poll is ongoing. // Also don't let them mess with the summon while waiting summoned.alpha = 0 @@ -456,6 +456,7 @@ summoned.ghostize(FALSE) summoned.PossessByPlayer(chosen_one.key) + summoned.ai_controller?.clear_forced_off() //the client keeps the AI off from here; if they disconnect the AI may take back over user.log_message("created a [summoned.name], controlled by [key_name(chosen_one)].", LOG_GAME) message_admins("[ADMIN_LOOKUPFLW(user)] created a [summoned.name], [ADMIN_LOOKUPFLW(summoned)].") diff --git a/code/modules/bitrunning/antagonists/netguardian.bt.json b/code/modules/bitrunning/antagonists/netguardian.bt.json new file mode 100644 index 000000000000..794640d5a971 --- /dev/null +++ b/code/modules/bitrunning/antagonists/netguardian.bt.json @@ -0,0 +1,110 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/netguardian", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_NETGUARDIAN_ROCKET_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/avoid_friendly_fire", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "time_between_perform": "1 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/bitrunning/antagonists/netguardian.dm b/code/modules/bitrunning/antagonists/netguardian.dm index 80bba1c050a6..d05d249733f7 100644 --- a/code/modules/bitrunning/antagonists/netguardian.dm +++ b/code/modules/bitrunning/antagonists/netguardian.dm @@ -113,29 +113,10 @@ return TRUE /datum/ai_controller/basic_controller/netguardian + behavior_tree_json = "code/modules/bitrunning/antagonists/netguardian.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/simple_find_wounded_target, - /datum/ai_planning_subtree/targeted_mob_ability/fire_rockets, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/netguardian, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/netguardian - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/netguardian - -/datum/ai_behavior/basic_ranged_attack/netguardian - action_cooldown = 1 SECONDS - avoid_friendly_fire = TRUE -/datum/ai_planning_subtree/targeted_mob_ability/fire_rockets - ability_key = BB_NETGUARDIAN_ROCKET_ABILITY - finish_planning = FALSE diff --git a/code/modules/bitrunning/virtual_domain/domains/crewman.bt.json b/code/modules/bitrunning/virtual_domain/domains/crewman.bt.json new file mode 100644 index 000000000000..7fd6dd5e23b5 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/crewman.bt.json @@ -0,0 +1,118 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fake_crewman", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.json b/code/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.json new file mode 100644 index 000000000000..29c836e25cdd --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.json @@ -0,0 +1,118 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fake_crewman/instant_hostile", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.json b/code/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.json new file mode 100644 index 000000000000..d50e13f3197a --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.json @@ -0,0 +1,129 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fake_crewman/instant_hostile/ranged", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.json b/code/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.json new file mode 100644 index 000000000000..beaf56a6d789 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.json @@ -0,0 +1,129 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fake_crewman/ranged", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm b/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm index 77a4fcfed605..3dee62028c23 100644 --- a/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm +++ b/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm @@ -78,54 +78,26 @@ user.AddElement(/datum/element/rust_healing) user.add_faction(FACTION_HERETIC) -// All it does is stand there, only attacks if attacked (Manuel player) +// All it does is stand there, only attacks if attacked (Manuel player) (TODO: make them ahelp to really simulate manuel players) /datum/ai_controller/basic_controller/fake_crewman + behavior_tree_json = "code/modules/bitrunning/virtual_domain/domains/crewman.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, BB_REINFORCEMENTS_SAY = "Help me!", + BB_CALLS_REINFORCEMENTS = TRUE, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /datum/ai_controller/basic_controller/fake_crewman/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.json" // Immediately tries to attack the player (Terry player) /datum/ai_controller/basic_controller/fake_crewman/instant_hostile - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.json" /datum/ai_controller/basic_controller/fake_crewman/instant_hostile/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.json" // The actual crewmate /mob/living/basic/fake_crewman diff --git a/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm b/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm index f0b0b3331f70..ee70b0611937 100644 --- a/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm +++ b/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm @@ -314,7 +314,7 @@ var/mob/living/basic/mining/demon_afterimage/crusher/friend = new(drop_off) friend.set_faction(list(FACTION_NEUTRAL)) friend.befriend(user) - friend.ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target) + friend.ai_controller?.set_blackboard_key(BB_CURRENT_TARGET, target) COOLDOWN_START(src, summon_cooldown, 30 SECONDS) ///try to make them spawn all around the target to surround him diff --git a/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm b/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm index e91b2b07f515..6edec34d3d90 100644 --- a/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm +++ b/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm @@ -244,7 +244,7 @@ return var/mob/living/basic/mining/legion_brood/minion = new (user.loc) minion.assign_creator(user) - minion.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target) + minion.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, target) /obj/item/crusher_trophy/legionnaire_spine/attack_self(mob/user) if(!isliving(user)) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index d61dcd47c5ee..777771fd6043 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -194,6 +194,7 @@ if(QDELETED(I)) // this is here because some ABSTRACT items like slappers and circle hands could be moved from hand to hand then delete, which meant you'd have a null in your hand until you cleared it (say, by dropping it) held_items[hand_index] = null return FALSE + SEND_SIGNAL(I, COMSIG_ITEM_ENTERED_HANDS, src, hand_index) return hand_index //Puts the item into the first available left hand if possible and calls all necessary triggers/updates. returns 1 on success. diff --git a/code/modules/mob/living/basic/alien/_alien.dm b/code/modules/mob/living/basic/alien/_alien.dm index af0bd3491722..e76c7fec11b0 100644 --- a/code/modules/mob/living/basic/alien/_alien.dm +++ b/code/modules/mob/living/basic/alien/_alien.dm @@ -67,23 +67,25 @@ /mob/living/basic/alien/get_butt_sprite() return icon('icons/mob/butts.dmi', BUTT_SPRITE_XENOMORPH) -///Places alien weeds on the turf the mob is currently standing on. +///Places alien weeds on the turf the mob is currently standing on. Returns TRUE if weeds were placed. /mob/living/basic/alien/proc/place_weeds() if(!isturf(loc) || isspaceturf(loc)) - return + return FALSE if(locate(/obj/structure/alien/weeds/node) in get_turf(src)) - return + return FALSE visible_message(span_alertalien("[src] plants some alien weeds!")) new /obj/structure/alien/weeds/node(loc) + return TRUE -///Lays an egg on the turf the mob is currently standing on. +///Lays an egg on the turf the mob is currently standing on. Returns TRUE if an egg was laid. /mob/living/basic/alien/proc/lay_alien_egg() if(!isturf(loc) || isspaceturf(loc)) - return + return FALSE if(locate(/obj/structure/alien/egg) in get_turf(src)) - return + return FALSE visible_message(span_alertalien("[src] lays an egg!")) new /obj/structure/alien/egg(loc) + return TRUE /mob/living/basic/alien/get_bloodtype() return get_blood_type(BLOOD_TYPE_XENO) diff --git a/code/modules/mob/living/basic/alien/alien.bt.json b/code/modules/mob/living/basic/alien/alien.bt.json new file mode 100644 index 000000000000..5c8713ca333d --- /dev/null +++ b/code/modules/mob/living/basic/alien/alien.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/alien", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_alien", + "bindings": { + "buewuqr6": "/datum/bt_node/subtree/melee_alien_combat", + "bkzbx73d": "/datum/bt_node/subtree/lay_alien_egg" + } +} diff --git a/code/modules/mob/living/basic/alien/alien_ai.dm b/code/modules/mob/living/basic/alien/alien_ai.dm index decca14cec86..2e31eed32635 100644 --- a/code/modules/mob/living/basic/alien/alien_ai.dm +++ b/code/modules/mob/living/basic/alien/alien_ai.dm @@ -1,76 +1,57 @@ /datum/ai_controller/basic_controller/alien + behavior_tree_json = "code/modules/mob/living/basic/alien/alien.bt.json" ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, + BB_RANGED_SKIRMISH_MIN_DISTANCE = 2, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 3, ) movement_delay = 0.8 SECONDS /datum/ai_controller/basic_controller/alien/sentinel - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/alien, - ) + behavior_tree_json = "code/modules/mob/living/basic/alien/sentinel.bt.json" + /datum/ai_controller/basic_controller/alien/drone - idle_behavior = /datum/idle_behavior/idle_random_walk/plant_weeds + behavior_tree_json = "code/modules/mob/living/basic/alien/drone.bt.json" /datum/ai_controller/basic_controller/alien/queen - idle_behavior = /datum/idle_behavior/idle_random_walk/plant_weeds/queen + behavior_tree_json = "code/modules/mob/living/basic/alien/queen.bt.json" - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/alien, - ) -/** - * Alien projectile - * Try to avoid friendly fire, and has a 3 second delay. - */ -/datum/ai_planning_subtree/basic_ranged_attack_subtree/alien - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/alien - -/datum/ai_behavior/basic_ranged_attack/alien - action_cooldown = 3 SECONDS - required_distance = 3 - avoid_friendly_fire = TRUE - -/datum/idle_behavior/idle_random_walk/plant_weeds - var/plant_cooldown = 30 - var/plants_off = 0 - -/datum/idle_behavior/idle_random_walk/plant_weeds/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - if(!.) - return . - plant_cooldown-- +/datum/bt_node/subtree/basic_alien + behavior_tree_json = "code/modules/mob/living/basic/alien/basic_alien.bt.json" + + +/datum/bt_node/subtree/ranged_alien_combat + behavior_tree_json = "code/modules/mob/living/basic/alien/ranged_alien_combat.bt.json" + + +/datum/bt_node/subtree/melee_alien_combat + behavior_tree_json = "code/modules/mob/living/basic/alien/melee_alien_combat.bt.json" + +/datum/bt_node/subtree/plant_alien_weeds + behavior_tree_json = "code/modules/mob/living/basic/alien/plant_alien_weeds.bt.json" + +/datum/bt_node/subtree/lay_alien_egg + behavior_tree_json = "code/modules/mob/living/basic/alien/lay_alien_egg.bt.json" + + +/// Plants alien weeds on the pawn's current turf. Fails if the pawn can't plant or weeds couldn't be placed. +/datum/bt_node/ai_behavior/plant_alien_weeds + +/datum/bt_node/ai_behavior/plant_alien_weeds/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/alien/alien_pawn = controller.pawn - if(alien_pawn.can_plant_weeds && !plants_off && plant_cooldown <= 0) - plant_cooldown = initial(plant_cooldown) - alien_pawn.place_weeds() - -/datum/idle_behavior/idle_random_walk/plant_weeds/queen - var/eggs_off = 0 - var/egg_cooldown = 30 - -/datum/idle_behavior/idle_random_walk/plant_weeds/queen/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - if(!.) - return . - egg_cooldown-- + if(!alien_pawn.can_plant_weeds || !alien_pawn.place_weeds()) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Lays an alien egg on the pawn's current turf. Fails if the pawn can't lay eggs or an egg couldn't be placed. +/datum/bt_node/ai_behavior/lay_alien_egg + +/datum/bt_node/ai_behavior/lay_alien_egg/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/alien/alien_pawn = controller.pawn - if(alien_pawn.can_lay_eggs && !eggs_off && egg_cooldown <= 0) - egg_cooldown = initial(egg_cooldown) - alien_pawn.lay_alien_egg() + if(!alien_pawn.can_lay_eggs || !alien_pawn.lay_alien_egg()) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/modules/mob/living/basic/alien/basic_alien.bt.json b/code/modules/mob/living/basic/alien/basic_alien.bt.json new file mode 100644 index 000000000000..c9163e6f7751 --- /dev/null +++ b/code/modules/mob/living/basic/alien/basic_alien.bt.json @@ -0,0 +1,60 @@ +{ + "dm_type": "/datum/bt_node/subtree/basic_alien", + "bindings": { + "buewuqr6": { + "label": "Combat Subtree", + "default": "/datum/bt_node/subtree" + }, + "bkzbx73d": { + "label": "Idle Behavior", + "default": "/datum/bt_node/subtree/random_walk" + } + }, + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "$buewuqr6" + } + }, + { + "type": "subtree", + "subtype": "$bkzbx73d" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/alien/drone.bt.json b/code/modules/mob/living/basic/alien/drone.bt.json new file mode 100644 index 000000000000..7763e2fcd9b4 --- /dev/null +++ b/code/modules/mob/living/basic/alien/drone.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/alien/drone", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_alien", + "bindings": { + "buewuqr6": "/datum/bt_node/subtree/melee_alien_combat", + "bkzbx73d": "/datum/bt_node/ai_behavior/plant_alien_weeds" + } +} diff --git a/code/modules/mob/living/basic/alien/lay_alien_egg.bt.json b/code/modules/mob/living/basic/alien/lay_alien_egg.bt.json new file mode 100644 index 000000000000..1cb4f5336b0d --- /dev/null +++ b/code/modules/mob/living/basic/alien/lay_alien_egg.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/bt_node/subtree/lay_alien_egg", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_ALIEN_PLANT_COOLDOWN", + "cooldown_duration": "30 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/lay_alien_egg" + } +} diff --git a/code/modules/mob/living/basic/alien/melee_alien_combat.bt.json b/code/modules/mob/living/basic/alien/melee_alien_combat.bt.json new file mode 100644 index 000000000000..51a646244426 --- /dev/null +++ b/code/modules/mob/living/basic/alien/melee_alien_combat.bt.json @@ -0,0 +1,47 @@ +{ + "dm_type": "/datum/bt_node/subtree/melee_alien_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] +} diff --git a/code/modules/mob/living/basic/alien/plant_alien_weeds.bt.json b/code/modules/mob/living/basic/alien/plant_alien_weeds.bt.json new file mode 100644 index 000000000000..a8bb21032ce6 --- /dev/null +++ b/code/modules/mob/living/basic/alien/plant_alien_weeds.bt.json @@ -0,0 +1,23 @@ +{ + "dm_type": "/datum/bt_node/subtree/plant_alien_weeds", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_ALIEN_PLANT_COOLDOWN", + "cooldown_duration": "30 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/plant_alien_weeds" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] +} diff --git a/code/modules/mob/living/basic/alien/queen.bt.json b/code/modules/mob/living/basic/alien/queen.bt.json new file mode 100644 index 000000000000..d4570cee74f1 --- /dev/null +++ b/code/modules/mob/living/basic/alien/queen.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/alien/queen", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_alien", + "bindings": { + "buewuqr6": "/datum/bt_node/subtree/ranged_alien_combat", + "bkzbx73d": "/datum/bt_node/ai_behavior/lay_alien_egg" + } +} diff --git a/code/modules/mob/living/basic/alien/ranged_alien_combat.bt.json b/code/modules/mob/living/basic/alien/ranged_alien_combat.bt.json new file mode 100644 index 000000000000..47f467ed9427 --- /dev/null +++ b/code/modules/mob/living/basic/alien/ranged_alien_combat.bt.json @@ -0,0 +1,48 @@ +{ + "dm_type": "/datum/bt_node/subtree/ranged_alien_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "time_between_perform": "3 SECONDS", + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "avoid_friendly_fire": true + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "approach_movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] +} diff --git a/code/modules/mob/living/basic/alien/sentinel.bt.json b/code/modules/mob/living/basic/alien/sentinel.bt.json new file mode 100644 index 000000000000..a0e73fda01a9 --- /dev/null +++ b/code/modules/mob/living/basic/alien/sentinel.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/alien/sentinel", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_alien", + "bindings": { + "buewuqr6": "/datum/bt_node/subtree/ranged_alien_combat" + } +} diff --git a/code/modules/mob/living/basic/blob_minions/blob_ai.dm b/code/modules/mob/living/basic/blob_minions/blob_ai.dm index b8368fc7f3ca..208d38dee9b5 100644 --- a/code/modules/mob/living/basic/blob_minions/blob_ai.dm +++ b/code/modules/mob/living/basic/blob_minions/blob_ai.dm @@ -3,55 +3,34 @@ * Only notable quirk is that it uses JPS movement, simple avoidance would fail to realise it can path through blobs */ /datum/ai_controller/basic_controller/blobbernaut + behavior_tree_json = "code/modules/mob/living/basic/blob_minions/blobbernaut.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/jps - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /** * Move to a point designated by the overmind, otherwise just slap people nearby */ /datum/ai_controller/basic_controller/blob_zombie + behavior_tree_json = "code/modules/mob/living/basic/blob_minions/blob_zombie.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/jps - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/travel_to_point/and_clear_target, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /** * As blob zombie but will prioritise attacking corpses to zombify them */ /datum/ai_controller/basic_controller/blob_spore + behavior_tree_json = "code/modules/mob/living/basic/blob_minions/blob_spore.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/jps - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/find_and_hunt_target/corpses/human, - /datum/ai_planning_subtree/travel_to_point/and_clear_target, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/blob_minions/blob_spore.bt.json b/code/modules/mob/living/basic/blob_minions/blob_spore.bt.json new file mode 100644 index 000000000000..17d02701525a --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_spore.bt.json @@ -0,0 +1,69 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/blob_spore", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bvtz06kb": "BB_CURRENT_HUNTING_TARGET", + "b3y599q4": "BB_CURRENT_HUNTING_TARGET", + "brrasnah": "BB_CURRENT_HUNTING_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_TRAVEL_DESTINATION" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TRAVEL_DESTINATION", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TRAVEL_DESTINATION" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "targeting_strategy": "/datum/targeting_strategy/dead_mob", + "target_source": "/datum/target_source/oview_single_type/human_mob" + } + } + ] +} diff --git a/code/modules/mob/living/basic/blob_minions/blob_zombie.bt.json b/code/modules/mob/living/basic/blob_minions/blob_zombie.bt.json new file mode 100644 index 000000000000..7d2cf1b5b58a --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_zombie.bt.json @@ -0,0 +1,42 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/blob_zombie", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_TRAVEL_DESTINATION" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TRAVEL_DESTINATION", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TRAVEL_DESTINATION" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/blob_minions/blobbernaut.bt.json b/code/modules/mob/living/basic/blob_minions/blobbernaut.bt.json new file mode 100644 index 000000000000..89e67315111b --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blobbernaut.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/blobbernaut", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/boss/blood_drunk_miner/_blood_drunk_miner.dm b/code/modules/mob/living/basic/boss/blood_drunk_miner/_blood_drunk_miner.dm index a251db94200a..682d24646b5d 100644 --- a/code/modules/mob/living/basic/boss/blood_drunk_miner/_blood_drunk_miner.dm +++ b/code/modules/mob/living/basic/boss/blood_drunk_miner/_blood_drunk_miner.dm @@ -68,11 +68,10 @@ Difficulty: Medium grant_actions_by_list(get_innate_actions()) ai_controller.set_blackboard_key(BB_BDM_RANGED_ATTACK_COOLDOWN, ranged_attack_cooldown_duration) - RegisterSignals(ai_controller, list(AI_CONTROLLER_BEHAVIOR_QUEUED(/datum/ai_behavior/basic_melee_attack), AI_CONTROLLER_BEHAVIOR_QUEUED(/datum/ai_behavior/targeted_mob_ability)), PROC_REF(handle_saw_transformation)) RegisterSignal(src, COMSIG_LIVING_DROP_LOOT, PROC_REF(death_effect)) - AddComponent(/datum/component/boss_music, 'sound/music/boss/bdm_boss.ogg', COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET)) + AddComponent(/datum/component/boss_music, 'sound/music/boss/bdm_boss.ogg', COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET)) /// Block deletion of their saw under normal circumstances. It is fused to their hands as far as we're concerned. /mob/living/basic/boss/blood_drunk_miner/proc/on_saw_deleted(datum/source, force) @@ -99,12 +98,6 @@ Difficulty: Medium ) return innate_abilities -/// Invokes the transform weapon ability when signaled by the AI controller. -/mob/living/basic/boss/blood_drunk_miner/proc/handle_saw_transformation() - SIGNAL_HANDLER - - INVOKE_ASYNC(ai_controller.blackboard[BB_BDM_TRANSFORM_WEAPON_ABILITY], TYPE_PROC_REF(/datum/action, Trigger), src, NONE) - /mob/living/basic/boss/blood_drunk_miner/proc/transform_saw() miner_saw.attack_self(src) var/saw_open = HAS_TRAIT(miner_saw, TRAIT_TRANSFORM_ACTIVE) diff --git a/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_ai.dm b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_ai.dm index 64efdb0b8a96..41c22c242f8c 100644 --- a/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_ai.dm +++ b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_ai.dm @@ -5,6 +5,7 @@ /// - If in melee range, use melee attacks (depending on saw state) /// - After attacks, transform saw state from open to closed. /datum/ai_controller/blood_drunk_miner + behavior_tree_json = "code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/no_gutted_mobs, BB_TARGET_MINIMUM_STAT = DEAD, @@ -14,53 +15,10 @@ movement_delay = 0.25 SECONDS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/shoot_pka, - /datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/dash_attack, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/// Parent type that contains key logic important for subsequent abilities -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk - /// Range where we determine what distance we're at. If higher, we consider ourselves out of PKA range and will dash attack instead. Inclusive when it comes to choosing to shoot PKA. - var/pka_range = 3 - -/// Check our blackboard to see if we are able to use a ranged ability in the first place -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/additional_ability_checks(datum/ai_controller/controller, datum/action/cooldown/using_action) - . = ..() - if(controller.blackboard[BB_BDM_RANGED_ATTACK_COOLDOWN] > world.time) - return FALSE - controller.override_blackboard_key(BB_BDM_RANGED_ATTACK_COOLDOWN, world.time + controller.blackboard[BB_BDM_RANGED_ATTACK_COOLDOWN_DURATION]) - -/// The BDM will preferentially shoot its PKA within range over other abilities -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/shoot_pka - ability_key = BB_BDM_KINETIC_ACCELERATOR_ABILITY - -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/shoot_pka/additional_ability_checks(datum/ai_controller/controller, datum/action/cooldown/using_action) - . = ..() - // do not shoot the PKA if we are not in the right range - var/mob/living/pawn = controller.pawn - var/mob/living/victim = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(get_dist(pawn, victim) >= pka_range) - return FALSE - return TRUE - -/// The BDM will dash attack if not in PKA range -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/dash_attack - ability_key = BB_BDM_DASH_ATTACK_ABILITY - -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/dash_attack/additional_ability_checks(datum/ai_controller/controller, datum/action/cooldown/using_action) - . = ..() - // only dash attack if we are out of PKA range - var/mob/living/pawn = controller.pawn - var/mob/living/victim = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(get_dist(pawn, victim) < pka_range) - return FALSE - return TRUE /datum/ai_controller/blood_drunk_miner/doom movement_delay = 0.5 SECONDS + + +/datum/bt_node/subtree/blood_drunk_miner_combat + behavior_tree_json = "code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.json" diff --git a/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.json b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.json new file mode 100644 index 000000000000..f6c7192c71b3 --- /dev/null +++ b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.json @@ -0,0 +1,45 @@ +{ + "dm_type": "/datum/ai_controller/blood_drunk_miner", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/blood_drunk_miner_combat" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": 0 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.json b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.json new file mode 100644 index 000000000000..ad023b0ae2ad --- /dev/null +++ b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.json @@ -0,0 +1,132 @@ +{ + "dm_type": "/datum/bt_node/subtree/blood_drunk_miner_combat", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/mob_stat_at_least", + "vars": { + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_BDM_KINETIC_ACCELERATOR_ABILITY", + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 3 + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_BDM_TRANSFORM_WEAPON_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "invert": false, + "target_key": "BB_CURRENT_TARGET", + "min_distance": 3 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_BDM_DASH_ATTACK_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_BDM_TRANSFORM_WEAPON_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/boss/boss.dm b/code/modules/mob/living/basic/boss/boss.dm index 8f4a49248009..8a5cf5e81595 100644 --- a/code/modules/mob/living/basic/boss/boss.dm +++ b/code/modules/mob/living/basic/boss/boss.dm @@ -81,7 +81,8 @@ return if(should_devour(target)) devour(target) - return BASIC_MOB_END_ATTACK_CHAIN_COOLDOWN + return BASIC_MOB_END_ATTACK_CHAIN_COOLDOWN + return /// Determines if this mob is worth devouring /mob/living/basic/boss/proc/should_devour(mob/living/victim) diff --git a/code/modules/mob/living/basic/boss/thing/thing.dm b/code/modules/mob/living/basic/boss/thing/thing.dm index 02943669ff24..a1d2167e2411 100644 --- a/code/modules/mob/living/basic/boss/thing/thing.dm +++ b/code/modules/mob/living/basic/boss/thing/thing.dm @@ -69,8 +69,8 @@ if(!maploaded) return spawn_loc = loc - RegisterSignal(src, COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(target_gained)) - RegisterSignal(src, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(target_lost)) + RegisterSignal(src, COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET), PROC_REF(target_gained)) + RegisterSignal(src, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_CURRENT_TARGET), PROC_REF(target_lost)) SSqueuelinks.add_to_queue(src, RUIN_QUEUE, 0) return INITIALIZE_HINT_LATELOAD @@ -179,9 +179,9 @@ /// Immediately set out blackboard target key (if empty) to whoever attacks us; this is primarily because it has a lowered aggro range and a high sight range /mob/living/basic/boss/thing/proc/immediate_aggro(datum/source, mob/attacker, flags) SIGNAL_HANDLER - if(isnull(ai_controller) || stat || !istype(attacker) || ai_controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + if(isnull(ai_controller) || stat || !istype(attacker) || ai_controller.blackboard_key_exists(BB_CURRENT_TARGET)) return - ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, attacker) + ai_controller?.set_blackboard_key(BB_CURRENT_TARGET, attacker) /mob/living/basic/boss/thing/vv_edit_var(vname, vval) . = ..() @@ -303,7 +303,7 @@ /// queue id var/queue_id = RUIN_QUEUE /// blackboard key for target - var/target_bb_key = BB_BASIC_MOB_CURRENT_TARGET + var/target_bb_key = BB_CURRENT_TARGET /obj/structure/aggro_gate/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/basic/boss/thing/thing_ai.dm b/code/modules/mob/living/basic/boss/thing/thing_ai.dm index e8956cac2b13..55668658db2f 100644 --- a/code/modules/mob/living/basic/boss/thing/thing_ai.dm +++ b/code/modules/mob/living/basic/boss/thing/thing_ai.dm @@ -1,72 +1,17 @@ /datum/ai_controller/basic_controller/thing_boss + behavior_tree_json = "code/modules/mob/living/basic/boss/thing/thing_boss.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/no_gutted_mobs, BB_TARGET_MINIMUM_STAT = DEAD, // Will attack dead ungutted mobs - BB_THETHING_ATTACKMODE = TRUE, //Whether we are using our melee abilities right now + BB_THETHING_MELEEMODE = TRUE, //Whether we are using our melee abilities right now BB_THETHING_NOAOE = TRUE, // Restricts us to only melee abilities BB_THETHING_LASTAOE = null, // Last AOE ability key executed - BB_AGGRO_RANGE = 16, - BB_AGGRO_GRAB_RANGE = 6, ) ai_movement = /datum/ai_movement/basic_avoidance // dont need anything better because the arena is a square lol - idle_behavior = null - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/thing_boss_aoe, - /datum/ai_planning_subtree/thing_boss_melee, - ) - -/datum/ai_planning_subtree/thing_boss_aoe/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) - var/mob/living/pawn = controller.pawn - if(HAS_TRAIT_FROM(pawn, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT) || (controller.blackboard[BB_THETHING_ATTACKMODE] || controller.blackboard[BB_THETHING_NOAOE])) - return - // our target - var/mob/living/shaft_miner = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(shaft_miner) || shaft_miner.stat == DEAD) //Dont use abilities on off z level targets, or dead shaft miners. We want to melee those. - return - - controller.set_blackboard_key(BB_THETHING_ATTACKMODE, TRUE) // putting this here so we go to melee mode if we cant do any aoe - var/static/list/aoe_attacks = list(BB_THETHING_DECIMATE, BB_THETHING_BIGTENDRILS, BB_THETHING_CARDTENDRILS, BB_THETHING_ACIDSPIT) - var/list/possible_attacks = aoe_attacks.Copy() - controller.blackboard[BB_THETHING_LASTAOE] - for(var/bb_action_key in possible_attacks) - var/datum/action/action = controller.blackboard[bb_action_key] - if(!action?.IsAvailable()) - possible_attacks -= bb_action_key - if(!length(possible_attacks)) - return - var/current_aoe_key = pick(possible_attacks) - controller.set_blackboard_key(BB_THETHING_LASTAOE, current_aoe_key) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability, current_aoe_key, BB_BASIC_MOB_CURRENT_TARGET) - if(prob(60) && shaft_miner.body_position != LYING_DOWN) //potential follow-up - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability, BB_THETHING_CHARGE, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - -/datum/ai_planning_subtree/thing_boss_melee/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) - var/mob/living/pawn = controller.pawn - if(HAS_TRAIT_FROM(pawn, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT)) - return - - // our target - var/mob/living/shaft_miner = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(shaft_miner)) - return - var/target_dist = get_dist(pawn, shaft_miner) - - var/datum/action/shriek = controller.blackboard[BB_THETHING_SHRIEK] - var/datum/action/charge = controller.blackboard[BB_THETHING_CHARGE] - if(isnull(shriek) || isnull(charge)) - return // pray this never occurs - - controller.set_blackboard_key(BB_THETHING_ATTACKMODE, FALSE) - if(shriek.IsAvailable() && target_dist <= 2 && shaft_miner.stat != DEAD) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/min_range/short, BB_THETHING_SHRIEK, BB_BASIC_MOB_CURRENT_TARGET) - return - else if(charge.IsAvailable() && target_dist >= 5) // While we cant hit prone targets, this helps to close the distance. - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability, BB_THETHING_CHARGE, BB_BASIC_MOB_CURRENT_TARGET) - return +/datum/bt_node/subtree/thing_aoe + behavior_tree_json = "code/modules/mob/living/basic/boss/thing/thing_aoe.bt.json" - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) +/datum/bt_node/subtree/thing_melee + behavior_tree_json = "code/modules/mob/living/basic/boss/thing/thing_melee.bt.json" diff --git a/code/modules/mob/living/basic/boss/thing/thing_aoe.bt.json b/code/modules/mob/living/basic/boss/thing/thing_aoe.bt.json new file mode 100644 index 000000000000..43fdef79c1a3 --- /dev/null +++ b/code/modules/mob/living/basic/boss/thing/thing_aoe.bt.json @@ -0,0 +1,72 @@ +{ + "dm_type": "/datum/bt_node/subtree/thing_aoe", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_THETHING_NOAOE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_THETHING_MELEEMODE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_has_trait_from", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": true, + "trait": "TRAIT_IMMOBILIZED", + "source": "MEGAFAUNA_TRAIT" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_THETHING_MELEEMODE", + "value": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_random_ability", + "vars": { + "ability_keys": "list(BB_THETHING_DECIMATE, BB_THETHING_BIGTENDRILS, BB_THETHING_CARDTENDRILS, BB_THETHING_ACIDSPIT)", + "last_used_key": "BB_THETHING_LASTAOE", + "result_key": "BB_GENERIC_ACTION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_GENERIC_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.6 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_THETHING_CHARGE", + "target_key": "BB_CURRENT_TARGET" + } + } + } + ] + } + } + } +} diff --git a/code/modules/mob/living/basic/boss/thing/thing_boss.bt.json b/code/modules/mob/living/basic/boss/thing/thing_boss.bt.json new file mode 100644 index 000000000000..e681d7518513 --- /dev/null +++ b/code/modules/mob/living/basic/boss/thing/thing_boss.bt.json @@ -0,0 +1,70 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/thing_boss", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/thing_aoe" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/thing_melee" + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": 0 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "vision_range": 6 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/boss/thing/thing_melee.bt.json b/code/modules/mob/living/basic/boss/thing/thing_melee.bt.json new file mode 100644 index 000000000000..96abd197f262 --- /dev/null +++ b/code/modules/mob/living/basic/boss/thing/thing_melee.bt.json @@ -0,0 +1,89 @@ +{ + "dm_type": "/datum/bt_node/subtree/thing_melee", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/true_for_time", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "duration": "5 SECONDS" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_THETHING_MELEEMODE", + "value": false + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_THETHING_SHRIEK", + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 2 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "min_distance": 3 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_THETHING_CHARGE", + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_has_trait_from", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": true, + "trait": "TRAIT_IMMOBILIZED", + "source": "MEGAFAUNA_TRAIT" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] + } + ] + } +} diff --git a/code/modules/mob/living/basic/bots/bot.bt.json b/code/modules/mob/living/basic/bots/bot.bt.json new file mode 100644 index 000000000000..b1cc876173b4 --- /dev/null +++ b/code/modules/mob/living/basic/bots/bot.bt.json @@ -0,0 +1,22 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] +} diff --git a/code/modules/mob/living/basic/bots/bot_ai.dm b/code/modules/mob/living/basic/bots/bot_ai.dm index a5f8202e66e7..42ff9f2715d0 100644 --- a/code/modules/mob/living/basic/bots/bot_ai.dm +++ b/code/modules/mob/living/basic/bots/bot_ai.dm @@ -1,6 +1,5 @@ -#define BOT_NO_BEACON_PATH_PENALTY 30 SECONDS - /datum/ai_controller/basic_controller/bot + behavior_tree_json = "code/modules/mob/living/basic/bots/bot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_SALUTE_MESSAGES = list( @@ -11,24 +10,16 @@ ) ai_movement = /datum/ai_movement/jps/bot - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon, - ) max_target_distance = AI_BOT_PATH_LENGTH - can_idle = FALSE - ///minimum distance we need to be from our target in path calculations - var/minimum_distance = 0 - ///keys to be reset when the bot is reseted + ai_traits = DEFAULT_AI_FLAGS | RUN_WHILE_UNWATCHED + ///keys to be reset when the bot is reset var/list/reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, ) -/datum/targeting_strategy/basic/bot/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/bot/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) var/datum/ai_controller/basic_controller/bot/my_controller = living_mob.ai_controller if(isnull(my_controller)) return FALSE @@ -39,9 +30,8 @@ return FALSE if(get_turf(living_mob) == get_turf(living_target)) return ..() - var/list/path = get_path_to(living_mob, living_target, mintargetdist = my_controller.minimum_distance, max_distance = 10, access = my_controller.get_access()) - if(!length(path) || QDELETED(living_mob)) - my_controller?.add_to_blacklist(living_target) + if(!my_controller.can_reach_target(living_target, distance = 10)) + my_controller.add_to_blacklist(living_target) return FALSE return ..() @@ -56,7 +46,7 @@ /datum/ai_controller/basic_controller/bot/proc/on_movement_start(mob/living/basic/bot/source, atom/target) SIGNAL_HANDLER - if(current_movement_target == blackboard[BB_BEACON_TARGET]) + if(target == blackboard[BB_BEACON_TARGET]) source.update_bot_mode(new_mode = BOT_PATROL) return @@ -69,6 +59,10 @@ set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, target, TRUE) addtimer(CALLBACK(src, PROC_REF(remove_from_blacklist), target), final_duration) +/// Unreachable targets get added to the temporary ignore list so we stop pathing to them. Subtypes override to skip blacklisting in stationary mode. +/datum/ai_controller/basic_controller/bot/note_unreachable_target(atom/target) + add_to_blacklist(target) + /datum/ai_controller/basic_controller/bot/proc/remove_from_blacklist(atom/target) if(QDELETED(target)) return @@ -100,15 +94,15 @@ /datum/ai_controller/basic_controller/bot/proc/reset_bot() SIGNAL_HANDLER - CancelActions() + cancel_current_plan() if(!length(reset_keys)) return for(var/key in reset_keys) clear_blackboard_key(key) ///set the target if we can reach them -/datum/ai_controller/basic_controller/bot/proc/set_if_can_reach(key, target, duration, distance = 10, bypass_add_to_blacklist = FALSE) - if(can_reach_target(target, distance)) +/datum/ai_controller/basic_controller/bot/proc/set_if_can_reach(key, target, duration, distance = 10, bypass_add_to_blacklist = FALSE, minimum_distance = 0) + if(can_reach_target(target, distance, minimum_distance)) EVLOG_MAPTEXT(src, EVLOG_CATEGORY_AI_TARGETING, "[pawn] has selected [target] as a target for blackboard key [key]!", get_turf(target), "Target: [target]") EVLOG_LINES(src, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(pawn), get_turf(target)) set_blackboard_key(key, target) @@ -120,252 +114,3 @@ EVLOG_LINES(src, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(pawn), get_turf(target)) add_to_blacklist(target, final_duration) return FALSE - -/datum/ai_controller/basic_controller/bot/proc/can_reach_target(target, distance = 10) - if(!isdatum(target)) //we dont need to check if its not a datum! - return TRUE - if(get_turf(pawn) == get_turf(target)) - return TRUE - var/list/path = get_path_to(pawn, target, simulated_only = !HAS_TRAIT(pawn, TRAIT_SPACEWALK), mintargetdist = minimum_distance, max_distance = distance, access = get_access()) - return (!!length(path)) - -/datum/ai_planning_subtree/find_patrol_beacon - ///travel towards beacon behavior - var/travel_behavior = /datum/ai_behavior/travel_towards/beacon - -/datum/ai_planning_subtree/find_patrol_beacon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - - if(controller.blackboard[BB_BOT_BEACON_COOLDOWN] > world.time) - return - - if(!(bot_pawn.bot_mode_flags & BOT_MODE_AUTOPATROL) || bot_pawn.mode == BOT_SUMMON) - return - - if(controller.blackboard_key_exists(BB_BEACON_TARGET)) - controller.queue_behavior(travel_behavior, BB_BEACON_TARGET) - return - - if(controller.blackboard_key_exists(BB_PREVIOUS_BEACON_TARGET)) - controller.queue_behavior(/datum/ai_behavior/find_next_beacon_target, BB_BEACON_TARGET) - return - - controller.queue_behavior(/datum/ai_behavior/find_first_beacon_target, BB_BEACON_TARGET) - -/datum/ai_behavior/find_first_beacon_target - -/datum/ai_behavior/find_first_beacon_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/closest_distance = INFINITY - var/mob/living/basic/bot/bot_pawn = controller.pawn - var/atom/final_target - var/atom/previous_target = controller.blackboard[BB_PREVIOUS_BEACON_TARGET] - for(var/obj/machinery/navbeacon/beacon as anything in GLOB.navbeacons["[bot_pawn.z]"]) - var/dist = get_dist(bot_pawn, beacon) - if(beacon == previous_target || dist <= 1) - continue - if(dist > closest_distance) - continue - closest_distance = dist - final_target = beacon - - if(isnull(final_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(BB_BEACON_TARGET, final_target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_next_beacon_target - action_cooldown = 5 SECONDS - -/datum/ai_behavior/find_next_beacon_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key) - var/mob/living/basic/bot/bot_pawn = controller.pawn - var/atom/final_target - var/obj/machinery/navbeacon/prev_beacon = controller.blackboard[BB_PREVIOUS_BEACON_TARGET] - if(QDELETED(prev_beacon)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - for(var/obj/machinery/navbeacon/beacon as anything in GLOB.navbeacons["[bot_pawn.z]"]) - if(beacon.location == prev_beacon.codes[NAVBEACON_PATROL_NEXT]) - final_target = beacon - break - - if(isnull(final_target)) - controller.clear_blackboard_key(BB_PREVIOUS_BEACON_TARGET) //failed to find the next beacon, search for a first beacon again - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(BB_PREVIOUS_BEACON_TARGET, final_target) - controller.clear_blackboard_key(BB_BEACON_TARGET) - - if(LAZYACCESS(controller.blackboard[BB_TEMPORARY_IGNORE_LIST], final_target) || get_dist(bot_pawn, final_target) > controller.max_target_distance) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(controller.set_if_can_reach(key = BB_BEACON_TARGET, target = final_target, duration = 3 MINUTES, distance = controller.max_target_distance)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - controller.set_blackboard_key(BB_BOT_BEACON_COOLDOWN, world.time + BOT_NO_BEACON_PATH_PENALTY) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - -/datum/ai_behavior/travel_towards/beacon - clear_target = TRUE - new_movement_type = /datum/ai_movement/jps/bot/travel_to_beacon - -/datum/ai_behavior/travel_towards/beacon/setup(datum/ai_controller/controller, target_key) - var/atom/target_beacon = controller.blackboard[target_key] - if(LAZYACCESS(controller.blackboard[BB_TEMPORARY_IGNORE_LIST], target_beacon)) - return FALSE - return ..() - -/datum/ai_behavior/travel_towards/beacon/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key) - var/atom/target = controller.blackboard[target_key] - if(!succeeded) - controller.set_blackboard_key(BB_BOT_BEACON_COOLDOWN, world.time + BOT_NO_BEACON_PATH_PENALTY) - controller.add_to_blacklist(target, 3 MINUTES) - controller.set_blackboard_key(BB_PREVIOUS_BEACON_TARGET, target) - return ..() - -/datum/ai_planning_subtree/respond_to_summon - -/datum/ai_planning_subtree/respond_to_summon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_BOT_SUMMON_TARGET)) - return - controller.queue_behavior(/datum/ai_behavior/travel_towards/bot_summon, BB_BOT_SUMMON_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/travel_towards/bot_summon - clear_target = TRUE - new_movement_type = /datum/ai_movement/jps/bot/travel_to_beacon - -/datum/ai_behavior/travel_towards/bot_summon/finish_action(datum/ai_controller/controller, succeeded, target_key) - var/mob/living/basic/bot/bot_pawn = controller.pawn - if(QDELETED(bot_pawn)) // pawn can be null at this point - return ..() - bot_pawn.calling_ai_ref = null - bot_pawn.update_bot_mode(new_mode = BOT_IDLE) - return ..() - -/datum/ai_planning_subtree/salute_authority - -/datum/ai_planning_subtree/salute_authority/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - //we are criminals, dont salute the dirty pigs - if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) - return - if(controller.blackboard_key_exists(BB_SALUTE_TARGET)) - controller.queue_behavior(/datum/ai_behavior/salute_authority, BB_SALUTE_TARGET, BB_SALUTE_MESSAGES) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_authority, BB_SALUTE_TARGET) - - -/datum/ai_behavior/find_and_set/valid_authority - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = BOT_COMMISSIONED_SALUTE_DELAY - -/datum/ai_behavior/find_and_set/valid_authority/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/mob/living/nearby_mob in oview(search_range, controller.pawn)) - if(!HAS_TRAIT(nearby_mob, TRAIT_COMMISSIONED)) - continue - return nearby_mob - return null - -/datum/ai_behavior/salute_authority - -/datum/ai_behavior/salute_authority/perform(seconds_per_tick, datum/ai_controller/controller, target_key, salute_keys) - if(!controller.blackboard_key_exists(target_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/list/salute_list = controller.blackboard[salute_keys] - if(!length(salute_list)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/basic/bot/bot_pawn = controller.pawn - //special interaction if we are wearing a fedora - var/obj/item/our_hat = (locate(/obj/item/clothing/head) in bot_pawn) - if(our_hat) - salute_list += "tips [our_hat] at " - - bot_pawn.manual_emote(pick(salute_list) + " [controller.blackboard[target_key]]!") - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/salute_authority/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/bot_search - action_cooldown = 2 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/bot_search/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, looking_for, radius = 5, pathing_distance = 10, bypass_add_blacklist = FALSE, turf_search = FALSE) - if(!istype(controller)) - stack_trace("attempted to give [controller.pawn] the bot search behavior!") - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_pawn = controller.pawn - var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - var/list/objects_to_search = turf_search ? RANGE_TURFS(radius, controller.pawn) : oview(radius, controller.pawn) //use range turfs instead of oview when we can for performance - for(var/atom/potential_target as anything in objects_to_search) - if(QDELETED(living_pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(!is_type_in_typecache(potential_target, looking_for)) - continue - if(LAZYACCESS(ignore_list, potential_target)) - continue - if(!valid_target(controller, potential_target)) - continue - if(!can_see(controller.pawn, potential_target, radius)) - continue - if(controller.set_if_can_reach(key = target_key, target = potential_target, distance = pathing_distance, bypass_add_to_blacklist = bypass_add_blacklist)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/bot_search/proc/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) - return TRUE - -///behavior to make our bot talk -/datum/ai_behavior/bot_speech - action_cooldown = 5 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/bot_speech/perform(seconds_per_tick, datum/ai_controller/controller, list/list_to_pick_from, announce_key) - var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[announce_key] - - if(isnull(announcement) || !length(list_to_pick_from)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - announcement.announce(pick(list_to_pick_from)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -///behavior to interact with atoms -/datum/ai_behavior/bot_interact - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - ///should we remove the target afterwards? - var/clear_target = TRUE - -/datum/ai_behavior/bot_interact/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/bot_interact/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/basic/living_pawn = controller.pawn - var/atom/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - living_pawn.UnarmedAttack(target, proximity_flag = TRUE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/bot_interact/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(clear_target) - controller.clear_blackboard_key(target_key) - if(!succeeded && !isnull(target)) - controller.add_to_blacklist(target) - -/datum/ai_behavior/bot_interact/keep_target - clear_target = FALSE - - -#undef BOT_NO_BEACON_PATH_PENALTY diff --git a/code/modules/mob/living/basic/bots/bot_hud.dm b/code/modules/mob/living/basic/bots/bot_hud.dm index 602b2cc54bdd..f3d693073bc1 100644 --- a/code/modules/mob/living/basic/bots/bot_hud.dm +++ b/code/modules/mob/living/basic/bots/bot_hud.dm @@ -32,7 +32,7 @@ set_hud_image_state(DIAG_BOT_HUD, "") ///proc that handles drawing and transforming the bot's path onto diagnostic huds -/mob/living/basic/bot/proc/generate_bot_path(datum/move_loop/has_target/jps/source) +/mob/living/basic/bot/proc/generate_bot_path(datum/move_loop/has_target/jps/source, list/path) SIGNAL_HANDLER UnregisterSignal(src, COMSIG_MOVELOOP_JPS_FINISHED_PATHING) @@ -40,8 +40,11 @@ if(isnull(ai_controller)) return + if(!length(path)) + return + - var/atom/move_target = ai_controller.current_movement_target + var/atom/move_target = path[path.len] if(move_target != ai_controller.blackboard[BB_BEACON_TARGET]) return diff --git a/code/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.json b/code/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.json new file mode 100644 index 000000000000..d3d867cc11fc --- /dev/null +++ b/code/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.json @@ -0,0 +1,28 @@ +{ + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_PET_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/execute_clean", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.json b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.json new file mode 100644 index 000000000000..10be8fb903ec --- /dev/null +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.json @@ -0,0 +1,251 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/cleanbot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/override_id_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_is_emagged", + "vars": { + "invert": true + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/execute_clean", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_FRIENDLY_JANITOR" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FRIENDLY_JANITOR", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_FRIENDLY_JANITOR", + "befriend_message": "BB_FRIENDLY_MESSAGE" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FRIENDLY_JANITOR", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FRIENDLY_JANITOR", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_human/cleanbot_whisperer", + "vision_range": 5, + "time_between_perform": "30 SECONDS" + } + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "cooldown_key": "BB_POST_CLEAN_COOLDOWN" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "time_between_perform": "3 SECONDS", + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/cleanbot_cleanables", + "vision_range": 5, + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "reach_distance": 15 + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_is_emagged", + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_ACID_SPRAY_COOLDOWN", + "cooldown_duration": 30 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_ACID_SPRAY_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ACID_SPRAY_TARGET", + "required_dist": 0, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/execute_clean", + "vars": { + "target_key": "BB_ACID_SPRAY_TARGET" + } + } + ] + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_CLEANBOT_FOAM" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_POST_CLEAN_COOLDOWN" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_ACID_SPRAY_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_human", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "vision_range": 5, + "time_between_perform": "30 SECONDS" + } + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm index 25029c5bc55a..99948e13dec4 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm @@ -2,6 +2,7 @@ #define POST_CLEAN_COOLDOWN 5 SECONDS /datum/ai_controller/basic_controller/bot/cleanbot + behavior_tree_json = "code/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -13,20 +14,9 @@ ), BB_FRIENDLY_MESSAGE = "empathetically acknowledges your hardwork and tough circumstances", ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/pet_planning/cleanbot, - /datum/ai_planning_subtree/cleaning_subtree, - /datum/ai_planning_subtree/befriend_janitors, - /datum/ai_planning_subtree/acid_spray, - /datum/ai_planning_subtree/use_mob_ability/foam_area, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon/cleanbot, - ) reset_keys = list( BB_ACTIVE_PET_COMMAND, - BB_CLEAN_TARGET, + BB_CURRENT_TARGET, BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, @@ -39,99 +29,40 @@ BB_HUNTABLE_TRASH = CLEANBOT_CLEAN_TRASH, ) -/datum/ai_planning_subtree/pet_planning/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - //we are DONE listening to orders - if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) - return - return ..() - - -/datum/ai_planning_subtree/cleaning_subtree -/datum/ai_planning_subtree/cleaning_subtree/SelectBehaviors(datum/ai_controller/basic_controller/bot/cleanbot/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_CLEAN_TARGET)) - controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CLEAN_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING +/// Gathers nearby cleanable atoms: decals plus whatever types the cleanbot's currently enabled janitor mode flags allow. +/datum/target_source/cleanbot_cleanables +/datum/target_source/cleanbot_cleanables/collect_candidates(mob/living/pawn, datum/ai_controller/basic_controller/bot/cleanbot/controller, range) var/list/final_hunt_list = list() - final_hunt_list += controller.blackboard[BB_CLEANABLE_DECALS] - var/list/flag_list = controller.clean_flags - var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn - for(var/list_key in flag_list) - if(!(bot_pawn.janitor_mode_flags & flag_list[list_key])) + var/mob/living/basic/bot/cleanbot/bot_pawn = pawn + for(var/list_key in controller.clean_flags) + if(!(bot_pawn.janitor_mode_flags & controller.clean_flags[list_key])) continue final_hunt_list += controller.blackboard[list_key] + if(!length(final_hunt_list)) + return list() + var/list/type_filter = typecacheof(final_hunt_list) + return typecache_filter_list(oview(range, pawn), type_filter) - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list/clean_targets, BB_CLEAN_TARGET, final_hunt_list) - -/datum/ai_behavior/find_and_set/in_list/clean_targets - action_cooldown = 3 SECONDS - -/datum/ai_behavior/find_and_set/in_list/clean_targets/search_tactic(datum/ai_controller/basic_controller/bot/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) - var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - for(var/atom/found_item in found) - if(QDELETED(controller.pawn)) - break - if(LAZYACCESS(ignore_list, found_item)) - continue - if(get_turf(found_item) == get_turf(controller.pawn)) - return found_item - var/list/path = get_path_to(controller.pawn, found_item, max_distance = BOT_CLEAN_PATH_LIMIT, access = controller.get_access()) - if(!length(path)) - controller.add_to_blacklist(found_item) - continue - return found_item - -/datum/ai_planning_subtree/acid_spray - -/datum/ai_planning_subtree/acid_spray/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn - if(!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) - return - if(controller.blackboard_key_exists(BB_ACID_SPRAY_TARGET)) - controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_ACID_SPRAY_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/spray_target, BB_ACID_SPRAY_TARGET, /mob/living/carbon/human, 5) - -/datum/ai_behavior/find_and_set/spray_target - action_cooldown = 30 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_and_set/spray_target/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - for(var/mob/living/carbon/human/human_target in oview(search_range, controller.pawn)) - if(LAZYACCESS(ignore_list, human_target)) - continue - if(human_target.stat != CONSCIOUS || isnull(human_target.mind)) - continue - return human_target - return null - -/datum/ai_behavior/execute_clean - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/execute_clean/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) +///clean that shit bro fr fr 67 +/datum/bt_node/ai_behavior/execute_clean + var/target_key -/datum/ai_behavior/execute_clean/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/execute_clean/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/living_pawn = controller.pawn var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[living_pawn] execute_clean: target deleted") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - + if(get_dist(living_pawn, target) > 1) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[living_pawn] cleaning [target]", get_turf(target), "Cleaning") living_pawn.UnarmedAttack(target, proximity_flag = TRUE) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/execute_clean/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/execute_clean/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) . = ..() controller.set_blackboard_key(BB_POST_CLEAN_COOLDOWN, POST_CLEAN_COOLDOWN + world.time) var/atom/target = controller.blackboard[target_key] @@ -147,56 +78,25 @@ var/list/speech_list = controller.blackboard[BB_CLEANBOT_EMAGGED_PHRASES] if(length(speech_list)) var/mob/living/living_pawn = controller.pawn - if(!QDELETED(living_pawn)) // pawn can be null at this point - living_pawn.say(pick(speech_list), forced = "ai controller") + if(!QDELETED(living_pawn)) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), pick(speech_list), forced = "ai controller") controller.clear_blackboard_key(target_key) -/datum/ai_planning_subtree/use_mob_ability/foam_area - ability_key = BB_CLEANBOT_FOAM - finish_planning = FALSE - -/datum/ai_planning_subtree/use_mob_ability/foam_area/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - if(!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) - return - return ..() - -/datum/ai_planning_subtree/befriend_janitors -/datum/ai_planning_subtree/befriend_janitors/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - //we are now evil. dont befriend the janitors - if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) - return - if(controller.blackboard_key_exists(BB_FRIENDLY_JANITOR)) - controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_FRIENDLY_JANITOR, BB_FRIENDLY_MESSAGE) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/friendly_janitor, BB_FRIENDLY_JANITOR, /mob/living/carbon/human, 5) - -/datum/ai_behavior/find_and_set/friendly_janitor - action_cooldown = 30 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_and_set/friendly_janitor/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - for(var/mob/living/carbon/human/human_target in oview(search_range, living_pawn)) - if(human_target.stat != CONSCIOUS || isnull(human_target.mind)) - continue - if(!HAS_TRAIT(human_target, TRAIT_CLEANBOT_WHISPERER)) - continue - if(living_pawn.has_ally(REF(human_target))) - continue - return human_target - return null +/// Valid if the target is a conscious human janitor-whisperer the cleanbot hasn't already befriended. +/datum/targeting_strategy/conscious_human/cleanbot_whisperer/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!HAS_TRAIT(target, TRAIT_CLEANBOT_WHISPERER)) + return FALSE + return !living_mob.has_ally(REF(target)) -/datum/ai_planning_subtree/find_patrol_beacon/cleanbot -/datum/ai_planning_subtree/find_patrol_beacon/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard[BB_POST_CLEAN_COOLDOWN] >= world.time) - return - return ..() +/datum/bt_node/subtree/clean_pet_target + behavior_tree_json = "code/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.json" +///Tells the cleanbot to go clean a target /datum/pet_command/clean command_name = "Clean" command_desc = "Command a cleanbot to clean the mess." @@ -215,11 +115,11 @@ return ..() /datum/pet_command/clean/execute_action(datum/ai_controller/basic_controller/bot/controller) - if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) - controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + var/atom/target = controller.blackboard[BB_CURRENT_PET_TARGET] + if(QDELETED(target)) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + return + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/clean_pet_target) #undef BOT_CLEAN_PATH_LIMIT #undef POST_CLEAN_COOLDOWN diff --git a/code/modules/mob/living/basic/bots/dedbot.bt.json b/code/modules/mob/living/basic/bots/dedbot.bt.json new file mode 100644 index 000000000000..04c05cdf64ed --- /dev/null +++ b/code/modules/mob/living/basic/bots/dedbot.bt.json @@ -0,0 +1,73 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/dedbot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/melee", + "vars": { + "ability_key": "BB_DEDBOT_SLASH", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/dedbot.dm b/code/modules/mob/living/basic/bots/dedbot.dm index d55570edbdcd..c81baec82c92 100644 --- a/code/modules/mob/living/basic/bots/dedbot.dm +++ b/code/modules/mob/living/basic/bots/dedbot.dm @@ -49,31 +49,20 @@ grant_actions_by_list(innate_actions) /datum/ai_controller/basic_controller/bot/dedbot + behavior_tree_json = "code/modules/mob/living/basic/bots/dedbot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = DEAD, BB_AGGRO_RANGE = 2, ) ai_movement = /datum/ai_movement/jps/bot - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/exenterate, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/find_patrol_beacon, - ) max_target_distance = AI_BOT_PATH_LENGTH - ///keys to be reset when the bot is reseted reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, ) -/datum/ai_planning_subtree/targeted_mob_ability/exenterate - ability_key = BB_DEDBOT_SLASH - finish_planning = FALSE - /datum/action/cooldown/mob_cooldown/exenterate name = "Exenterate" desc = "Disembowel every living thing in range with your blades." diff --git a/code/modules/mob/living/basic/bots/ed209/ed209.bt.json b/code/modules/mob/living/basic/bots/ed209/ed209.bt.json new file mode 100644 index 000000000000..b1fd91ac2cd0 --- /dev/null +++ b/code/modules/mob/living/basic/bots/ed209/ed209.bt.json @@ -0,0 +1,132 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/ed209", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": false, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": false, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_target_stunned", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "max_range": 9 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "approach_movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/ed209/ed209_ai.dm b/code/modules/mob/living/basic/bots/ed209/ed209_ai.dm index d699a153bb25..4305104d5fb5 100644 --- a/code/modules/mob/living/basic/bots/ed209/ed209_ai.dm +++ b/code/modules/mob/living/basic/bots/ed209/ed209_ai.dm @@ -2,18 +2,13 @@ #define SPECIAL_LINES "special_lines" /datum/ai_controller/basic_controller/bot/ed209 + behavior_tree_json = "code/modules/mob/living/basic/bots/ed209/ed209.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/secbot, BB_UNREACHABLE_LIST_COOLDOWN = 1 MINUTES, BB_ALWAYS_IGNORE_FACTION = TRUE, - ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/arrest_target/ed209, - /datum/ai_planning_subtree/find_patrol_beacon, + BB_RANGED_SKIRMISH_MIN_DISTANCE = 2, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 3 ) reset_keys = list( BB_BEACON_TARGET, @@ -26,7 +21,7 @@ . = ..() if(. & AI_CONTROLLER_INCOMPATIBLE) return - RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(on_target_set)) + RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET), PROC_REF(on_target_set)) /datum/ai_controller/basic_controller/bot/ed209/proc/on_target_set() @@ -48,18 +43,5 @@ var/list/final_list = my_bot.sheriffized ? lines_to_pick[SPECIAL_LINES] : lines_to_pick[DEFAULT_LINES] INVOKE_ASYNC(announcement, TYPE_PROC_REF(/datum/action/cooldown/bot_announcement, announce), pick(final_list)) -/datum/ai_planning_subtree/arrest_target/ed209 - arrest_behavior = /datum/ai_behavior/basic_melee_attack/interact_once/bot/ed209 - - -/datum/ai_behavior/basic_melee_attack/interact_once/bot/ed209 - action_cooldown = 0.5 SECONDS - -/datum/ai_behavior/basic_melee_attack/interact_once/bot/ed209/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - if(!(. & AI_BEHAVIOR_DELAY)) - return AI_BEHAVIOR_DELAY //this kinda sucks but we have to do this cause we need to shoot while moving to stun - - #undef DEFAULT_LINES #undef SPECIAL_LINES diff --git a/code/modules/mob/living/basic/bots/ed209/ed209_nukie_ai.dm b/code/modules/mob/living/basic/bots/ed209/ed209_nukie_ai.dm index 24bf9a203670..3d5a6e283e17 100644 --- a/code/modules/mob/living/basic/bots/ed209/ed209_nukie_ai.dm +++ b/code/modules/mob/living/basic/bots/ed209/ed209_nukie_ai.dm @@ -2,13 +2,10 @@ blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_UNREACHABLE_LIST_COOLDOWN = 1 MINUTES, + BB_RANGED_SKIRMISH_MIN_DISTANCE = 2, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 3 ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree, - /datum/ai_planning_subtree/find_patrol_beacon, - ) + behavior_tree_json = "code/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.json" reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, diff --git a/code/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.json b/code/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.json new file mode 100644 index 000000000000..469fba535420 --- /dev/null +++ b/code/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.json @@ -0,0 +1,21 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/ed209/syndicate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ranged_combat", + "bindings": { + "bsjjwub2": "/datum/bt_node/subtree/bot_patrol" + } + } + ] +} diff --git a/code/modules/mob/living/basic/bots/firebot/firebot.bt.json b/code/modules/mob/living/basic/bots/firebot/firebot.bt.json new file mode 100644 index 000000000000..08156b1ddd8a --- /dev/null +++ b/code/modules/mob/living/basic/bots/firebot/firebot.bt.json @@ -0,0 +1,119 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/firebot", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/announce_fire_detected" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_interact/extinguish", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "target_source": "/datum/target_source/firebot_targets", + "targeting_strategy": "/datum/targeting_strategy/extinguishable_person", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "vision_range": 5 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "target_source": "/datum/target_source/range_turfs/firebot_hotspots", + "targeting_strategy": "/datum/targeting_strategy/burning_hotspot", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "vision_range": 5 + } + } + ] + } + ] + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/handle_firebot_speech" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm b/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm index b40f5da5abd4..8e44c73c5484 100644 --- a/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm +++ b/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm @@ -5,17 +5,9 @@ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_turfs, BB_UNREACHABLE_LIST_COOLDOWN = 3 MINUTES, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/extinguishing_people, - /datum/ai_planning_subtree/extinguishing_turfs, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/firebot_speech, - /datum/ai_planning_subtree/find_patrol_beacon, - ) + behavior_tree_json = "code/modules/mob/living/basic/bots/firebot/firebot.bt.json" reset_keys = list( - BB_FIREBOT_EXTINGUISH_TARGET, + BB_CURRENT_TARGET, BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, @@ -27,106 +19,106 @@ . = ..() if(. & AI_CONTROLLER_INCOMPATIBLE) return - RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_FIREBOT_EXTINGUISH_TARGET), PROC_REF(on_target_found)) -///say a silly line whenever we find someone on fire -/datum/ai_controller/basic_controller/bot/firebot/proc/on_target_found() - SIGNAL_HANDLER - if(!COOLDOWN_FINISHED(src, announcement_cooldown)) - return - var/datum/action/cooldown/bot_announcement/announcement = blackboard[BB_ANNOUNCE_ABILITY] - if(isnull(announcement)) - return - var/list/lines = blackboard[BB_FIREBOT_FIRE_DETECTED_LINES] +/datum/bt_node/ai_behavior/announce_fire_detected + +/datum/bt_node/ai_behavior/announce_fire_detected/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/firebot/controller) + if(!COOLDOWN_FINISHED(controller, announcement_cooldown)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + if(isnull(announcement)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + var/list/lines = controller.blackboard[BB_FIREBOT_FIRE_DETECTED_LINES] if(!length(lines)) - return + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED INVOKE_ASYNC(announcement, TYPE_PROC_REF(/datum/action/cooldown/bot_announcement, announce), pick(lines)) - COOLDOWN_START(src, announcement_cooldown, ANNOUNCEMENT_TIMER) - - -///subtree for extinguishing people -/datum/ai_planning_subtree/extinguishing_people + COOLDOWN_START(controller, announcement_cooldown, ANNOUNCEMENT_TIMER) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED -/datum/ai_planning_subtree/extinguishing_people/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_FIREBOT_EXTINGUISH_TARGET)) - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/interact_once/extinguish, BB_FIREBOT_EXTINGUISH_TARGET, BB_TARGETING_STRATEGY) - return SUBTREE_RETURN_FINISH_PLANNING - var/mob/living/basic/bot/firebot/living_bot = controller.pawn - var/range = living_bot.firebot_mode_flags & FIREBOT_STATIONARY_MODE ? 1 : 5 - - if(living_bot.firebot_mode_flags & FIREBOT_EXTINGUISH_PEOPLE) - controller.queue_behavior(/datum/ai_behavior/bot_search/people_on_fire, BB_FIREBOT_EXTINGUISH_TARGET, controller.blackboard[BB_FIREBOT_CAN_EXTINGUISH], range) +/// Firebot skips blacklisting unreachable targets while stationary, matching the old set_if_can_reach bypass. +/datum/ai_controller/basic_controller/bot/firebot/note_unreachable_target(atom/target) + var/mob/living/basic/bot/firebot/bot_pawn = pawn + if(bot_pawn.firebot_mode_flags & FIREBOT_STATIONARY_MODE) + return + return ..() -///behavior for finding people on fire -/datum/ai_behavior/bot_search/people_on_fire +/// Gathers nearby living mobs to extinguish; empty unless people-extinguishing is on, range clamped to adjacent tiles when stationary. +/datum/target_source/firebot_targets + +/datum/target_source/firebot_targets/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/mob/living/basic/bot/firebot/bot_pawn = pawn + if(!(bot_pawn.firebot_mode_flags & FIREBOT_EXTINGUISH_PEOPLE)) + return list() + if(bot_pawn.firebot_mode_flags & FIREBOT_STATIONARY_MODE) + range = 1 + var/list/candidates = list() + for(var/mob/living/candidate in oview(range, pawn)) + candidates += candidate + return candidates + +/// Valid if the mob is on fire (or anyone, while emagged) and is a type this firebot is allowed to extinguish. +/datum/targeting_strategy/extinguishable_person/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/target_mob = target + if(!isliving(target_mob)) + return FALSE + var/mob/living/basic/bot/firebot/bot_pawn = living_mob + if(!target_mob.on_fire && !(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) + return FALSE + return is_type_in_list(target_mob, controller.blackboard[BB_FIREBOT_CAN_EXTINGUISH]) + + +/// Gathers turfs in range; empty unless flame-extinguishing is enabled. +/datum/target_source/range_turfs/firebot_hotspots + +/datum/target_source/range_turfs/firebot_hotspots/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/mob/living/basic/bot/firebot/bot_pawn = pawn + if(!(bot_pawn.firebot_mode_flags & FIREBOT_EXTINGUISH_FLAMES)) + return list() + return ..(pawn, controller, range) + +/// Valid if the turf is an open turf with an active fire. +/datum/targeting_strategy/burning_hotspot/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!isopenturf(target)) + return FALSE + var/turf/open/open_turf = target + return !!open_turf.active_hotspot -/datum/ai_behavior/bot_search/people_on_fire/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/my_target) - var/mob/living/basic/bot/living_bot = controller.pawn - return (my_target.on_fire || (living_bot.bot_access_flags & BOT_COVER_EMAGGED)) -///subtree for finding turfs to extinguish -/datum/ai_planning_subtree/extinguishing_turfs -/datum/ai_planning_subtree/extinguishing_turfs/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_FIREBOT_EXTINGUISH_TARGET)) - return +/datum/bt_node/ai_behavior/bot_interact/extinguish - var/mob/living/basic/bot/firebot/living_bot = controller.pawn - var/should_bypass_blacklist = living_bot.firebot_mode_flags & FIREBOT_STATIONARY_MODE - - if(living_bot.firebot_mode_flags & FIREBOT_EXTINGUISH_FLAMES) - controller.queue_behavior(/datum/ai_behavior/search_burning_turfs, BB_FIREBOT_EXTINGUISH_TARGET, should_bypass_blacklist) - -///behavior to find burning turfs -/datum/ai_behavior/search_burning_turfs - action_cooldown = 2 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/search_burning_turfs/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, bypass_add_blacklist = FALSE) - var/mob/living/living_pawn = controller.pawn - var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - - for(var/turf/possible_turf as anything in RANGE_TURFS(5, living_pawn)) - if(QDELETED(living_pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(!isopenturf(possible_turf)) - continue - var/turf/open/open_turf = possible_turf - if(!open_turf.active_hotspot) - continue - if(LAZYACCESS(ignore_list, possible_turf)) - continue - if(controller.set_if_can_reach(key = target_key, target = possible_turf, bypass_add_to_blacklist = bypass_add_blacklist)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -///behavior to extinguish mobs or turfs -/datum/ai_behavior/basic_melee_attack/interact_once/extinguish - -/datum/ai_behavior/basic_melee_attack/interact_once/extinguish/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - var/atom/target = controller.blackboard[BB_FIREBOT_EXTINGUISH_TARGET] +/datum/bt_node/ai_behavior/bot_interact/extinguish/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) + . = ..() + // if we couldn't reach OR we emagged a living target, blacklist them + var/atom/target = controller.blackboard[target_key] var/mob/living/basic/bot/living_bot = controller.pawn - - //if we couldnt path, or we successfully burnt someone, ignore them for a bit! if(!succeeded || (isliving(target) && (living_bot.bot_access_flags & BOT_COVER_EMAGGED))) controller.add_to_blacklist(target) - return ..() -///subtree to make us say funny idle lines -/datum/ai_planning_subtree/firebot_speech - ///chance we spout lines + +/datum/bt_node/ai_behavior/handle_firebot_speech + time_between_perform = 20 SECONDS var/speech_prob = 3 -/datum/ai_planning_subtree/firebot_speech/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard[BB_FIREBOT_EXTINGUISH_TARGET] || !SPT_PROB(speech_prob, seconds_per_tick)) - return +/datum/bt_node/ai_behavior/handle_firebot_speech/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(speech_prob, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/mob/living/basic/bot/living_bot = controller.pawn var/list/idle_lines = (living_bot.bot_access_flags & BOT_COVER_EMAGGED) ? controller.blackboard[BB_FIREBOT_EMAGGED_LINES] : controller.blackboard[BB_FIREBOT_IDLE_LINES] - controller.queue_behavior(/datum/ai_behavior/bot_speech, idle_lines, BB_ANNOUNCE_ABILITY) + if(!length(idle_lines)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + announcement?.announce(pick(idle_lines)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED #undef ANNOUNCEMENT_TIMER diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot.bt.json b/code/modules/mob/living/basic/bots/honkbots/honkbot.bt.json new file mode 100644 index 000000000000..2e6d19f16c4e --- /dev/null +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot.bt.json @@ -0,0 +1,191 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/honkbot", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/honkbot_slip" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CLOWN_FRIEND" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/play_with_clown", + "vars": { + "target_key": "BB_CLOWN_FRIEND" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CLOWN_FRIEND", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SLIPPERY_TARGET", + "target_source": "/datum/target_source/honkbot_slippery", + "targeting_strategy": "/datum/targeting_strategy/can_see", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "vision_range": 5, + "time_between_perform": "5 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SLIP_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/can_see/slip_victim", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "vision_range": 5 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CLOWN_FRIEND", + "target_source": "/datum/target_source/oview_single_type/living_mob", + "targeting_strategy": "/datum/targeting_strategy/clown_friend", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "vision_range": 5, + "time_between_perform": "5 SECONDS" + } + } + ] + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] +} diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot.dm b/code/modules/mob/living/basic/bots/honkbots/honkbot.dm index fbe91b7cc4de..a96ee6309ddc 100644 --- a/code/modules/mob/living/basic/bots/honkbots/honkbot.dm +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot.dm @@ -55,7 +55,7 @@ return honkbot_sounds /mob/living/basic/bot/secbot/honkbot/proc/pre_slip() - return (prob(70) && ai_controller?.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return (prob(70) && ai_controller?.blackboard_key_exists(BB_CURRENT_TARGET)) /mob/living/basic/bot/secbot/honkbot/proc/post_slip() INVOKE_ASYNC(src, TYPE_PROC_REF(/mob/living/basic/bot, speak), HONKBOT_VOICED_HONK_SAD) diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm b/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm index 3a4d022087d9..ea668a55c19a 100644 --- a/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm @@ -4,16 +4,7 @@ BB_UNREACHABLE_LIST_COOLDOWN = 1 MINUTES, BB_ALWAYS_IGNORE_FACTION = TRUE, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/use_mob_ability/random_honk, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/arrest_target, - /datum/ai_planning_subtree/slip_victims, - /datum/ai_planning_subtree/play_with_clowns, - /datum/ai_planning_subtree/find_patrol_beacon, - ) + behavior_tree_json = "code/modules/mob/living/basic/bots/honkbots/honkbot.bt.json" reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, @@ -24,15 +15,10 @@ . = ..() if(. & AI_CONTROLLER_INCOMPATIBLE) return - RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_SLIP_TARGET), PROC_REF(on_clear_target)) + // on_clear_target intentionally removed it caused grab-then-immediately-release bug + // Cleanup is handled by on_stop_pulling instead RegisterSignal(new_pawn, COMSIG_ATOM_NO_LONGER_PULLING, PROC_REF(on_stop_pulling)) -/datum/ai_controller/basic_controller/bot/honkbot/proc/on_clear_target(datum/source) - SIGNAL_HANDLER - - var/mob/living/living_pawn = pawn - living_pawn.stop_pulling() - /datum/ai_controller/basic_controller/bot/honkbot/proc/on_stop_pulling(datum/source) SIGNAL_HANDLER @@ -43,128 +29,107 @@ add_to_blacklist(slip_target) clear_blackboard_key(BB_SLIP_TARGET) -/datum/ai_planning_subtree/play_with_clowns/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/clown_target = controller.blackboard[BB_CLOWN_FRIEND] - if(QDELETED(clown_target)) - var/list/my_list = controller.blackboard[BB_CLOWNS_LIST] - controller.queue_behavior(/datum/ai_behavior/bot_search/clown_friends, BB_CLOWN_FRIEND, my_list) - return - controller.queue_behavior(/datum/ai_behavior/play_with_clown, BB_CLOWN_FRIEND) - return SUBTREE_RETURN_FINISH_PLANNING -/datum/ai_behavior/bot_search/clown_friends -/datum/ai_behavior/bot_search/clown_friends/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/my_target) - if(HAS_TRAIT(my_target, TRAIT_PERCEIVED_AS_CLOWN)) - return TRUE - if(!istype(my_target, /mob/living/silicon/robot)) - return FALSE - var/mob/living/silicon/robot/robot_target = my_target - return istype(robot_target.model, /obj/item/robot_model/clown) +/datum/bt_node/subtree/honkbot_slip + behavior_tree_json = "code/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.json" -/datum/ai_behavior/play_with_clown - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION -/datum/ai_behavior/play_with_clown/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) -/datum/ai_behavior/play_with_clown/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/living_target = controller.blackboard[target_key] - if(QDELETED(living_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/living_pawn = controller.pawn - var/datum/action/honk_ability = controller.blackboard[BB_HONK_ABILITY] - honk_ability?.Trigger() - living_pawn.manual_emote("celebrates with [living_target]!") - living_pawn.emote("flip") - living_pawn.emote("beep") - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED +/datum/bt_node/ai_behavior/use_mob_ability/random_honk -/datum/ai_behavior/play_with_clown/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - var/mob/living/living_target = controller.blackboard[target_key] - if(QDELETED(living_target)) - return - controller.add_to_blacklist(living_target) - controller.clear_blackboard_key(target_key) +/datum/bt_node/ai_behavior/use_mob_ability/random_honk/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(5, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return ..() -/datum/ai_planning_subtree/slip_victims/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(!living_pawn.has_gravity()) - return - var/atom/slippery_item = controller.blackboard[BB_SLIPPERY_TARGET] - if(QDELETED(slippery_item) || !can_see(controller.pawn, slippery_item, 5)) - controller.clear_blackboard_key(BB_SLIP_TARGET) - controller.clear_blackboard_key(BB_SLIPPERY_TARGET) - controller.queue_behavior(/datum/ai_behavior/bot_search, BB_SLIPPERY_TARGET, controller.blackboard[BB_SLIPPERY_ITEMS]) - return - var/mob/living/living_target = controller.blackboard[BB_SLIP_TARGET] +/// Valid if the target is a visible human who isn't buckled and has gravity someone a slip can actually knock over. +/datum/targeting_strategy/can_see/slip_victim/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/carbon/human/candidate = target + if(!istype(candidate)) + return FALSE + return !candidate.buckled && candidate.has_gravity() - if(QDELETED(living_target)) - var/static/list/to_slip = typecacheof(list(/mob/living/carbon/human)) - controller.queue_behavior(/datum/ai_behavior/bot_search/slip_target, BB_SLIP_TARGET, to_slip) - return - if(living_pawn.pulling == living_target) - controller.queue_behavior(/datum/ai_behavior/drag_to_slip, BB_SLIP_TARGET, BB_SLIPPERY_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING +// Positions the pulled victim onto the slippery item by stepping away, then releases. +/datum/bt_node/ai_behavior/release_and_slip + var/victim_key + +/datum/bt_node/ai_behavior/release_and_slip/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/victim = controller.blackboard[victim_key] + var/mob/living/our_mob = controller.pawn + if(QDELETED(victim) || our_mob.pulling != victim) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[our_mob] release_and_slip: not pulling victim") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[our_mob] releasing [victim]!", get_turf(our_mob), "HONK!") + var/list/possible_dirs = GLOB.alldirs.Copy() + possible_dirs -= get_dir(our_mob, victim) + for(var/direction in possible_dirs) + var/turf/possible_turf = get_step(our_mob, direction) + if(possible_turf.is_blocked_turf(source_atom = our_mob)) + possible_dirs -= direction + if(length(possible_dirs)) + step(our_mob, pick(possible_dirs)) + our_mob.stop_pulling() + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - controller.queue_behavior(/datum/ai_behavior/drag_target, BB_SLIP_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING -/datum/ai_behavior/bot_search/slip_target +/// Gathers nearby atoms matching the slippery-item typepaths stored in BB_SLIPPERY_ITEMS. +/datum/target_source/honkbot_slippery -/datum/ai_behavior/bot_search/slip_target/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/my_target) - return (!my_target.buckled && my_target.has_gravity()) +/datum/target_source/honkbot_slippery/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/slippery_items = controller.blackboard[BB_SLIPPERY_ITEMS] + if(!length(slippery_items)) + return list() + return typecache_filter_list(oview(range, pawn), typecacheof(slippery_items)) -/datum/ai_behavior/drag_to_slip - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 0 +/// Valid only if the target is within line of sight (not just within range). +/datum/targeting_strategy/can_see/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + return can_see(living_mob, target, vision_range) -/datum/ai_behavior/drag_to_slip/setup(datum/ai_controller/controller, slip_target, slippery_target) +/// Valid if the target is a conscious clown (by trait, or a borg running the clown model). +/datum/targeting_strategy/clown_friend/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) . = ..() - var/atom/target = controller.blackboard[slippery_target] - if(QDELETED(target)) + if(!.) return FALSE - set_movement_target(controller, target) + var/mob/living/target_mob = target + if(!isliving(target_mob) || target_mob.stat != CONSCIOUS) + return FALSE + if(HAS_TRAIT(target_mob, TRAIT_PERCEIVED_AS_CLOWN)) + return TRUE + if(istype(target_mob, /mob/living/silicon/robot)) + var/mob/living/silicon/robot/robot_target = target_mob + return istype(robot_target.model, /obj/item/robot_model/clown) + return FALSE + +/datum/bt_node/ai_behavior/play_with_clown + var/target_key -/datum/ai_behavior/drag_to_slip/perform(seconds_per_tick, datum/ai_controller/controller, slip_target, slippery_target) - var/mob/living/our_pawn = controller.pawn - var/atom/living_target = controller.blackboard[slip_target] +/datum/bt_node/ai_behavior/play_with_clown/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_target = controller.blackboard[target_key] if(QDELETED(living_target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/list/possible_dirs = GLOB.alldirs.Copy() - possible_dirs -= get_dir(our_pawn, living_target) - for(var/direction in possible_dirs) - var/turf/possible_turf = get_step(our_pawn, direction) - if(possible_turf.is_blocked_turf(source_atom = our_pawn)) - possible_dirs -= direction - step(our_pawn, pick(possible_dirs)) - our_pawn.stop_pulling() + if(get_dist(controller.pawn, living_target) > 1) + return AI_BEHAVIOR_INSTANT + var/mob/living/living_pawn = controller.pawn + var/datum/action/honk_ability = controller.blackboard[BB_HONK_ABILITY] + honk_ability?.Trigger() + living_pawn.manual_emote("celebrates with [living_target]!") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), "flip") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), "beep") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/drag_to_slip/finish_action(datum/ai_controller/basic_controller/bot/controller, success, slip_target, slippery_target) +/datum/bt_node/ai_behavior/play_with_clown/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) . = ..() - if(success) - var/mob/living/living_pawn = controller.pawn - living_pawn.emote("flip") - var/atom/slipped_victim = controller.blackboard[slip_target] - if(!isnull(slipped_victim)) - controller.add_to_blacklist(slipped_victim) - controller.clear_blackboard_key(slip_target) - controller.clear_blackboard_key(slippery_target) - -/datum/ai_planning_subtree/use_mob_ability/random_honk - ability_key = BB_HONK_ABILITY - -/datum/ai_planning_subtree/use_mob_ability/random_honk/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(5, seconds_per_tick)) - return - return ..() - + var/mob/living/living_target = controller.blackboard[target_key] + if(!isnull(living_target)) + controller.add_to_blacklist(living_target) + controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.json b/code/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.json new file mode 100644 index 000000000000..b54d73dc62b0 --- /dev/null +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.json @@ -0,0 +1,77 @@ +{ + "dm_type": "/datum/bt_node/subtree/honkbot_slip", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/can_see_target", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SLIPPERY_TARGET", + "range": 5 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/can_see_target", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SLIP_TARGET", + "range": 5 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_has_gravity", + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SLIP_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/grab_target", + "vars": { + "target_key": "BB_SLIP_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_grabbing_target", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_SLIP_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SLIPPERY_TARGET", + "required_dist": 0, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/release_and_slip", + "vars": { + "victim_key": "BB_SLIP_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "flip" + } + } + ] + } + } + } +} diff --git a/code/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.json b/code/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.json new file mode 100644 index 000000000000..27d4acdcf326 --- /dev/null +++ b/code/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.json @@ -0,0 +1,109 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/hygienebot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_WASH_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wash_target", + "vars": { + "target_key": "BB_WASH_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_WASH_TARGET", + "required_dist": 0, + "finish_on_arrival": false, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "cooldown_key": "BB_TRASH_TALK_COOLDOWN", + "cooldown_duration": "4 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/commence_trashtalk", + "vars": { + "target_key": "BB_WASH_TARGET" + } + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/hygiene_wash", + "vars": { + "target_key": "BB_WASH_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_human/washable_human", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "vision_range": 5, + "time_between_perform": "5 SECONDS" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm b/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm index 1d0c689bff4b..495ae5715412 100644 --- a/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm +++ b/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm @@ -2,6 +2,7 @@ #define BOT_ANGER_THRESHOLD 5 /datum/ai_controller/basic_controller/bot/hygienebot + behavior_tree_json = "code/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.json" blackboard = list( BB_SALUTE_MESSAGES = list( "salutes", @@ -9,14 +10,6 @@ ), BB_WASH_FRUSTRATION = 0, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/handle_trash_talk, - /datum/ai_planning_subtree/wash_people, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon, - ) reset_keys = list( BB_WASH_TARGET, BB_BEACON_TARGET, @@ -24,115 +17,68 @@ BB_BOT_SUMMON_TARGET, ) -/datum/ai_planning_subtree/handle_trash_talk -/datum/ai_planning_subtree/handle_trash_talk/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_WASH_TARGET)) - return - controller.queue_behavior(/datum/ai_behavior/commence_trashtalk, BB_WASH_TARGET) -/datum/ai_behavior/commence_trashtalk - action_cooldown = 4 SECONDS +/datum/bt_node/ai_behavior/commence_trashtalk + var/target_key -/datum/ai_behavior/commence_trashtalk/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/commence_trashtalk/perform(seconds_per_tick, datum/ai_controller/controller) if(!controller.blackboard_key_exists(target_key)) - return AI_BEHAVIOR_FAILED | AI_BEHAVIOR_DELAY + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/frustration_count = controller.blackboard[BB_WASH_FRUSTRATION] controller.set_blackboard_key(BB_WASH_FRUSTRATION, min(frustration_count + 1, BOT_FRUSTRATION_LIMIT)) if(controller.blackboard[BB_WASH_FRUSTRATION] < BOT_ANGER_THRESHOLD) - return AI_BEHAVIOR_FAILED | AI_BEHAVIOR_DELAY + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement?.announce(pick(controller.blackboard[BB_WASH_THREATS])) - return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY - - -/datum/ai_planning_subtree/wash_people - -/datum/ai_planning_subtree/wash_people/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - - var/atom/wash_target = controller.blackboard[BB_WASH_TARGET] - if(QDELETED(wash_target)) - controller.queue_behavior(/datum/ai_behavior/find_valid_wash_targets, BB_WASH_TARGET, bot_pawn.bot_access_flags) - return - - if(get_dist(bot_pawn, wash_target) < 9) - controller.queue_behavior(/datum/ai_behavior/wash_target, BB_WASH_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.clear_blackboard_key(BB_WASH_TARGET) //delete if too far - -/datum/ai_behavior/find_valid_wash_targets - action_cooldown = 5 SECONDS - -/datum/ai_behavior/find_valid_wash_targets/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, our_access_flags) - . = ..() - var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - var/atom/found_target - for(var/mob/living/carbon/human/wash_potential in oview(5, controller.pawn)) - - if(found_target) - break - - if(isnull(wash_potential.mind) || wash_potential.stat != CONSCIOUS) - continue - - if(LAZYACCESS(ignore_list, wash_potential)) - continue - - if(our_access_flags & BOT_COVER_EMAGGED) - controller.add_to_blacklist(wash_potential) - found_target = wash_potential - break - - for(var/atom/clothing in wash_potential.get_equipped_items(INCLUDE_HELD|INCLUDE_PROSTHETICS)) - if(GET_ATOM_BLOOD_DNA_LENGTH(clothing)) - found_target = wash_potential - break + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if(isnull(found_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(target_key, found_target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/find_valid_wash_targets/finish_action(datum/ai_controller/controller, succeeded, target_key) +/// Valid if the target is a conscious human with bloodied clothing (or anyone, while emagged). +/datum/targeting_strategy/conscious_human/washable_human/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) . = ..() - if(!succeeded) - return + if(!.) + return FALSE + var/mob/living/basic/bot/bot_pawn = living_mob + if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) + return TRUE + var/mob/living/carbon/human/human_target = target + for(var/atom/clothing in human_target.get_equipped_items(INCLUDE_HELD|INCLUDE_PROSTHETICS)) + if(GET_ATOM_BLOOD_DNA_LENGTH(clothing)) + return TRUE + return FALSE + +/// Finds someone to wash and announces it; while emagged the target is blacklisted so the bot washes each person only once. +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/hygiene_wash + +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/hygiene_wash/on_target_found(datum/ai_controller/basic_controller/bot/controller, atom/target, datum/targeting_strategy/strategy) + var/mob/living/basic/bot/bot_pawn = controller.pawn + if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) + controller.add_to_blacklist(target) var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement.announce(pick(controller.blackboard[BB_WASH_FOUND])) -/datum/ai_behavior/wash_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 0 -/datum/ai_behavior/wash_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) +/datum/bt_node/ai_behavior/wash_target + var/target_key -/datum/ai_behavior/wash_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key) - . = ..() +/datum/bt_node/ai_behavior/wash_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/mob/living/carbon/human/unclean_target = controller.blackboard[target_key] var/mob/living/basic/living_pawn = controller.pawn if(QDELETED(unclean_target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(get_dist(living_pawn, unclean_target) > 0) + return AI_BEHAVIOR_INSTANT + living_pawn.melee_attack(unclean_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if(living_pawn.loc == get_turf(unclean_target)) - living_pawn.melee_attack(unclean_target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/wash_target/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/wash_target/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) var/wash_frustration = controller.blackboard[BB_WASH_FRUSTRATION] - controller.clear_blackboard_key(BB_WASH_FRUSTRATION) + controller.set_blackboard_key(BB_WASH_FRUSTRATION, 0) if(!succeeded || wash_frustration <= BOT_ANGER_THRESHOLD) return var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.bt.json b/code/modules/mob/living/basic/bots/medbot/medbot.bt.json new file mode 100644 index 000000000000..695f0cebe8ca --- /dev/null +++ b/code/modules/mob/living/basic/bots/medbot/medbot.bt.json @@ -0,0 +1,109 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/medbot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": false, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": false, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": false, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_medical_flag", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "flag": "MEDBOT_DECLARE_CRIT" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/medbot_find_and_announce_crit" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_medical_flag", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": true, + "flag": "MEDBOT_TIPPED_MODE" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/medbot_treat_patient" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_PATIENT_IN_CRIT", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/crit_patient", + "vision_range": 7 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/medbot_patient", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob/medbot_patient", + "targeting_strategy": "/datum/targeting_strategy/treatable_patient", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "reach_distance": 20, + "vision_range": 7 + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_medical_flag", + "vars": { + "flag": "MEDBOT_SPEAK_MODE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/handle_medbot_speech", + "vars": { + "announce_key": "BB_ANNOUNCE_ABILITY" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm index 8b53bc619466..60035e0a8a01 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm @@ -1,17 +1,18 @@ #define BOT_PATIENT_PATH_LIMIT 20 + +/// Find and treat a patient used by both the speak-mode parallel and the silent fallback branch. +/datum/bt_node/subtree/medbot_treat_patient + behavior_tree_json = "code/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.json" + +/// Find a patient in hard-crit and announce them on radio. +/datum/bt_node/subtree/medbot_find_and_announce_crit + behavior_tree_json = "code/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.json" + /datum/ai_controller/basic_controller/bot/medbot - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/handle_medbot_speech, - /datum/ai_planning_subtree/find_and_hunt_target/patients_in_crit, - /datum/ai_planning_subtree/treat_wounded_target, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon/medbot, - ) + behavior_tree_json = "code/modules/mob/living/basic/bots/medbot/medbot.bt.json" ai_movement = /datum/ai_movement/jps/bot/medbot reset_keys = list( - BB_PATIENT_TARGET, + BB_CURRENT_TARGET, BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, @@ -21,7 +22,7 @@ maximum_length = BOT_PATIENT_PATH_LIMIT max_pathing_attempts = 20 -// only AI isnt allowed to move when this flag is set, sentient players can +// only AI isn't allowed to move when this flag is set, sentient players can /datum/ai_movement/jps/bot/medbot/allowed_to_move(datum/move_loop/source) var/datum/ai_controller/controller = source.extra_info var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn @@ -33,172 +34,137 @@ maximum_length = AI_BOT_PATH_LENGTH -/datum/ai_planning_subtree/treat_wounded_target -/datum/ai_planning_subtree/treat_wounded_target/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - if(bot_pawn.medical_mode_flags & MEDBOT_TIPPED_MODE) - controller.clear_blackboard_key(BB_PATIENT_TARGET) +/// Medbot's note_unreachable_target skips blacklisting while stationary, matching the old set_if_can_reach bypass. +/datum/ai_controller/basic_controller/bot/medbot/note_unreachable_target(atom/target) + var/mob/living/basic/bot/medbot/bot_pawn = pawn + if(bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) return - var/is_stationary = bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE - if(controller.blackboard_key_exists(BB_PATIENT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/tend_to_patient, BB_PATIENT_TARGET, bot_pawn.heal_threshold, bot_pawn.damage_type_healer, bot_pawn.bot_access_flags, is_stationary) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_suitable_patient, BB_PATIENT_TARGET, bot_pawn.heal_threshold, bot_pawn.damage_type_healer, bot_pawn.medical_mode_flags, bot_pawn.bot_access_flags) - -/datum/ai_behavior/find_suitable_patient - var/search_range = 7 - action_cooldown = 2 SECONDS - -/datum/ai_behavior/find_suitable_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, threshold, heal_type, mode_flags, access_flags) - search_range = (mode_flags & MEDBOT_STATIONARY_MODE) ? 1 : initial(search_range) - var/list/ignore_keys = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - for(var/mob/living/carbon/human/treatable_target in oview(search_range, controller.pawn)) - if(LAZYACCESS(ignore_keys, treatable_target) || treatable_target.stat == DEAD) - continue - if((access_flags & BOT_COVER_EMAGGED) && treatable_target.stat == CONSCIOUS) - controller.set_if_can_reach(key = BB_PATIENT_TARGET, target = treatable_target, distance = BOT_PATIENT_PATH_LIMIT, bypass_add_to_blacklist = (search_range == 1)) - break - if((heal_type == HEAL_ALL_DAMAGE)) - if(treatable_target.get_total_damage() > threshold) - controller.set_if_can_reach(key = BB_PATIENT_TARGET, target = treatable_target, distance = BOT_PATIENT_PATH_LIMIT, bypass_add_to_blacklist = (search_range == 1)) - break - continue - if(treatable_target.get_current_damage_of_type(damagetype = heal_type) > threshold) - controller.set_if_can_reach(key = BB_PATIENT_TARGET, target = treatable_target, distance = BOT_PATIENT_PATH_LIMIT, bypass_add_to_blacklist = (search_range == 1)) - break - - if(controller.blackboard_key_exists(BB_PATIENT_TARGET)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - else - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return ..() + +/// Gathers nearby humans as patients; range is clamped to adjacent tiles when the medbot is in stationary mode. I should probably just make this a blackboard thing but I cannot be arsed right now. +/datum/target_source/oview_single_type/human_mob/medbot_patient -/datum/ai_behavior/find_suitable_patient/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/target_source/oview_single_type/human_mob/medbot_patient/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/mob/living/basic/bot/medbot/bot_pawn = pawn + if(bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) + range = 1 + return ..(pawn, controller, range) + +/// Valid if the patient needs the damage type this medbot heals (or is a conscious target while emagged). +/datum/targeting_strategy/treatable_patient/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) . = ..() - if(!succeeded || QDELETED(controller.pawn) ||get_dist(controller.pawn, controller.blackboard[target_key]) <= 1) + if(!.) + return FALSE + var/mob/living/carbon/human/patient = target + if(!istype(patient) || patient.stat == DEAD) + return FALSE + var/mob/living/basic/bot/medbot/bot_pawn = living_mob + if((bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) && patient.stat == CONSCIOUS) + return TRUE + if(bot_pawn.damage_type_healer == HEAL_ALL_DAMAGE) + return patient.get_total_damage() > bot_pawn.heal_threshold + return patient.get_current_damage_of_type(damagetype = bot_pawn.damage_type_healer) > bot_pawn.heal_threshold + +/// Finds a patient to treat, announcing that the bot is on its way when the patient isn't already adjacent. +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/medbot_patient + +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/medbot_patient/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + if(QDELETED(controller.pawn) || get_dist(controller.pawn, target) <= 1) return var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement?.announce(pick(controller.blackboard[BB_WAIT_SPEECH])) -/datum/ai_behavior/tend_to_patient - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH -/datum/ai_behavior/tend_to_patient/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) -/datum/ai_behavior/tend_to_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, threshold, damage_type_healer, access_flags, is_stationary) +/datum/bt_node/ai_behavior/tend_to_patient + var/target_key + +/datum/bt_node/ai_behavior/tend_to_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/mob/living/carbon/human/patient = controller.blackboard[target_key] if(QDELETED(patient) || patient.stat == DEAD) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] tend_to_patient: patient gone (deleted=[QDELETED(patient)], stat=[patient?.stat])") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(check_if_healed(patient, threshold, damage_type_healer, access_flags)) + if(get_dist(controller.pawn, patient) > 1) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED //We technically failed, but we want to try again so succeed. + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn + if(check_if_healed(patient, bot_pawn.heal_threshold, bot_pawn.damage_type_healer, bot_pawn.bot_access_flags)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] tend_to_patient: [patient] is fully healed") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - var/mob/living/basic/bot/bot_pawn = controller.pawn if(patient.stat >= HARD_CRIT && prob(5)) var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement?.announce(pick(controller.blackboard[BB_NEAR_DEATH_SPEECH])) + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] healing [patient] (dmg=[patient.get_total_damage()])", get_turf(patient), "Heal") bot_pawn.melee_attack(patient) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -// only clear the target if they get healed -/datum/ai_behavior/tend_to_patient/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key, threshold, damage_type_healer, access_flags, is_stationary) +/datum/bt_node/ai_behavior/tend_to_patient/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) . = ..() + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn var/atom/target = controller.blackboard[target_key] + var/is_stationary = bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE if(!succeeded) - if(!isnull(target) && !is_stationary) controller.add_to_blacklist(target) - controller.clear_blackboard_key(target_key) return - - if(QDELETED(target) || !check_if_healed(target, threshold, damage_type_healer, access_flags)) + if(QDELETED(target) || !check_if_healed(target, bot_pawn.heal_threshold, bot_pawn.damage_type_healer, bot_pawn.bot_access_flags)) return - var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement?.announce(pick(controller.blackboard[BB_AFTERHEAL_SPEECH])) controller.clear_blackboard_key(target_key) -/datum/ai_behavior/tend_to_patient/proc/check_if_healed(mob/living/carbon/human/patient, threshold, damage_type_healer, access_flags) +/datum/bt_node/ai_behavior/tend_to_patient/proc/check_if_healed(mob/living/carbon/human/patient, threshold, damage_type_healer, access_flags) if(access_flags & BOT_COVER_EMAGGED) return (patient.stat > CONSCIOUS) var/patient_damage = (damage_type_healer == HEAL_ALL_DAMAGE) ? patient.get_total_damage() : patient.get_current_damage_of_type(damagetype = damage_type_healer) return (patient_damage <= threshold) -/datum/ai_planning_subtree/handle_medbot_speech - var/speech_chance = 5 -/datum/ai_planning_subtree/handle_medbot_speech/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - //we cant speak! - if(!(bot_pawn.medical_mode_flags & MEDBOT_SPEAK_MODE)) - return +/datum/bt_node/ai_behavior/handle_medbot_speech + var/announce_key + time_between_perform = 20 SECONDS +/datum/bt_node/ai_behavior/handle_medbot_speech/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn var/currently_tipped = bot_pawn.medical_mode_flags & MEDBOT_TIPPED_MODE - speech_chance = ((bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) || currently_tipped) ? 15 : initial(speech_chance) - + var/speech_chance = ((bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) || currently_tipped) ? 15 : 5 if(!SPT_PROB(speech_chance, seconds_per_tick)) - return - - controller.queue_behavior(/datum/ai_behavior/handle_medbot_speech, BB_ANNOUNCE_ABILITY, bot_pawn.mode, bot_pawn.bot_access_flags, currently_tipped) - -/datum/ai_behavior/handle_medbot_speech - action_cooldown = 20 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/handle_medbot_speech/perform(seconds_per_tick, datum/ai_controller/controller, announce_key, mode, cover_flags, currently_tipped) - . = ..() + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[announce_key] var/list/speech_to_pick_from - if(currently_tipped) speech_to_pick_from = controller.blackboard[BB_WORRIED_ANNOUNCEMENTS] - else if(cover_flags & BOT_COVER_EMAGGED) + else if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) speech_to_pick_from = controller.blackboard[BB_EMAGGED_SPEECH] - else if(mode == BOT_IDLE) + else if(bot_pawn.mode == BOT_IDLE) speech_to_pick_from = controller.blackboard[BB_IDLE_SPEECH] var/mob/living/living_pawn = controller.pawn - if(locate(/obj/item/clothing/head/costume/chicken) in living_pawn) speech_to_pick_from += MEDIBOT_VOICED_CHICKEN - if(!length(speech_to_pick_from)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - announcement.announce(pick(speech_to_pick_from)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_planning_subtree/find_and_hunt_target/patients_in_crit - target_key = BB_PATIENT_IN_CRIT - hunting_behavior = /datum/ai_behavior/announce_patient - finding_behavior = /datum/ai_behavior/find_hunt_target/patient_in_crit - hunt_targets = list(/mob/living/carbon/human) - finish_planning = FALSE -/datum/ai_planning_subtree/find_and_hunt_target/patients_in_crit/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - if(!(bot_pawn.medical_mode_flags & MEDBOT_DECLARE_CRIT)) - return - return ..() -/datum/ai_behavior/find_hunt_target/patient_in_crit - -/datum/ai_behavior/find_hunt_target/patient_in_crit/valid_dinner(mob/living/source, mob/living/carbon/human/patient, radius) - if(patient.stat < UNCONSCIOUS || isnull(patient.mind)) +/// Valid if the patient is at least unconscious, has a mind, and is visible used to announce medical emergencies. +/datum/targeting_strategy/crit_patient/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) return FALSE - return can_see(source, patient, radius) + var/mob/living/carbon/human/patient = target + if(!istype(patient) || patient.stat < UNCONSCIOUS || isnull(patient.mind)) + return FALSE + return can_see(living_mob, patient, vision_range) -/datum/ai_behavior/announce_patient - action_cooldown = 3 MINUTES - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION +/datum/bt_node/ai_behavior/announce_patient + var/target_key + time_between_perform = 3 MINUTES -/datum/ai_behavior/announce_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key) +/datum/bt_node/ai_behavior/announce_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/mob/living/living_target = controller.blackboard[target_key] if(QDELETED(living_target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED @@ -209,22 +175,8 @@ announcement.announce(text_to_announce, controller.blackboard[BB_RADIO_CHANNEL]) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/announce_patient/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/announce_patient/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) -/datum/ai_planning_subtree/find_patrol_beacon/medbot - ///travel towards beacon behavior - travel_behavior = /datum/ai_behavior/travel_towards/beacon/medbot - -/datum/ai_planning_subtree/find_patrol_beacon/medbot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - if(bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) - return - return ..() - - -/datum/ai_behavior/travel_towards/beacon/medbot - new_movement_type = /datum/ai_movement/jps/bot/medbot/travel_to_beacon - #undef BOT_PATIENT_PATH_LIMIT diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.json b/code/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.json new file mode 100644 index 000000000000..48d29e9b23c6 --- /dev/null +++ b/code/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.json @@ -0,0 +1,16 @@ +{ + "dm_type": "/datum/bt_node/subtree/medbot_find_and_announce_crit", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_PATIENT_IN_CRIT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/announce_patient", + "vars": { + "target_key": "BB_PATIENT_IN_CRIT" + } + } +} diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.json b/code/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.json new file mode 100644 index 000000000000..eb4da905953f --- /dev/null +++ b/code/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/bt_node/subtree/medbot_treat_patient", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/tend_to_patient", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot.bt.json b/code/modules/mob/living/basic/bots/mulebot/mulebot.bt.json new file mode 100644 index 000000000000..7b0d49a1a865 --- /dev/null +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot.bt.json @@ -0,0 +1,103 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/mulebot", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MULEBOT_TRAVEL_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_MULEBOT_TRAVEL_TARGET", + "required_dist": 0, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/handle_delivery", + "vars": { + "target_key": "BB_MULEBOT_TRAVEL_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_wire_cut", + "vars": { + "wire": "WIRE_BEACON", + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MULEBOT_TRAVEL_TARGET", + "invert": true + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_mode", + "vars": { + "mode": "BOT_DELIVER" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_delivery_beacon", + "vars": { + "target_key": "BB_MULEBOT_TRAVEL_TARGET", + "tag_key": "BB_MULEBOT_DESTINATION_BEACON" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_mode", + "vars": { + "mode": "BOT_GO_HOME" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_delivery_beacon", + "vars": { + "target_key": "BB_MULEBOT_TRAVEL_TARGET", + "tag_key": "BB_MULEBOT_HOME_BEACON" + } + } + } + ] + } + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] +} diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot.dm index 55d5632e5e18..9367a81d6d54 100644 --- a/code/modules/mob/living/basic/bots/mulebot/mulebot.dm +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot.dm @@ -165,7 +165,7 @@ . = ..() if(mode != BOT_BLOCKED) return - var/obj/machinery/navbeacon/beacon = ai_controller.current_movement_target + var/obj/machinery/navbeacon/beacon = ai_controller.blackboard[BB_CURRENT_MOVEMENT_TARGET] if(!istype(beacon)) return var/intended_mode = beacon.location == ai_controller.blackboard[BB_MULEBOT_HOME_BEACON] ? BOT_GO_HOME : BOT_DELIVER diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm index 7898ced148ce..c5fba5546191 100644 --- a/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/bot/mulebot + behavior_tree_json = "code/modules/mob/living/basic/bots/mulebot/mulebot.bt.json" blackboard = list( BB_SALUTE_MESSAGES = list( "blinks its light in appreciation towards", @@ -6,12 +7,6 @@ ) ai_movement = /datum/ai_movement/jps/bot/mulebot max_target_distance = AI_MULEBOT_PATH_LENGTH - planning_subtrees = list( - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/attempt_delivery, - /datum/ai_planning_subtree/find_delivery_beacon, - ) reset_keys = list( BB_BOT_SUMMON_TARGET, BB_MULEBOT_DESTINATION_BEACON, @@ -40,72 +35,15 @@ ) RegisterSignals(my_bot, content_signals, PROC_REF(update_able_to_run)) -/datum/ai_planning_subtree/find_delivery_beacon - ///what behavior do we use to seek beacons - var/find_beacon_behaviour = /datum/ai_behavior/find_delivery_beacon - -/datum/ai_planning_subtree/find_delivery_beacon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn - if(bot_pawn.wires.is_cut(WIRE_BEACON)) - return - - if(!controller.blackboard_key_exists(BB_MULEBOT_TRAVEL_TARGET)) - controller.queue_behavior(find_beacon_behaviour, BB_MULEBOT_TRAVEL_TARGET) - -/datum/ai_behavior/find_delivery_beacon - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_delivery_beacon/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn - var/atom/delivery_beacon - - var/beacon_tag = null - - switch(bot_pawn.mode) - if(BOT_DELIVER) - beacon_tag = controller.blackboard[BB_MULEBOT_DESTINATION_BEACON] - if(BOT_GO_HOME) - beacon_tag = controller.blackboard[BB_MULEBOT_HOME_BEACON] - else - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - for(var/obj/machinery/navbeacon/beacon as anything in GLOB.deliverybeacons) - if(beacon.location == beacon_tag) - delivery_beacon = beacon - break - - if(isnull(delivery_beacon)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(BB_MULEBOT_TRAVEL_TARGET, delivery_beacon) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/travel_towards/delivery_beacon - new_movement_type = /datum/ai_movement/jps/bot/mulebot - -/datum/ai_planning_subtree/attempt_delivery - ///behavior we use to unload crates - var/delivery_behaviour = /datum/ai_behavior/handle_delivery - -/datum/ai_planning_subtree/attempt_delivery/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_MULEBOT_TRAVEL_TARGET)) - return +/// Loads or unloads cargo at the delivery beacon held in target_key, then heads home or idles. +/datum/bt_node/ai_behavior/handle_delivery + var/target_key + time_between_perform = 1 SECONDS - controller.queue_behavior(delivery_behaviour, BB_MULEBOT_TRAVEL_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/handle_delivery - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/handle_delivery/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/handle_delivery/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/handle_delivery/perform(seconds_per_tick, datum/ai_controller/controller) var/obj/machinery/navbeacon/beacon = controller.blackboard[target_key] + if(QDELETED(beacon)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn var/load_direction = beacon.codes[NAVBEACON_DELIVERY_DIRECTION] // this will be the load/unload dir diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json new file mode 100644 index 000000000000..2949201eb088 --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json @@ -0,0 +1,32 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/repairbot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/repairbot_repair_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/repairbot_find_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm b/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm index 621d00338956..41c2330fdb8c 100644 --- a/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm @@ -1,200 +1,158 @@ #define REPAIRBOT_SPEECH_TIMER 30 SECONDS +/// Emagged repairbot behavior: mug robots then deconstruct structures. +/datum/bt_node/subtree/repairbot_emagged + behavior_tree_json = "code/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.json" + +/datum/bt_node/subtree/repairbot_repair_target + behavior_tree_json = "code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json" + +/datum/bt_node/subtree/repairbot_find_target + behavior_tree_json = "code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json" + /datum/ai_controller/basic_controller/bot/repairbot - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/repairbot_speech, - /datum/ai_planning_subtree/mug_robot, - /datum/ai_planning_subtree/refill_materials, - /datum/ai_planning_subtree/repairbot_deconstruction, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/replace_floors/breaches, - /datum/ai_planning_subtree/wall_girder, - /datum/ai_planning_subtree/build_girder, - /datum/ai_planning_subtree/replace_window, - /datum/ai_planning_subtree/replace_floors, - /datum/ai_planning_subtree/fix_window, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon, - ) + behavior_tree_json = "code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json" + + + reset_keys = list( - BB_TILELESS_FLOOR, - BB_GIRDER_TARGET, - BB_GIRDER_TO_WALL_TARGET, BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, - BB_WELDER_TARGET, - BB_WINDOW_FRAMETARGET, + BB_CURRENT_TARGET, ) - minimum_distance = 1 -///subtree to refill our stacks -/datum/ai_planning_subtree/refill_materials -/datum/ai_planning_subtree/refill_materials/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/static/list/refillable_items = typecacheof(list( - /obj/item/stack/sheet/iron, - /obj/item/stack/sheet/glass, - /obj/item/stack/tile, - )) - if(!controller.blackboard_key_exists(BB_REFILLABLE_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_search/refillable_target, BB_REFILLABLE_TARGET, refillable_items) - return - controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_REFILLABLE_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING -/datum/ai_behavior/bot_search/refillable_target - action_cooldown = 10 SECONDS +/datum/bt_node/ai_behavior/repairbot_speech -/datum/ai_behavior/bot_search/refillable_target/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) - var/static/list/desired_types = list( - /obj/item/stack/sheet/iron, - /obj/item/stack/sheet/glass, - /obj/item/stack/tile, +/datum/bt_node/ai_behavior/repairbot_speech/setup(datum/ai_controller/controller) + if(controller.blackboard[BB_REPAIRBOT_SPEECH_COOLDOWN] > world.time) + return FALSE + var/static/list/keys_to_look = list( + BB_CURRENT_TARGET, + BB_DECONSTRUCT_TARGET, ) - for(var/object_type in desired_types) - if(!istype(my_target, object_type)) - continue - var/obj/item/stack/sheet_type = locate(object_type) in controller.pawn - if(isnull(sheet_type)) - return TRUE //we dont have any of it! - if(sheet_type.amount < sheet_type.max_amount && sheet_type.can_merge(my_target)) - return TRUE + for(var/key in keys_to_look) + if(controller.blackboard_key_exists(key)) + return ..() return FALSE -/datum/ai_planning_subtree/mug_robot - -/datum/ai_planning_subtree/mug_robot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/living_bot = controller.pawn - if(!(living_bot.bot_access_flags & BOT_COVER_EMAGGED)) - return - var/static/list/robot_targets = typecacheof( - /mob/living/silicon/robot, - ) - if(!controller.blackboard_key_exists(BB_ROBOT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_search/valid_robot, BB_ROBOT_TARGET, robot_targets) - return - if(!living_bot.pulling) - controller.queue_behavior(/datum/ai_behavior/drag_target, BB_ROBOT_TARGET) +/datum/bt_node/ai_behavior/repairbot_speech/perform(seconds_per_tick, datum/ai_controller/controller) + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + // determine speech type: emagged -> emagged speech, otherwise normal + var/list/speech_to_pick_from + if(controller.blackboard_key_exists(BB_DECONSTRUCT_TARGET)) + speech_to_pick_from = controller.blackboard[BB_REPAIRBOT_EMAGGED_SPEECH] else - controller.queue_behavior(/datum/ai_behavior/bot_interact/tip_robot, BB_ROBOT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + speech_to_pick_from = controller.blackboard[BB_REPAIRBOT_NORMAL_SPEECH] + if(!length(speech_to_pick_from)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + announcement.announce(pick(speech_to_pick_from)) + controller.set_blackboard_key(BB_REPAIRBOT_SPEECH_COOLDOWN, world.time + REPAIRBOT_SPEECH_TIMER) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/bot_search/valid_robot - action_cooldown = 10 SECONDS -/datum/ai_behavior/bot_search/valid_robot/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) - return (!HAS_TRAIT(my_target, TRAIT_MOB_TIPPED)) && can_see(controller.pawn, my_target) -/datum/ai_behavior/bot_interact/tip_robot +/datum/bt_node/ai_behavior/bot_interact/tip_robot + +/datum/bt_node/ai_behavior/bot_interact/tip_robot/setup(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + var/mob/living/pawn = controller.pawn + if(QDELETED(target) || pawn.pulling != target) + return FALSE + return ..() -/datum/ai_behavior/bot_interact/tip_robot/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/bot_interact/tip_robot/finish_action(datum/ai_controller/controller, succeeded) . = ..() if(succeeded) var/mob/living/living_pawn = controller.pawn living_pawn.stop_pulling() -///subtree to deconstruct things when we're emagged -/datum/ai_planning_subtree/repairbot_deconstruction +/datum/bt_node/ai_behavior/bot_search/valid_robot + time_between_perform = 10 SECONDS + +/datum/bt_node/ai_behavior/bot_search/valid_robot/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + if(!istype(my_target, /mob/living/silicon/robot)) + return FALSE + return (!HAS_TRAIT(my_target, TRAIT_MOB_TIPPED)) && can_see(controller.pawn, my_target) + -/datum/ai_planning_subtree/repairbot_deconstruction/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/living_bot = controller.pawn - if(!(living_bot.bot_access_flags & BOT_COVER_EMAGGED)) - return + +/datum/bt_node/ai_behavior/bot_search/deconstructable + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/bot_search/deconstructable/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + return (!(my_target.resistance_flags & INDESTRUCTIBLE) && !isgroundlessturf(my_target)) + +/datum/bt_node/ai_behavior/bot_search/deconstructable/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/static/list/things_to_deconstruct = typecacheof(list( /obj/structure/window, /turf/open/floor, /turf/closed/wall, )) - if(!controller.blackboard_key_exists(BB_DECONSTRUCT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_search/deconstructable, BB_DECONSTRUCT_TARGET, things_to_deconstruct) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_DECONSTRUCT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + looking_for = things_to_deconstruct + return ..() -/datum/ai_behavior/bot_search/deconstructable - action_cooldown = 5 SECONDS -/datum/ai_behavior/bot_search/deconstructable/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) - return (!(my_target.resistance_flags & INDESTRUCTIBLE) && !isgroundlessturf(my_target)) -///subtree to control bot speech -/datum/ai_planning_subtree/repairbot_speech +/datum/bt_node/ai_behavior/bot_search/refillable_target + time_between_perform = 10 SECONDS -/datum/ai_planning_subtree/repairbot_speech/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard[BB_REPAIRBOT_SPEECH_COOLDOWN] > world.time) - return - var/static/list/keys_to_look = list( - BB_WELDER_TARGET, - BB_WINDOW_FRAMETARGET, - BB_TILELESS_FLOOR, - BB_BREACHED_FLOOR, - BB_GIRDER_TO_WALL_TARGET, - BB_GIRDER_TARGET, - BB_DECONSTRUCT_TARGET, +/datum/bt_node/ai_behavior/bot_search/refillable_target/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + var/static/list/desired_types = list( + /obj/item/stack/sheet/iron, + /obj/item/stack/sheet/glass, + /obj/item/stack/tile, ) - for(var/key in keys_to_look) - if(controller.blackboard_key_exists(key)) - controller.queue_behavior(/datum/ai_behavior/repairbot_speech, key) - return + for(var/object_type in desired_types) + if(!istype(my_target, object_type)) + continue + var/obj/item/stack/sheet_type = locate(object_type) in controller.pawn + if(isnull(sheet_type)) + return TRUE //we dont have any of it! + if(sheet_type.amount < sheet_type.max_amount && sheet_type.can_merge(my_target)) + return TRUE + return FALSE -/datum/ai_behavior/repairbot_speech - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION +/datum/bt_node/ai_behavior/bot_search/refillable_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + var/static/list/refillable_items = typecacheof(list( + /obj/item/stack/sheet/iron, + /obj/item/stack/sheet/glass, + /obj/item/stack/tile, + )) + looking_for = refillable_items + return ..() -/datum/ai_behavior/repairbot_speech/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] - var/list/speech_to_pick_from = (target_key == BB_DECONSTRUCT_TARGET) ? controller.blackboard[BB_REPAIRBOT_EMAGGED_SPEECH] : controller.blackboard[BB_REPAIRBOT_NORMAL_SPEECH] - if(!length(speech_to_pick_from)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - announcement.announce(pick(speech_to_pick_from)) - controller.set_blackboard_key(BB_REPAIRBOT_SPEECH_COOLDOWN, world.time + REPAIRBOT_SPEECH_TIMER) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -///subtree to replace iron platings -/datum/ai_planning_subtree/replace_floors - ///flag we check before executing - var/required_flag = REPAIRBOT_REPLACE_TILES - ///key of our floor target - var/floor_key = BB_TILELESS_FLOOR - ///type of tile we need to replace floors - var/needed_tile_type = /obj/item/stack/tile - ///type of floors we can replace - var/list/type_of_turf = list(/turf/open/floor/plating) - ///our searching behavior - var/search_behavior = /datum/ai_behavior/bot_search/valid_plateless_turf - -/datum/ai_planning_subtree/replace_floors/New() - . = ..() - type_of_turf = typecacheof(type_of_turf) +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf + turf_search = TRUE + time_between_perform = 5 SECONDS -/datum/ai_planning_subtree/replace_floors/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn - if(!(bot_pawn.repairbot_flags & required_flag)) - return - if(!locate(needed_tile_type) in bot_pawn) - return - if(controller.blackboard_key_exists(floor_key)) - controller.queue_behavior(/datum/ai_behavior/bot_interact, floor_key) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/proc/get_turf_type_filter() + return typecacheof(list(/turf/open/floor/plating)) - controller.queue_behavior(search_behavior, floor_key, type_of_turf, 5, 10, FALSE, TRUE) +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/setup(datum/ai_controller/controller) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & REPAIRBOT_REPLACE_TILES)) + return FALSE + if(!locate(/obj/item/stack/tile) in bot_pawn) + return FALSE + return TRUE -/datum/ai_behavior/bot_search/valid_plateless_turf - action_cooldown = 5 SECONDS +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + looking_for = get_turf_type_filter() + return ..() -/datum/ai_behavior/bot_search/valid_plateless_turf/valid_target(datum/ai_controller/basic_controller/bot/controller, turf/open/my_target) +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/valid_target(datum/ai_controller/basic_controller/bot/controller, turf/open/my_target) var/static/list/blacklist_objects = typecacheof(list( /obj/structure/window, /obj/structure/grille, )) - for(var/atom/possible_blacklisted in my_target.contents) if(is_type_in_typecache(possible_blacklisted, blacklist_objects)) return FALSE - if(istype(my_target, /turf/open/floor/plating) && !can_see(controller.pawn, my_target, 5)) return FALSE - var/static/list/blacklist_areas = typecacheof(list( /area/space, /area/station/maintenance, @@ -202,129 +160,133 @@ var/turf_area = get_area(my_target) return !(is_type_in_typecache(turf_area, blacklist_areas)) -///subtree to fix hull breaches -/datum/ai_planning_subtree/replace_floors/breaches - floor_key = BB_BREACHED_FLOOR - needed_tile_type = /obj/item/stack/tile/iron - type_of_turf = list(/turf/open/space) - required_flag = REPAIRBOT_FIX_BREACHES - search_behavior = /datum/ai_behavior/bot_search/valid_plateless_turf/breached +/// Breach variant: searches /turf/open/space instead, requires REPAIRBOT_FIX_BREACHES flag. +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached -///exists as to not conflict with the base turf searching behavior cause of how the queue system works... -/datum/ai_behavior/bot_search/valid_plateless_turf/breached +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached/setup(datum/ai_controller/controller) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & REPAIRBOT_FIX_BREACHES)) + return FALSE + if(!locate(/obj/item/stack/tile/iron) in bot_pawn) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached/get_turf_type_filter() + return typecacheof(list(/turf/open/space)) -///subtree to build girders -/datum/ai_planning_subtree/build_girder +/datum/bt_node/ai_behavior/bot_search/valid_girder + time_between_perform = 5 SECONDS -/datum/ai_planning_subtree/build_girder/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) +/datum/bt_node/ai_behavior/bot_search/valid_girder/setup(datum/ai_controller/controller) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & REPAIRBOT_FIX_GIRDERS)) + return FALSE + var/obj/item/stack/sheet/iron/my_iron = locate() in bot_pawn + if(isnull(my_iron) || my_iron.amount < 2) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/bot_search/valid_girder/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + var/static/list/searchable_girder = typecacheof(list(/obj/structure/girder)) + looking_for = searchable_girder + return ..() + +/datum/bt_node/ai_behavior/bot_search/valid_girder/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/my_target) + if(!istype(my_target, /obj/structure/girder)) + return FALSE + return isfloorturf(my_target.loc) + + +/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder + maximum_distance = 1 + +/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder/setup(datum/ai_controller/controller) var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn if(!(bot_pawn.repairbot_flags & REPAIRBOT_BUILD_GIRDERS)) - return + return FALSE var/obj/item/stack/rods/my_rods = locate() in bot_pawn if(isnull(my_rods) || my_rods.amount < 2) - return - var/datum/action/cooldown/ability = controller.blackboard[BB_GIRDER_BUILD_ABILITY] + return FALSE + var/datum/action/cooldown/ability = controller.blackboard[ability_key] if(!ability?.IsAvailable()) - return - if(controller.blackboard_key_exists(BB_GIRDER_TARGET)) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/build_girder, BB_GIRDER_BUILD_ABILITY, BB_GIRDER_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + return FALSE + return ..() - var/static/list/searchable_turfs = typecacheof(list(/turf/open)) - controller.queue_behavior(/datum/ai_behavior/bot_search/valid_wall_target, BB_GIRDER_TARGET, searchable_turfs, 5, 10, FALSE, TRUE) +/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +/// Search for open turfs adjacent to space (valid girder build locations). +/datum/bt_node/ai_behavior/bot_search/valid_wall_target + turf_search = TRUE + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/bot_search/valid_wall_target/setup(datum/ai_controller/controller) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & REPAIRBOT_BUILD_GIRDERS)) + return FALSE + var/obj/item/stack/rods/my_rods = locate() in bot_pawn + if(isnull(my_rods) || my_rods.amount < 2) + return FALSE + return TRUE -/datum/ai_behavior/bot_search/valid_wall_target - action_cooldown = 5 SECONDS +/datum/bt_node/ai_behavior/bot_search/valid_wall_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + var/static/list/searchable_turfs = typecacheof(list(/turf/open)) + looking_for = searchable_turfs + return ..() -/datum/ai_behavior/bot_search/valid_wall_target/valid_target(datum/ai_controller/basic_controller/bot/controller, turf/my_target) +/datum/bt_node/ai_behavior/bot_search/valid_wall_target/valid_target(datum/ai_controller/basic_controller/bot/controller, turf/my_target) + if(!istype(my_target, /turf/open)) + return FALSE if(istype(get_area(my_target), /area/space) || isgroundlessturf(my_target) || my_target.is_blocked_turf()) return FALSE - var/static/list/blacklist_objects = list( + var/static/list/blacklist_objects = typecacheof(list( /obj/machinery/door, /obj/structure/grille, - ) - + )) for(var/atom/contents in my_target) if(is_type_in_typecache(contents, blacklist_objects)) return FALSE - var/turf/adjacent_turfs = get_adjacent_open_turfs(my_target) for(var/turf/possible_spaced_turf as anything in adjacent_turfs) if(isspaceturf(possible_spaced_turf) && istype(get_area(possible_spaced_turf), /area/space)) return TRUE return FALSE -/datum/ai_behavior/targeted_mob_ability/build_girder - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION +/datum/bt_node/ai_behavior/bot_search/valid_grille_target + time_between_perform = 5 SECONDS -/datum/ai_behavior/targeted_mob_ability/build_girder/setup(datum/ai_controller/controller, ability_key, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) +/datum/bt_node/ai_behavior/bot_search/valid_grille_target/setup(datum/ai_controller/controller) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & REPAIRBOT_REPLACE_WINDOWS)) return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/targeted_mob_ability/build_girder/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) - . = ..() - controller.clear_blackboard_key(target_key) + if(!locate(/obj/item/stack/sheet/glass) in bot_pawn) + return FALSE + return TRUE -///subtree to place glass on windows -/datum/ai_planning_subtree/replace_window - -/datum/ai_planning_subtree/replace_window/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/repairbot/living_pawn = controller.pawn - if(!(living_pawn.repairbot_flags & REPAIRBOT_REPLACE_WINDOWS)) - return - if(!locate(/obj/item/stack/sheet/glass) in living_pawn) - return - if(controller.blackboard_key_exists(BB_WINDOW_FRAMETARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_WINDOW_FRAMETARGET) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/bot_search/valid_grille_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/static/list/searchable_grilles = typecacheof(list(/obj/structure/grille)) - controller.queue_behavior(/datum/ai_behavior/bot_search/valid_grille_target, BB_WINDOW_FRAMETARGET, searchable_grilles) + looking_for = searchable_grilles + return ..() -/datum/ai_behavior/bot_search/valid_grille_target/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/structure/my_target) +/datum/bt_node/ai_behavior/bot_search/valid_grille_target/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/structure/my_target) + if(!istype(my_target, /obj/structure/grille)) + return FALSE if(locate(/obj/structure/window) in get_turf(my_target)) return FALSE return (!istype(get_area(my_target), /area/space)) +/datum/bt_node/ai_behavior/bot_search/valid_window_fix + time_between_perform = 5 SECONDS -///subtree to place iron on girders -/datum/ai_planning_subtree/wall_girder - -/datum/ai_planning_subtree/wall_girder/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/repairbot/living_pawn = controller.pawn - if(!(living_pawn.repairbot_flags & REPAIRBOT_FIX_GIRDERS)) - return - var/obj/item/stack/sheet/iron/my_iron = locate() in living_pawn - if(isnull(my_iron) || my_iron.amount < 2) - return - if(controller.blackboard_key_exists(BB_GIRDER_TO_WALL_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_GIRDER_TO_WALL_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - var/static/list/searchable_girder = typecacheof(list(/obj/structure/girder)) - controller.queue_behavior(/datum/ai_behavior/bot_search/valid_girder, BB_GIRDER_TO_WALL_TARGET, searchable_girder) - -/datum/ai_behavior/bot_search/valid_girder - action_cooldown = 5 SECONDS - -/datum/ai_behavior/bot_search/valid_girder/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/my_target) - return isfloorturf(my_target.loc) - -///subtree to repair machines with welders -/datum/ai_planning_subtree/fix_window - -/datum/ai_planning_subtree/fix_window/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_WELDER_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_WELDER_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/bot_search/valid_window_fix/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/static/list/searchable_objects = typecacheof(list(/obj/structure/window)) - controller.queue_behavior(/datum/ai_behavior/bot_search/valid_window_fix, BB_WELDER_TARGET, searchable_objects) + looking_for = searchable_objects + return ..() -/datum/ai_behavior/bot_search/valid_window_fix - action_cooldown = 5 SECONDS - -/datum/ai_behavior/bot_search/valid_window_fix/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/my_target) +/datum/bt_node/ai_behavior/bot_search/valid_window_fix/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/my_target) + if(!istype(my_target, /obj/structure/window)) + return FALSE return (my_target.get_integrity() < my_target.max_integrity || !my_target.anchored) #undef REPAIRBOT_SPEECH_TIMER diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.json new file mode 100644 index 000000000000..3970e4e04afb --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.json @@ -0,0 +1,71 @@ +{ + "dm_type": "/datum/bt_node/subtree/repairbot_emagged", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_ROBOT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ROBOT_TARGET", + "required_dist": 0, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_interact/tip_robot", + "vars": { + "target_key": "BB_ROBOT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/grab_target", + "vars": { + "target_key": "BB_ROBOT_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_DECONSTRUCT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DECONSTRUCT_TARGET", + "required_dist": 0, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_interact", + "vars": { + "target_key": "BB_DECONSTRUCT_TARGET" + } + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json new file mode 100644 index 000000000000..b9d7b7f5c7f1 --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json @@ -0,0 +1,143 @@ +{ + "dm_type": "/datum/bt_node/subtree/repairbot_find_target", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_is_emagged", + "vars": { + "invert": false + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_robot", + "vars": { + "target_key": "BB_ROBOT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/deconstructable", + "vars": { + "target_key": "BB_DECONSTRUCT_TARGET", + "minimum_distance": 1 + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "invert": true, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/refillable_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_grille_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_window_fix", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_girder", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_REPAIRBOT_INTERACTION_TYPE", + "value": "REPAIRBOT_INTERACTION_INTERACT" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/cancel_current_plan" + } + ] + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_wall_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_REPAIRBOT_INTERACTION_TYPE", + "value": "REPAIRBOT_INTERACTION_BUILD_GIRDERS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/cancel_current_plan" + } + ] + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json new file mode 100644 index 000000000000..d8e44907e6ce --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json @@ -0,0 +1,88 @@ +{ + "dm_type": "/datum/bt_node/subtree/repairbot_repair_target", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_is_emagged", + "vars": { + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/repairbot_emagged" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_is_emagged", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_REPAIRBOT_INTERACTION_TYPE", + "value": "REPAIRBOT_INTERACTION_INTERACT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_interact", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_REPAIRBOT_INTERACTION_TYPE", + "value": "REPAIRBOT_INTERACTION_BUILD_GIRDERS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder", + "vars": { + "ability_key": "BB_GIRDER_BUILD_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + } + } + ] + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] +} diff --git a/code/modules/mob/living/basic/bots/secbot/secbot.bt.json b/code/modules/mob/living/basic/bots/secbot/secbot.bt.json new file mode 100644 index 000000000000..e7908f713eff --- /dev/null +++ b/code/modules/mob/living/basic/bots/secbot/secbot.bt.json @@ -0,0 +1,85 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/secbot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/secbot/secbot.dm b/code/modules/mob/living/basic/bots/secbot/secbot.dm index 46b2835764c0..df9633d92656 100644 --- a/code/modules/mob/living/basic/bots/secbot/secbot.dm +++ b/code/modules/mob/living/basic/bots/secbot/secbot.dm @@ -198,7 +198,7 @@ /mob/living/basic/bot/secbot/proc/on_entered(datum/source, atom/movable/to_be_tripped) SIGNAL_HANDLER - var/mob/living/possible_target = ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/mob/living/possible_target = ai_controller.blackboard[BB_CURRENT_TARGET] if(!has_gravity() || !ismob(to_be_tripped) || !possible_target) return var/mob/living/carbon/tripped_mob = to_be_tripped diff --git a/code/modules/mob/living/basic/bots/secbot/secbot_ai.dm b/code/modules/mob/living/basic/bots/secbot/secbot_ai.dm index 6aadd73fd218..2de48a126394 100644 --- a/code/modules/mob/living/basic/bots/secbot/secbot_ai.dm +++ b/code/modules/mob/living/basic/bots/secbot/secbot_ai.dm @@ -1,23 +1,17 @@ /datum/ai_controller/basic_controller/bot/secbot + behavior_tree_json = "code/modules/mob/living/basic/bots/secbot/secbot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/secbot, BB_UNREACHABLE_LIST_COOLDOWN = 1 MINUTES, BB_ALWAYS_IGNORE_FACTION = TRUE, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/arrest_target, - /datum/ai_planning_subtree/find_patrol_beacon, - ) reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, ) -/datum/targeting_strategy/basic/secbot/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/secbot/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) var/datum/ai_controller/basic_controller/bot/my_controller = living_mob.ai_controller if(isnull(my_controller)) return FALSE @@ -37,13 +31,12 @@ my_controller.set_blackboard_key(BB_CURRENT_CRIMINAL_ASSESSMENT, assessed_threat) return (assessed_threat > THREAT_ASSESS_DANGEROUS) - /datum/ai_controller/basic_controller/bot/secbot/TryPossessPawn(atom/new_pawn) . = ..() if(. & AI_CONTROLLER_INCOMPATIBLE) return - RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(on_target_set)) - RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(on_clear_target)) + RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET), PROC_REF(on_target_set)) + RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_CURRENT_TARGET), PROC_REF(on_clear_target)) /datum/ai_controller/basic_controller/bot/secbot/proc/on_target_set() SIGNAL_HANDLER @@ -61,31 +54,3 @@ /datum/ai_controller/basic_controller/bot/secbot/proc/on_clear_target() SIGNAL_HANDLER clear_blackboard_key(BB_CURRENT_CRIMINAL_ASSESSMENT) - -/datum/ai_planning_subtree/arrest_target - ///what behavior do we use when arresting? - var/datum/ai_behavior/arrest_behavior = /datum/ai_behavior/basic_melee_attack/interact_once/bot - -/datum/ai_planning_subtree/arrest_target/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/carbon/my_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(my_target) || !istype(my_target) || my_target.handcuffed) - controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) - return - - var/mob/living/basic/bot/secbot/my_bot = controller.pawn - var/bot_flags = my_bot.security_mode_flags - if(my_target.IsParalyzed() && !(bot_flags & SECBOT_HANDCUFF_TARGET)) - controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) - return - - controller.queue_behavior(arrest_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/basic_melee_attack/interact_once/bot - movement_behavior = /datum/ai_movement/basic_avoidance - -/datum/ai_behavior/basic_melee_attack/interact_once/bot/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - var/mob/living/carbon/human/human_target = controller.blackboard[target_key] - if(!isnull(human_target) && human_target.handcuffed) - controller.remove_from_blackboard_lazylist_key(BB_BASIC_MOB_RETALIATE_LIST, human_target) - return ..() diff --git a/code/modules/mob/living/basic/bots/secbot/super_beepsky.dm b/code/modules/mob/living/basic/bots/secbot/super_beepsky.dm index 1f82db49c7a5..f3dcd1c5bf83 100644 --- a/code/modules/mob/living/basic/bots/secbot/super_beepsky.dm +++ b/code/modules/mob/living/basic/bots/secbot/super_beepsky.dm @@ -57,7 +57,7 @@ /mob/living/basic/bot/secbot/grievous/on_entered(datum/source, atom/movable/movable_target) . = ..() - if(!ismob(movable_target) || !ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] == movable_target) + if(!ismob(movable_target) || !ai_controller.blackboard[BB_CURRENT_TARGET] == movable_target) return visible_message(span_warning("[src] flails his swords and cuts [movable_target]!")) playsound(src, 'sound/mobs/non-humanoids/beepsky/beepskyspinsabre.ogg' , 100, TRUE, -1) diff --git a/code/modules/mob/living/basic/bots/secbot/super_beepsky_ai.dm b/code/modules/mob/living/basic/bots/secbot/super_beepsky_ai.dm index effd9a98cab6..93ab2932f48c 100644 --- a/code/modules/mob/living/basic/bots/secbot/super_beepsky_ai.dm +++ b/code/modules/mob/living/basic/bots/secbot/super_beepsky_ai.dm @@ -1,16 +1,8 @@ /datum/ai_controller/basic_controller/bot/secbot/super_beepsky - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_patrol_beacon, - ) - /datum/ai_controller/basic_controller/bot/secbot/super_beepsky/on_target_set() . = ..() var/mob/living/basic/bot/secbot/grievous/super_beeps = pawn if(!super_beeps.sword_active) INVOKE_ASYNC(super_beeps.weapon, TYPE_PROC_REF(/obj/item, attack_self), super_beeps) - super_beeps.visible_message("[super_beeps] points at [blackboard[BB_BASIC_MOB_CURRENT_TARGET]]!") + super_beeps.visible_message("[super_beeps] points at [blackboard[BB_CURRENT_TARGET]]!") diff --git a/code/modules/mob/living/basic/bots/vibebot/vibebot.bt.json b/code/modules/mob/living/basic/bots/vibebot/vibebot.bt.json new file mode 100644 index 000000000000..696062b91c0a --- /dev/null +++ b/code/modules/mob/living/basic/bots/vibebot/vibebot.bt.json @@ -0,0 +1,74 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/vibebot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_VIBEBOT_PARTY_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_VIBEBOT_PARTY_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/vibebot_party", + "vars": { + "ability_key": "BB_VIBEBOT_PARTY_ABILITY", + "target_key": "BB_VIBEBOT_PARTY_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_VIBEBOT_PARTY_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_human/party_friend", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "vision_range": 5, + "time_between_perform": "5 SECONDS" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/vibebot/vibebot_ai.dm b/code/modules/mob/living/basic/bots/vibebot/vibebot_ai.dm index 77773746b08d..6b4aaa9efd73 100644 --- a/code/modules/mob/living/basic/bots/vibebot/vibebot_ai.dm +++ b/code/modules/mob/living/basic/bots/vibebot/vibebot_ai.dm @@ -1,16 +1,11 @@ /datum/ai_controller/basic_controller/bot/vibebot + behavior_tree_json = "code/modules/mob/living/basic/bots/vibebot/vibebot.bt.json" blackboard = list( BB_UNREACHABLE_LIST_COOLDOWN = 2 MINUTES, BB_VIBEBOT_HAPPY_SONG = VIBEBOT_CHEER_SONG, BB_VIBEBOT_GRIM_SONG = VIBEBOT_GRIM_MUSIC, BB_VIBEBOT_BIRTHDAY_SONG = VIBEBOT_HAPPY_BIRTHDAY, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/find_party_friends, - /datum/ai_planning_subtree/find_patrol_beacon, - ) reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, @@ -45,43 +40,36 @@ song.start_playing(pawn) addtimer(CALLBACK(song, TYPE_PROC_REF(/datum/song, stop_playing)), 10 SECONDS) //in 10 seconds, stop playing music -///subtree we use to find party friends in general -/datum/ai_planning_subtree/find_party_friends - -/datum/ai_planning_subtree/find_party_friends/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/static/list/type_to_search = typecacheof(list(/mob/living/carbon/human)) - if(!controller.blackboard_key_exists(BB_VIBEBOT_PARTY_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_search/party_friends, BB_VIBEBOT_PARTY_TARGET, type_to_search) - return - - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target/vibebot_party, BB_VIBEBOT_PARTY_ABILITY, BB_VIBEBOT_PARTY_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -///behavior we use to party with people -/datum/ai_behavior/targeted_mob_ability/and_clear_target/vibebot_party - behavior_flags = AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_REQUIRE_MOVEMENT -/datum/ai_behavior/targeted_mob_ability/and_clear_target/vibebot_party/setup(datum/ai_controller/controller, ability_key, target_key) +/// Valid if the target is a conscious human who's in a bad mood or having a birthday someone who could use cheering up. +/datum/targeting_strategy/conscious_human/party_friend/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) + if(!.) return FALSE - set_movement_target(controller, target) + var/mob/living/carbon/human/human_target = target + return human_target.mob_mood?.mood_level < MOOD_LEVEL_NEUTRAL || HAS_TRAIT(human_target, TRAIT_BIRTHDAY_BOY) -/datum/ai_behavior/targeted_mob_ability/and_clear_target/vibebot_party/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, ability_key, target_key) - var/atom/target = controller.blackboard[target_key] - controller.add_to_blacklist(target) - if(succeeded) - var/mob/living/living_pawn = controller.pawn - living_pawn.manual_emote("celebrates with [target]!") - living_pawn.emote("flip") - return ..() +/datum/bt_node/ai_behavior/vibebot_party + var/ability_key + var/target_key -///behavior that searches for party friends -/datum/ai_behavior/bot_search/party_friends - action_cooldown = 5 SECONDS +/datum/bt_node/ai_behavior/vibebot_party/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(get_dist(controller.pawn, living_target) > 1) + return AI_BEHAVIOR_INSTANT + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + if(ability) + INVOKE_ASYNC(ability, TYPE_PROC_REF(/datum/action, Trigger), living_target) + var/mob/living/living_pawn = controller.pawn + living_pawn.manual_emote("celebrates with [living_target]!") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), "flip") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/bot_search/party_friends/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/carbon/human/my_target) - if(my_target.stat != CONSCIOUS || isnull(my_target.mind)) - return FALSE - return (my_target.mob_mood.mood_level < MOOD_LEVEL_NEUTRAL || HAS_TRAIT(my_target, TRAIT_BIRTHDAY_BOY)) +/datum/bt_node/ai_behavior/vibebot_party/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(!isnull(target)) + controller.add_to_blacklist(target) + controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/clown/clown.bt.json b/code/modules/mob/living/basic/clown/clown.bt.json new file mode 100644 index 000000000000..bbfc9f477e5a --- /dev/null +++ b/code/modules/mob/living/basic/clown/clown.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/clown", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/clown/clown_ai.dm b/code/modules/mob/living/basic/clown/clown_ai.dm index dfa9b71a40eb..091e14ef167c 100644 --- a/code/modules/mob/living/basic/clown/clown_ai.dm +++ b/code/modules/mob/living/basic/clown/clown_ai.dm @@ -1,17 +1,11 @@ /datum/ai_controller/basic_controller/clown + behavior_tree_json = "code/modules/mob/living/basic/clown/clown.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_BASIC_MOB_SPEAK_LINES = null, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/blackboard, - ) /datum/ai_controller/basic_controller/clown/murder blackboard = list( diff --git a/code/modules/mob/living/basic/cult/constructs/artificer.bt.json b/code/modules/mob/living/basic/cult/constructs/artificer.bt.json new file mode 100644 index 000000000000..f0ed947ff493 --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/artificer.bt.json @@ -0,0 +1,83 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/artificer", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1 + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/cult/constructs/construct_ai.dm b/code/modules/mob/living/basic/cult/constructs/construct_ai.dm index c7ea47a83ae6..6f333bd20c4a 100644 --- a/code/modules/mob/living/basic/cult/constructs/construct_ai.dm +++ b/code/modules/mob/living/basic/cult/constructs/construct_ai.dm @@ -5,6 +5,7 @@ * If there is no one to heal, they will run away from any non-allied mobs. */ /datum/ai_controller/basic_controller/artificer + behavior_tree_json = "code/modules/mob/living/basic/cult/constructs/artificer.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/same_faction/construct, BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, @@ -12,14 +13,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_wounded_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - ) /** * Juggernauts @@ -27,19 +20,13 @@ * Juggernauts slowly walk toward non-allied mobs and pummel them to death. */ /datum/ai_controller/basic_controller/juggernaut + behavior_tree_json = "code/modules/mob/living/basic/cult/constructs/juggernaut.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /** * Proteons @@ -47,6 +34,7 @@ * Proteons perform cowardly hit-and-run attacks, fleeing melee when struck but returning to fight again. */ /datum/ai_controller/basic_controller/proteon + behavior_tree_json = "code/modules/mob/living/basic/cult/constructs/proteon.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, @@ -54,15 +42,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /** * Wraiths @@ -70,25 +49,17 @@ * Wraiths seek out the most injured non-allied mob to beat to death. */ /datum/ai_controller/basic_controller/wraith + behavior_tree_json = "code/modules/mob/living/basic/cult/constructs/wraith.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_wounded_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - /// Targeting strategy that will only allow mobs that constructs can heal. /datum/targeting_strategy/basic/same_faction/construct target_wounded_key = BB_TARGET_WOUNDED_ONLY -/datum/targeting_strategy/basic/same_faction/construct/can_attack(mob/living/living_mob, atom/the_target, vision_range, check_faction = TRUE) +/datum/targeting_strategy/basic/same_faction/construct/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) if(isconstruct(the_target) || istype(the_target, /mob/living/basic/shade)) return ..() return FALSE diff --git a/code/modules/mob/living/basic/cult/constructs/juggernaut.bt.json b/code/modules/mob/living/basic/cult/constructs/juggernaut.bt.json new file mode 100644 index 000000000000..9cbc8c4bd8e4 --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/juggernaut.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/juggernaut", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" +} diff --git a/code/modules/mob/living/basic/cult/constructs/proteon.bt.json b/code/modules/mob/living/basic/cult/constructs/proteon.bt.json new file mode 100644 index 000000000000..df911230d81f --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/proteon.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/proteon", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_brawler_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/cult/constructs/wraith.bt.json b/code/modules/mob/living/basic/cult/constructs/wraith.bt.json new file mode 100644 index 000000000000..139ac7447819 --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/wraith.bt.json @@ -0,0 +1,83 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/wraith", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1 + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/cytology/vatbeast.bt.json b/code/modules/mob/living/basic/cytology/vatbeast.bt.json new file mode 100644 index 000000000000..2e5924a2fdc0 --- /dev/null +++ b/code/modules/mob/living/basic/cytology/vatbeast.bt.json @@ -0,0 +1,137 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/vatbeast", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_at_least", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_BASIC_MOB_HAS_TARGET_TIME", + "minimum": "10 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/melee", + "vars": { + "ability_key": "BB_GENERIC_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_FOOD", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_typed/from_bb_key/basic_foods" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/cytology/vatbeast.dm b/code/modules/mob/living/basic/cytology/vatbeast.dm index a52411f0d824..b0de4c1d7548 100644 --- a/code/modules/mob/living/basic/cytology/vatbeast.dm +++ b/code/modules/mob/living/basic/cytology/vatbeast.dm @@ -47,7 +47,7 @@ var/datum/action/cooldown/tentacle_slap/slapper = new (src) slapper.Grant(src) - ai_controller.set_blackboard_key(BB_TARGETED_ACTION, slapper) + ai_controller.set_blackboard_key(BB_GENERIC_ACTION, slapper) ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(enjoyed_food)) /mob/living/basic/vatbeast/tamed(mob/living/tamer, obj/item/food) @@ -64,34 +64,12 @@ /// Attack people and slap them /datum/ai_controller/basic_controller/vatbeast + behavior_tree_json = "code/modules/mob/living/basic/cytology/vatbeast.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/targeted_mob_ability/vatbeast_slap, - /datum/ai_planning_subtree/basic_melee_attack_subtree - ) - -/// Only do this if we are adjacent to target and have been mad at the same guy for at least 10 seconds -/// That slap REALLY hurts -/datum/ai_planning_subtree/targeted_mob_ability/vatbeast_slap - operational_datums = list(/datum/component/ai_target_timer) - -/datum/ai_planning_subtree/targeted_mob_ability/vatbeast_slap/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[target_key] - if (!isliving(target) || !controller.pawn.Adjacent(target)) - return - var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 - if (time_on_target < 10 SECONDS) - return - return ..() /// Ability that allows the owner to slap other mobs a short distance away. /// For vatbeats, this ability is shared with the rider. diff --git a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm index 409035c9683b..ccd0a0b04da0 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm @@ -55,7 +55,7 @@ /datum/pet_command/free, /datum/pet_command/beehive/enter, /datum/pet_command/beehive/exit, - /datum/pet_command/follow/bee, + /datum/pet_command/follow, /datum/pet_command/attack/swirl, /datum/pet_command/scatter, ) diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee.bt.json b/code/modules/mob/living/basic/farm_animals/bee/bee.bt.json new file mode 100644 index 000000000000..727a2cfd8d52 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/bee.bt.json @@ -0,0 +1,86 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bee", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_hive" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/transition_hive_status" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pollinate_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enter_exit_hive" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.85 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_HYDRO", + "target_source": "/datum/target_source/oview_single_type/hydroponics", + "targeting_strategy": "/datum/targeting_strategy/pollinatable_hydro", + "vision_range": 10, + "time_between_perform": "10 SECONDS" + } + } + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_TARGET_HOME" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_hive" + } + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm index 2a6ff31981c4..a5fdb64085f6 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm @@ -1,87 +1,9 @@ /// if we have a hive, this will be our aggro distance #define AGGRO_DISTANCE_FROM_HIVE 2 -/datum/ai_behavior/hunt_target/pollinate - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/pollinate/target_caught(mob/living/hunter, obj/machinery/hydroponics/hydro_target) - var/datum/callback/callback = CALLBACK(hunter, TYPE_PROC_REF(/mob/living/basic/bee, pollinate), hydro_target) - callback.Invoke() - -/datum/ai_behavior/find_hunt_target/pollinate - action_cooldown = 10 SECONDS - -/datum/ai_behavior/find_hunt_target/pollinate/valid_dinner(mob/living/source, obj/machinery/hydroponics/dinner, radius) - if(!dinner.can_bee_pollinate()) - return FALSE - return can_see(source, dinner, radius) - -/datum/ai_behavior/enter_exit_hive - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 10 SECONDS - -/datum/ai_behavior/enter_exit_hive/setup(datum/ai_controller/controller, target_key, attack_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/enter_exit_hive/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key) - var/obj/structure/beebox/current_home = controller.blackboard[target_key] - var/atom/attack_target = controller.blackboard[attack_key] - - if(attack_target) // forget about who we attacking when we go home - controller.clear_blackboard_key(attack_key) - - controller.ai_interact(target = current_home, combat_mode = FALSE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/inhabit_hive - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/inhabit_hive/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/inhabit_hive/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/obj/structure/beebox/potential_home = controller.blackboard[target_key] - var/mob/living/bee_pawn = controller.pawn - - if(!potential_home.habitable(bee_pawn)) //the house become full before we get to it - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target = potential_home, combat_mode = FALSE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/inhabit_hive/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(!succeeded) - controller.clear_blackboard_key(target_key) //failed to make it our home so find another - -/datum/ai_behavior/find_and_set/bee_hive - action_cooldown = 10 SECONDS - -/datum/ai_behavior/find_and_set/bee_hive/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/valid_hives = list() - var/mob/living/bee_pawn = controller.pawn - - if(istype(bee_pawn.loc, /obj/structure/beebox)) - return bee_pawn.loc //for premade homes - - for(var/obj/structure/beebox/potential_home in oview(search_range, bee_pawn)) - if(!potential_home.habitable(bee_pawn)) - continue - valid_hives += potential_home - - if(valid_hives.len) - return pick(valid_hives) /datum/targeting_strategy/basic/bee -/datum/targeting_strategy/basic/bee/can_attack(mob/living/owner, atom/target, vision_range) +/datum/targeting_strategy/basic/bee/is_valid_target(mob/living/owner, atom/target, vision_range, datum/ai_controller/controller = null) if(!isliving(target)) return FALSE . = ..() @@ -104,14 +26,6 @@ return !(mob_target.bee_friendly()) -///pet commands -/datum/pet_command/follow/bee - ///the behavior we use to follow - follow_behavior = /datum/ai_behavior/pet_follow_friend/bee - -/datum/ai_behavior/pet_follow_friend/bee - required_distance = 0 - ///swirl around the owner in menacing fashion /datum/pet_command/attack/swirl command_name = "Swirl" @@ -138,49 +52,7 @@ /datum/pet_command/attack/swirl/execute_action(datum/ai_controller/controller) if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) return ..() - controller.queue_behavior(/datum/ai_behavior/swirl_around_target, BB_SWARM_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/swirl_around_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - required_distance = 0 - ///chance to swirl - var/swirl_chance = 60 - -/datum/ai_behavior/swirl_around_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/swirl_around_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - var/mob/living/living_pawn = controller.pawn - - if(QDELETED(target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - - if(get_dist(target, living_pawn) > 1) - set_movement_target(controller, target) - return AI_BEHAVIOR_DELAY - - if(!SPT_PROB(swirl_chance, seconds_per_tick)) - return AI_BEHAVIOR_DELAY - - var/list/possible_turfs = list() - - for(var/turf/possible_turf in oview(2, target)) - if(possible_turf.is_blocked_turf(source_atom = living_pawn)) - continue - possible_turfs += possible_turf - - if(!length(possible_turfs)) - return AI_BEHAVIOR_DELAY - - if(isnull(controller.movement_target_source) || controller.movement_target_source == type) - set_movement_target(controller, pick(possible_turfs)) - return AI_BEHAVIOR_DELAY + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/swirl) /datum/pet_command/beehive @@ -205,9 +77,7 @@ return /datum/pet_command/beehive/execute_action(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/enter_exit_hive, BB_CURRENT_HOME) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/beehive) /datum/pet_command/beehive/enter command_name = "Enter beehive" @@ -237,11 +107,7 @@ set_command_target(parent, commander) /datum/pet_command/scatter/execute_action(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/run_away_from_target/scatter, BB_CURRENT_PET_TARGET) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/scatter) -/datum/ai_behavior/run_away_from_target/scatter - run_distance = 4 #undef AGGRO_DISTANCE_FROM_HIVE diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm index 3af1bb7e52c3..4047ad5f6a54 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm @@ -6,22 +6,7 @@ ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_valid_home, - /datum/ai_planning_subtree/enter_exit_home, - /datum/ai_planning_subtree/find_and_hunt_target/pollinate, - /datum/ai_planning_subtree/simple_find_target/bee, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/simple_find_target/bee/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/hydro_target = controller.blackboard[BB_TARGET_HYDRO] - if(hydro_target) - return SUBTREE_RETURN_FINISH_PLANNING - return ..() + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/bee/bee.bt.json" /datum/ai_controller/basic_controller/queen_bee blackboard = list( @@ -30,70 +15,14 @@ ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_valid_home, - /datum/ai_planning_subtree/enter_exit_home/queen, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - - -/datum/ai_planning_subtree/find_valid_home - -/datum/ai_planning_subtree/find_valid_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/work_bee = controller.pawn - - var/obj/structure/beebox/current_home = controller.blackboard[BB_CURRENT_HOME] - - if(QDELETED(current_home)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/bee_hive, BB_CURRENT_HOME, /obj/structure/beebox) - return - - if(work_bee in current_home.bees) - return - - controller.queue_behavior(/datum/ai_behavior/inhabit_hive, BB_CURRENT_HOME) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_planning_subtree/enter_exit_home - ///chance we go back home - var/flyback_chance = 15 - ///chance we exit the home - var/exit_chance = 35 - -/datum/ai_planning_subtree/enter_exit_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - var/obj/structure/beebox/current_home = controller.blackboard[BB_CURRENT_HOME] - - if(QDELETED(current_home)) - return - - var/mob/living/bee_pawn = controller.pawn - var/action_prob = (bee_pawn.loc == current_home) ? exit_chance : flyback_chance - - if(!SPT_PROB(action_prob, seconds_per_tick)) - return + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.json" - controller.queue_behavior(/datum/ai_behavior/enter_exit_hive, BB_CURRENT_HOME, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING -//the queen spend more time in the hive -/datum/ai_planning_subtree/enter_exit_home/queen - flyback_chance = 85 - exit_chance = 5 +/datum/bt_node/subtree/pollinate_target + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.json" -/datum/ai_planning_subtree/find_and_hunt_target/pollinate - target_key = BB_TARGET_HYDRO - hunting_behavior = /datum/ai_behavior/hunt_target/pollinate - finding_behavior = /datum/ai_behavior/find_hunt_target/pollinate - hunt_targets = list(/obj/machinery/hydroponics) - hunt_range = 10 - hunt_chance = 85 +/datum/bt_node/subtree/find_hive + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/bee/find_hive.bt.json" -/datum/ai_planning_subtree/find_and_hunt_target/pollinate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/atom_pawn = controller.pawn - if(!isturf(atom_pawn.loc)) - return - return ..() +/datum/bt_node/subtree/transition_hive_status + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.json" diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_bt.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_bt.dm new file mode 100644 index 000000000000..62da5e54ef5a --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_bt.dm @@ -0,0 +1,132 @@ + + +/** + * Searches for a valid beebox home. Skipped if the bee is already inside its home. + * Sets BB_CURRENT_HOME when found. + */ +/datum/bt_node/ai_behavior/find_hive + +/datum/bt_node/ai_behavior/find_hive/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/bee/bee_pawn = controller.pawn + var/obj/structure/beebox/current_home = controller.blackboard[BB_CURRENT_HOME] + + if(!QDELETED(current_home)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED // already have a home; let the inhabit sequence handle it + + if(istype(bee_pawn.loc, /obj/structure/beebox)) + controller.set_blackboard_key(BB_CURRENT_HOME, bee_pawn.loc) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + for(var/obj/structure/beebox/potential_home in oview(10, bee_pawn)) + if(!potential_home.habitable(bee_pawn)) + continue + controller.set_blackboard_key(BB_TARGET_HOME, potential_home) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + +/** + * Moves to BB_CURRENT_HOME and inhabits it (ai_interact). Clears BB_CURRENT_HOME on failure. + * Must be in range (adjacent) to work. Returns FAILURE if home is gone or full. + */ +/datum/bt_node/ai_behavior/inhabit_hive + time_between_perform = 10 SECONDS + +/datum/bt_node/ai_behavior/inhabit_hive/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/bee/bee_pawn = controller.pawn + var/obj/structure/beebox/home = controller.blackboard[BB_CURRENT_HOME] + if(QDELETED(home)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!home.habitable(bee_pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!bee_pawn.Adjacent(home)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), home, FALSE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/inhabit_hive/finish_action(datum/ai_controller/controller, succeeded, ...) + . = ..() + if(!succeeded) + controller.clear_blackboard_key(BB_CURRENT_HOME) + +///Chance to leave or enter hive +/datum/bt_node/ai_behavior/enter_exit_hive + time_between_perform = 1 SECONDS + var/flyback_chance = 15 + var/exit_chance = 35 + +/datum/bt_node/ai_behavior/enter_exit_hive/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/structure/beebox/home = controller.blackboard[BB_CURRENT_HOME] + if(QDELETED(home)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/bee_pawn = controller.pawn + var/prob_to_use = (bee_pawn.loc == home) ? exit_chance : flyback_chance + + if(!SPT_PROB(prob_to_use, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(BB_WANTS_TO_TRANSITION_HIVE, TRUE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/// Queen variant: strongly prefers staying in hive. +/datum/bt_node/ai_behavior/enter_exit_hive/queen + flyback_chance = 85 + exit_chance = 5 + + +/// Pollinates the hydro tray at BB_TARGET_HYDRO. Must be adjacent. +/datum/bt_node/ai_behavior/pollinate_hydro + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/pollinate_hydro/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/machinery/hydroponics/tray = controller.blackboard[BB_TARGET_HYDRO] + var/mob/living/basic/bee/bee_pawn = controller.pawn + if(QDELETED(tray) || !bee_pawn.Adjacent(tray)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + bee_pawn.pollinate(tray) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/pollinate_hydro/finish_action(datum/ai_controller/controller, succeeded, ...) + . = ..() + controller.clear_blackboard_key(BB_TARGET_HYDRO) + + +/// Picks a random turf near BB_SWARM_TARGET and stores it in BB_SWIRL_TURF for move_to_target. +/// Succeeds when a turf is found; fails otherwise so the parent sequence fails and retries. +/datum/bt_node/ai_behavior/swirl_around_target + var/swirl_chance = 60 + +/datum/bt_node/ai_behavior/swirl_around_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[BB_SWARM_TARGET] + var/mob/living/bee_pawn = controller.pawn + if(QDELETED(target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + if(!SPT_PROB(swirl_chance, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/list/possible_turfs = list() + for(var/turf/possible_turf in oview(2, target)) + if(!possible_turf.is_blocked_turf(source_atom = bee_pawn)) + possible_turfs += possible_turf + + if(!length(possible_turfs)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(BB_SWIRL_TURF, pick(possible_turfs)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +/// Scatter command: runs away from BB_CURRENT_PET_TARGET then clears the command. +/datum/bt_node/subtree/pet_command/scatter + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.json" + +/// Swirl command: swarm around BB_SWARM_TARGET continuously (no auto-clear). +/datum/bt_node/subtree/pet_command/swirl + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.json" + +/// Beehive command: move to hive and enter/exit it. +/datum/bt_node/subtree/pet_command/beehive + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.json" diff --git a/code/modules/mob/living/basic/farm_animals/bee/find_hive.bt.json b/code/modules/mob/living/basic/farm_animals/bee/find_hive.bt.json new file mode 100644 index 000000000000..f6f7442f9823 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/find_hive.bt.json @@ -0,0 +1,43 @@ +{ + "dm_type": "/datum/bt_node/subtree/find_hive", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_TARGET_HOME" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_HOME", + "invert": true + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_HOME", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/inhabit_hive" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TARGET_HOME" + } + } + ] + } + } +} diff --git a/code/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.json b/code/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.json new file mode 100644 index 000000000000..0489fac6fb78 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.json @@ -0,0 +1,27 @@ +{ + "dm_type": "/datum/bt_node/subtree/pollinate_target", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TARGET_HYDRO" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_HYDRO", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pollinate_hydro" + } + ] + } +} diff --git a/code/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.json b/code/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.json new file mode 100644 index 000000000000..be98e5038ef9 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.json @@ -0,0 +1,58 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/queen_bee", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": false, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": false, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_hive" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pollinate_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/transition_hive_status" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enter_exit_hive/queen" + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_TARGET_HOME" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_hive" + } + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.json b/code/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.json new file mode 100644 index 000000000000..612f7d683ae8 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.json @@ -0,0 +1,45 @@ +{ + "dm_type": "/datum/bt_node/subtree/transition_hive_status", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_WANTS_TO_TRANSITION_HIVE", + "value": true + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_HOME", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_CURRENT_HOME" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_WANTS_TO_TRANSITION_HIVE", + "value": false + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chick.bt.json b/code/modules/mob/living/basic/farm_animals/chicken/chick.bt.json new file mode 100644 index 000000000000..4d7a67fca031 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/chicken/chick.bt.json @@ -0,0 +1,87 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/chick", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOUND_MOM", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_FOUND_MOM", + "min_distance": 2 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_MOM", + "required_dist": 1, + "finish_on_arrival": true + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.15 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/look_to_parent", + "vars": { + "parent_key": "BB_FOUND_MOM" + } + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOUND_MOM", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mom" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chick.dm b/code/modules/mob/living/basic/farm_animals/chicken/chick.dm index 44d2c4a00ee3..407556f807e0 100644 --- a/code/modules/mob/living/basic/farm_animals/chicken/chick.dm +++ b/code/modules/mob/living/basic/farm_animals/chicken/chick.dm @@ -81,15 +81,18 @@ grow_as = null /datum/ai_controller/basic_controller/chick + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/chicken/chick.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FIND_MOM_TYPES = list(/mob/living/basic/chicken), + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Cherp.", "Cherp?", "Chirrup.", "Cheep!"), + BB_EMOTE_HEAR = list("cheeps."), + BB_EMOTE_SEE = list("pecks at the ground.","flaps her tiny wings."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/chicken/chick_peep.ogg'), + BB_SPEAK_CHANCE = 4, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/look_for_adult, - ) diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chicken.bt.json b/code/modules/mob/living/basic/farm_animals/chicken/chicken.bt.json new file mode 100644 index 000000000000..fc59585dab73 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/chicken/chicken.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/chicken", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_and_speak" +} diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm b/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm index b8dbefd78ab2..3d2bb5b09962 100644 --- a/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm +++ b/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm @@ -94,17 +94,18 @@ GLOBAL_VAR_INIT(chicken_count, 0) ) /datum/ai_controller/basic_controller/chicken + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/chicken/chicken.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Cluck!", "BWAAAAARK BWAK BWAK BWAK!", "Bwaak bwak."), + BB_EMOTE_HEAR = list("clucks.", "croons."), + BB_EMOTE_SEE = list("pecks at the ground.", "flaps her wings viciously."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/chicken/clucks.ogg', 'sound/mobs/non-humanoids/chicken/bagawk.ogg'), + BB_SPEAK_CHANCE = 15, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/random_speech/chicken, - ) diff --git a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm index 3c6b1e81422d..7fcbb5eb84ed 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm @@ -90,7 +90,7 @@ * tipper - the mob who tipped us */ /mob/living/basic/cow/proc/after_cow_tipped(mob/living/carbon/tipper) - addtimer(CALLBACK(src, PROC_REF(set_tip_react_blackboard), tipper), rand(10 SECONDS, 20 SECONDS)) + addtimer(CALLBACK(src, PROC_REF(set_tip_react_blackboard), tipper), rand(10 SECONDS, 20 SECONDS)) /* * We've been waiting long enough, we're going to tell our AI to begin pleading. diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow.bt.json b/code/modules/mob/living/basic/farm_animals/cow/cow.bt.json new file mode 100644 index 000000000000..f1a812b7e679 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/cow/cow.bt.json @@ -0,0 +1,48 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cow", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/tip_reaction" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm index df485197d334..ece35ce96579 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm @@ -1,15 +1,21 @@ /datum/ai_controller/basic_controller/cow + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/cow/cow.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_BASIC_MOB_TIP_REACTING = FALSE, BB_BASIC_MOB_TIPPER = null, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("moo?", "moo", "MOOOOOO"), + BB_EMOTE_HEAR = list("brays."), + BB_EMOTE_SEE = list("shakes her head."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/cow/cow.ogg'), + BB_SPEAK_CHANCE = 1, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/tip_reaction, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/random_speech/cow, - ) + +/// While we're tipped over, plead with nearby people instead of doing anything else. +/datum/bt_node/subtree/tip_reaction + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.json" diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.json b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.json new file mode 100644 index 000000000000..dd56e0306bf3 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.json @@ -0,0 +1,96 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cow/moonicorn", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/tip_reaction" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm index 25879d07d5c0..5ee91b3a913f 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm @@ -40,27 +40,24 @@ APPLY_FACTION_AND_ALLIES_FROM(src, tamer) /datum/ai_controller/basic_controller/cow/moonicorn + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items/moonicorn, BB_BASIC_MOB_TIP_REACTING = FALSE, BB_BASIC_MOB_TIPPER = null, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/tip_reaction, - /datum/ai_planning_subtree/random_speech/cow, - //finds someone to kill - /datum/ai_planning_subtree/simple_find_target, - //...or something to eat, possibly. both types of target handled by melee attack subtree - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/basic_melee_attack_subtree, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("moo?", "moo", "MOOOOOO"), + BB_EMOTE_HEAR = list("brays."), + BB_EMOTE_SEE = list("shakes her head."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/cow/cow.ogg'), + BB_SPEAK_CHANCE = 1, + ), ) ///moonicorns will not attack people holding something that could tame them. /datum/targeting_strategy/basic/allow_items/moonicorn -/datum/targeting_strategy/basic/allow_items/moonicorn/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/allow_items/moonicorn/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) . = ..() if(!.) return FALSE diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.json b/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.json new file mode 100644 index 000000000000..300def27d686 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cow/wisdom", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/tip_reaction" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm index 4452ae3100f2..ef782795eb68 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm @@ -12,6 +12,10 @@ /mob/living/basic/cow/wisdom/Initialize(mapload, granted_wisdom, granted_experience = 500, milked_reagent = null) src.milked_reagent = milked_reagent . = ..() + ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, list( + BB_EMOTE_SAY = GLOB.wisdoms, + BB_SPEAK_CHANCE = 15, + )) src.granted_wisdom = granted_wisdom if(!granted_wisdom) src.granted_wisdom = pick(GLOB.skill_types) @@ -28,15 +32,12 @@ return ..() /datum/ai_controller/basic_controller/cow/wisdom + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.json" //don't give a targeting strategy blackboard = list( BB_BASIC_MOB_TIP_REACTING = FALSE, BB_BASIC_MOB_TIPPER = null, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/tip_reaction, - /datum/ai_planning_subtree/random_speech/cow/wisdom, + BB_BASIC_MOB_SPEAK_LINES = null, ) ///Give intense wisdom to the attacker if they're being friendly about it diff --git a/code/modules/mob/living/basic/farm_animals/deer/deer.bt.json b/code/modules/mob/living/basic/farm_animals/deer/deer.bt.json new file mode 100644 index 000000000000..a3620e19a8d3 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/deer/deer.bt.json @@ -0,0 +1,372 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/deer", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_STATIONARY_CAUSE", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/stop_and_stare" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_DEER_NEXT_REST_TIMER" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_DEER_TREEHOME" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEER_TREEHOME", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/deer_rest" + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DEER_PLAYFRIEND" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEER_PLAYFRIEND", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_DEER_PLAY_COOLDOWN", + "cooldown_duration": "10 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/deer_play" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_DEER_TREE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEER_TREE_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/deer_mark", + "vars": { + "target_key": "BB_DEER_TREE_TARGET", + "cooldown_key": "BB_DEER_MARK_COOLDOWN" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DEER_GRASS_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEER_GRASS_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/deer_graze", + "vars": { + "target_key": "BB_DEER_GRASS_TARGET", + "cooldown_key": "BB_DEER_GRAZE_COOLDOWN" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DEER_WATER_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEER_WATER_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/deer_drink", + "vars": { + "target_key": "BB_DEER_WATER_TARGET", + "cooldown_key": "BB_DEER_DRINK_COOLDOWN" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech_blackboard" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_DEER_PLAY_COOLDOWN" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.03 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_DEER_WANTS_TO_PLAY", + "value": true + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DEER_WANTS_TO_PLAY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DEER_PLAYFRIEND", + "target_source": "/datum/target_source/oview_single_type/deer_animals", + "targeting_strategy": "/datum/targeting_strategy/playable_deer" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_DEER_WANTS_TO_PLAY", + "value": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_DEER_TREEHOME" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_DEER_TREE_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_DEER_MARK_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DEER_TREE_TARGET", + "target_source": "/datum/target_source/oview_single_type/flora_tree", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_DEER_GRASS_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_DEER_GRAZE_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DEER_GRASS_TARGET", + "target_source": "/datum/target_source/range_turfs/typecache_visible/deer_grass", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_DEER_WATER_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_DEER_DRINK_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DEER_WATER_TARGET", + "target_source": "/datum/target_source/range_turfs/typecache_visible/deer_water", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_STATIONARY_CAUSE", + "target_source": "/datum/target_source/oview_typed/from_bb_key/stationary_targets", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/deer/deer_ai.dm b/code/modules/mob/living/basic/farm_animals/deer/deer_ai.dm index a654e0c29d0e..0506827d589f 100644 --- a/code/modules/mob/living/basic/farm_animals/deer/deer_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/deer/deer_ai.dm @@ -1,156 +1,95 @@ /datum/ai_controller/basic_controller/deer + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/deer/deer.bt.json" blackboard = list( BB_STATIONARY_MOVE_TO_TARGET = TRUE, BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Weeeeeeee?", "Weeee", "WEOOOOOOOOOO"), + BB_EMOTE_HEAR = list("brays."), + BB_EMOTE_SEE = list("shakes her head."), + BB_SPEAK_CHANCE = 1, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/deer, - /datum/ai_planning_subtree/stare_at_thing, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/rest_at_home, - /datum/ai_planning_subtree/play_with_friends, - /datum/ai_planning_subtree/find_and_hunt_target/mark_territory, - /datum/ai_planning_subtree/find_and_hunt_target/graze, - /datum/ai_planning_subtree/find_and_hunt_target/drink_water, - ) - - -///subtree to go around drinking water -/datum/ai_planning_subtree/find_and_hunt_target/drink_water - target_key = BB_DEER_WATER_TARGET - finding_behavior = /datum/ai_behavior/find_and_set/in_list/turf_types - hunting_behavior = /datum/ai_behavior/hunt_target/drink_water - hunt_targets = list(/turf/open/water) - hunt_range = 7 - hunt_chance = 5 - -/datum/ai_behavior/hunt_target/drink_water - always_reset_target = TRUE - hunt_cooldown = 20 SECONDS - - -/datum/ai_behavior/hunt_target/drink_water/target_caught(mob/living/hunter, atom/hunted) - var/static/list/possible_emotes = list("drinks the water!", "dances in the water!", "splashes around happily!") - hunter.manual_emote(pick(possible_emotes)) - - -///subtree to go around grazing -/datum/ai_planning_subtree/find_and_hunt_target/graze - target_key = BB_DEER_GRASS_TARGET - finding_behavior = /datum/ai_behavior/find_and_set/in_list/turf_types - hunting_behavior = /datum/ai_behavior/hunt_target/eat_grass - hunt_targets = list(/turf/open/floor/grass, /turf/open/misc/grass) - hunt_range = 7 - hunt_chance = 45 - - -/datum/ai_behavior/hunt_target/eat_grass +/// Munches grass with a happy little emote. +/datum/bt_node/ai_behavior/hunt_target/deer_graze always_reset_target = TRUE hunt_cooldown = 15 SECONDS - -/datum/ai_behavior/hunt_target/eat_grass/target_caught(mob/living/hunter, atom/hunted) +/datum/bt_node/ai_behavior/hunt_target/deer_graze/target_caught(mob/living/hunter, atom/hunted) var/static/list/possible_emotes = list("eats the grass!", "munches down the grass!", "chews on the grass!") hunter.manual_emote(pick(possible_emotes)) -///subtree to go around playing with other deers -/datum/ai_planning_subtree/play_with_friends - - -/datum/ai_planning_subtree/play_with_friends/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/static/list/emote_list = list("plays with", "dances with", "celebrates with") - var/static/list/friend_types = typecacheof(list(/mob/living/basic/deer)) - if(controller.blackboard_key_exists(BB_DEER_PLAYFRIEND)) - controller.queue_behavior(/datum/ai_behavior/emote_on_target, BB_DEER_PLAYFRIEND, emote_list) - if(SPT_PROB(3, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/find_hunt_target/valid_deer, BB_DEER_PLAYFRIEND, friend_types) - return SUBTREE_RETURN_FINISH_PLANNING - - -/datum/ai_behavior/emote_on_target/deer_play - - -/datum/ai_behavior/emote_on_target/deer_play/run_emote(mob/living/living_pawn, atom/target, list/emote_list) - . = ..() - living_pawn.spin(spintime = 4, speed = 1) - - -/datum/ai_behavior/find_hunt_target/valid_deer/valid_dinner(mob/living/source, mob/living/deer, radius, datum/ai_controller/controller, seconds_per_tick) - if(deer.stat == DEAD) - return FALSE - if(!can_see(source, deer, radius)) - return FALSE - deer.ai_controller?.set_blackboard_key(BB_DEER_PLAYFRIEND, source) - return can_see(source, deer, radius) - +/// Splashes happily in the water. +/datum/bt_node/ai_behavior/hunt_target/deer_drink + always_reset_target = TRUE + hunt_cooldown = 20 SECONDS -///subtree to mark trees as territories -/datum/ai_planning_subtree/find_and_hunt_target/mark_territory - target_key = BB_DEER_TREE_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target - hunting_behavior = /datum/ai_behavior/hunt_target/mark_territory - hunt_targets = list(/obj/structure/flora/tree) - hunt_range = 7 - hunt_chance = 75 +/datum/bt_node/ai_behavior/hunt_target/deer_drink/target_caught(mob/living/hunter, atom/hunted) + var/static/list/possible_emotes = list("drinks the water!", "dances in the water!", "splashes around happily!") + hunter.manual_emote(pick(possible_emotes)) -/datum/ai_behavior/hunt_target/mark_territory +/// Claims a tree as home territory. +/datum/bt_node/ai_behavior/hunt_target/deer_mark always_reset_target = TRUE hunt_cooldown = 15 SECONDS - -/datum/ai_behavior/hunt_target/mark_territory/target_caught(mob/living/hunter, atom/hunted) +/datum/bt_node/ai_behavior/hunt_target/deer_mark/target_caught(mob/living/hunter, atom/hunted) hunter.manual_emote("marks [hunted] with its hooves!") - hunter.ai_controller.set_blackboard_key(BB_DEER_TREEHOME, hunted) - - -/datum/ai_planning_subtree/find_and_hunt_target/mark_territory/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_DEER_TREEHOME)) //already found our home, abort! - return - return ..() - + hunter.ai_controller?.set_blackboard_key(BB_DEER_TREEHOME, hunted) -/datum/ai_planning_subtree/rest_at_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_DEER_RESTING] > world.time) //we're resting for now, nothing more to do - return SUBTREE_RETURN_FINISH_PLANNING - if(!controller.blackboard_key_exists(BB_DEER_TREEHOME) || controller.blackboard[BB_DEER_NEXT_REST_TIMER] > world.time) - return - controller.queue_behavior(/datum/ai_behavior/return_home, BB_DEER_TREEHOME) - -/datum/ai_behavior/return_home - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - ///minimum time till next rest +/// Rests by its home tree, then won't feel the need to rest again for a while. +/datum/bt_node/ai_behavior/deer_rest + /// How long we stand resting once we arrive (kept RUNNING via the cooldown). + var/rest_duration = 15 SECONDS + /// Minimum/maximum time before we want to rest again. var/minimum_time = 2 MINUTES - ///maximum time till next rest var/maximum_time = 4 MINUTES + ///ID for the rest timer + var/timerid + ///Are we napping? + var/sleeping - -/datum/ai_behavior/return_home/setup(datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/deer_rest/setup(datum/ai_controller/controller) . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - var/list/possible_turfs = get_adjacent_open_turfs(target) - shuffle_inplace(possible_turfs) - for(var/turf/possible_turf as anything in possible_turfs) - if(!possible_turf.is_blocked_turf()) - set_movement_target(controller, possible_turf) - return TRUE - return FALSE - - -/datum/ai_behavior/return_home/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + timerid = addtimer(CALLBACK(src, PROC_REF(finish_action), controller, TRUE), rest_duration, TIMER_UNIQUE | TIMER_STOPPABLE) + +/datum/bt_node/ai_behavior/deer_rest/perform(seconds_per_tick, datum/ai_controller/controller) + if(sleeping) + return AI_BEHAVIOR_DELAY var/mob/living/living_pawn = controller.pawn var/static/list/possible_emotes = list("rests its legs...", "yawns and naps...", "curls up and rests...") living_pawn.manual_emote(pick(possible_emotes)) - controller.set_blackboard_key(BB_DEER_RESTING, world.time + 15 SECONDS) + sleeping = TRUE + return AI_BEHAVIOR_DELAY + +/datum/bt_node/ai_behavior/deer_rest/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + deltimer(timerid) + timerid = null + sleeping = FALSE controller.set_blackboard_key(BB_DEER_NEXT_REST_TIMER, world.time + rand(minimum_time, maximum_time)) + +/// Plays with another deer, spinning around them. +/datum/bt_node/ai_behavior/deer_play + /// Blackboard key holding the deer we're playing with. + var/friend_key = BB_DEER_PLAYFRIEND + +/datum/bt_node/ai_behavior/deer_play/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/friend = controller.blackboard[friend_key] + if(QDELETED(friend)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/static/list/possible_emotes = list("plays with", "dances with", "celebrates with") + var/mob/living/living_pawn = controller.pawn + living_pawn.manual_emote("[pick(possible_emotes)] [friend]!") + living_pawn.spin(spintime = 4, speed = 1) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/deer_play/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(friend_key) diff --git a/code/modules/mob/living/basic/farm_animals/goat/goat.bt.json b/code/modules/mob/living/basic/farm_animals/goat/goat.bt.json new file mode 100644 index 000000000000..dcbaa93499f0 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goat/goat.bt.json @@ -0,0 +1,26 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/goat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/forage_and_retaliate" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm b/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm index af409c5763dc..2a6edae756ae 100644 --- a/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm @@ -1,23 +1,15 @@ /// Goats are normally content to sorta hang around and crunch any plant in sight, but they will go ape on someone who attacks them. /datum/ai_controller/basic_controller/goat + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/goat/goat.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("EHEHEHEHEH", "eh?"), + BB_EMOTE_HEAR = list("brays."), + BB_EMOTE_SEE = list("shakes their head.", "stamps a foot.", "glares around."), + BB_SPEAK_CHANCE = 3, + ), ) ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/capricious_retaliate, // Capricious like Capra, get it? - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/goat, - ) - -/datum/ai_planning_subtree/random_speech/goat - speech_chance = 3 - emote_hear = list("brays.") - emote_see = list("shakes their head.", "stamps a foot.", "glares around.") - speak = list("EHEHEHEHEH", "eh?") diff --git a/code/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.json b/code/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.json new file mode 100644 index 000000000000..f7fe2284a28d --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.json @@ -0,0 +1,11 @@ +{ + "dm_type": "/datum/bt_node/subtree/forage_for_goose_food", + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_FOOD", + "targeting_strategy": "/datum/targeting_strategy/goose_edible", + "target_source": "/datum/target_source/oview_items", + "vision_range": 1 + } +} diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose.bt.json b/code/modules/mob/living/basic/farm_animals/goose/goose.bt.json new file mode 100644 index 000000000000..3319b58f849b --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goose/goose.bt.json @@ -0,0 +1,38 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/goose", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/forage_and_retaliate", + "bindings": { + "q8w3rtv1": "/datum/bt_node/subtree/forage_for_goose_food" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose.dm b/code/modules/mob/living/basic/farm_animals/goose/goose.dm index aa25ca7e9d7d..935b3af656e2 100644 --- a/code/modules/mob/living/basic/farm_animals/goose/goose.dm +++ b/code/modules/mob/living/basic/farm_animals/goose/goose.dm @@ -30,8 +30,6 @@ gold_core_spawnable = HOSTILE_SPAWN /// Do we actually destroy food we eat? var/conserve_food = FALSE - /// Unfortunately, geese want to eat every item - var/static/list/item_typecache = typecacheof(/obj/item) /mob/living/basic/goose/Initialize(mapload) . = ..() @@ -42,8 +40,6 @@ RegisterSignal(src, COMSIG_MOB_PRE_EAT, PROC_REF(on_tried_gobbling)) RegisterSignal(src, COMSIG_MOB_ATE, PROC_REF(on_gobbled)) - ai_controller.set_blackboard_key(BB_BASIC_FOODS, item_typecache) - /mob/living/basic/goose/death(gibbed) if (!gibbed && length(contents)) var/turf/drop_turf = drop_location() @@ -157,8 +153,7 @@ remove_status_effect(/datum/status_effect/goose_choking) // We're going to cough it out /mob/living/basic/goose/vomit/proc/stop_deadchat_plays() - var/initial_behaviour = initial(ai_controller?.idle_behavior) - ai_controller?.idle_behavior = SSidle_ai_behaviors.idle_behaviors[initial_behaviour] + ai_controller?.clear_blackboard_key(BB_DISABLE_IDLE) /mob/living/basic/goose/vomit/deadchat_plays(mode = ANARCHY_MODE, cooldown = 12 SECONDS) var/list/goose_inputs = list( @@ -172,6 +167,6 @@ return // Stop automated movement, retain the other behaviour so you can lead the horse to plastic and have it drink. - ai_controller?.idle_behavior = null + ai_controller?.set_blackboard_key(BB_DISABLE_IDLE, TRUE) #undef GOOSE_SATIATED diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm b/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm index 94202a90df32..43412f191643 100644 --- a/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm @@ -1,83 +1,38 @@ /// Geese like to eat random objects and kill themselves, and occasionally get pissed off for no reason /datum/ai_controller/basic_controller/goose + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/goose/goose.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_SEARCH_RANGE = 1, BB_EAT_FOOD_COOLDOWN = 10 SECONDS, - BB_EAT_EMOTES = list() + BB_EAT_EMOTES = list(), + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Honk!"), + BB_EMOTE_HEAR = list("honks.", "honks loudly.", "honks aggressively."), + BB_EMOTE_SEE = list("flaps.", "preens.", "glares around."), + BB_SPEAK_CHANCE = 3, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/goose - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/goose, - /datum/ai_planning_subtree/capricious_retaliate, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/find_food/goose, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Goose who doesn't randomly retaliate but does still try to die by eating random items /datum/ai_controller/basic_controller/goose/calm + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_SEARCH_RANGE = 1, BB_EAT_FOOD_COOLDOWN = 0.5 SECONDS, // Uh oh - BB_EAT_EMOTES = list() + BB_EAT_EMOTES = list(), + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Honk!"), + BB_EMOTE_HEAR = list("honks.", "honks loudly.", "honks aggressively."), + BB_EMOTE_SEE = list("flaps.", "preens.", "glares around."), + BB_SPEAK_CHANCE = 3, + ), ) - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/goose, - /datum/ai_planning_subtree/use_mob_ability/goose_vomit, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/find_food/goose, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/// Walk more if we're choking or vomiting -/datum/idle_behavior/idle_random_walk/goose - -/datum/idle_behavior/idle_random_walk/goose/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - walk_chance = controller.blackboard[BB_GOOSE_PANICKED] ? 100 : 25 // I think this sets it for every goose but that's fine because it'll reset it before using it - return ..() - -/// Only look for things geese will try to eat -/datum/ai_planning_subtree/find_food/goose - finding_behavior = /datum/ai_behavior/find_and_set/in_list/goose_food - -/datum/ai_planning_subtree/find_food/goose/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (controller.blackboard[BB_GOOSE_PANICKED]) - return // Don't look for food while choking or vomiting - return ..() - -/// Only set things geese will try to eat -/datum/ai_behavior/find_and_set/in_list/goose_food - -/datum/ai_behavior/find_and_set/in_list/goose_food/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) - if(!length(found)) - return - - var/list/filtered = list() - for (var/obj/item/thing as anything in found) - if (IS_EDIBLE(thing) || thing.has_material_type(/datum/material/plastic)) - filtered += thing - - if(length(filtered)) - return pick(filtered) - -/// Use this ability only if we roll a dice correctly -/datum/ai_planning_subtree/use_mob_ability/goose_vomit - -/datum/ai_planning_subtree/use_mob_ability/goose_vomit/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/trigger_prob = controller.blackboard[BB_GOOSE_VOMIT_CHANCE] || 0 - if (prob(trigger_prob)) - return ..() -/datum/ai_planning_subtree/random_speech/goose - speech_chance = 3 - emote_hear = list("honks.", "honks loudly.", "honks aggressively.") - emote_see = list("flaps.", "preens.", "glares around.") - speak = list("Honk!") +/// Geese are picky: they only forage for edible items and plastic, and only when it's right next to them. +/datum/bt_node/subtree/forage_for_goose_food + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.json" diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.json b/code/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.json new file mode 100644 index 000000000000..c6069c547145 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.json @@ -0,0 +1,30 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/goose/calm", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/forage_and_retaliate", + "bindings": { + "q8w3rtv1": "/datum/bt_node/subtree/forage_for_goose_food", + "z4n9bk7p": "/datum/bt_node/subtree/pick_retaliate_target" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.json b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.json new file mode 100644 index 000000000000..f49bf37a5daf --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.json @@ -0,0 +1,130 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/gorilla", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "can_attack_turfs": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance_from_key", + "vars": { + "chance_key": "BB_EMOTE_CHANCE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/run_emote", + "vars": { + "emote_key": "BB_EMOTE_KEY" + } + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm index ba9584f4bc19..98b6f6da9051 100644 --- a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm @@ -1,5 +1,6 @@ /// Pretty basic, just click people to death. Also hunt and eat bananas. /datum/ai_controller/basic_controller/gorilla + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = UNCONSCIOUS, @@ -9,22 +10,6 @@ ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/run_emote, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/gorilla, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - ) - -/datum/ai_planning_subtree/attack_obstacle_in_path/gorilla - attack_behaviour = /datum/ai_behavior/attack_obstructions/gorilla - -/datum/ai_behavior/attack_obstructions/gorilla - can_attack_turfs = TRUE /datum/ai_controller/basic_controller/gorilla/lesser blackboard = list( diff --git a/code/modules/mob/living/basic/farm_animals/pig.bt.json b/code/modules/mob/living/basic/farm_animals/pig.bt.json new file mode 100644 index 000000000000..5cb9d774e7be --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/pig.bt.json @@ -0,0 +1,26 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/pig", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_brawler_combat" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/pig.dm b/code/modules/mob/living/basic/farm_animals/pig.dm index 39008cab8674..6ba56fe5a19a 100644 --- a/code/modules/mob/living/basic/farm_animals/pig.dm +++ b/code/modules/mob/living/basic/farm_animals/pig.dm @@ -59,18 +59,17 @@ visible_message(span_notice("[src] snorts respectfully.")) /datum/ai_controller/basic_controller/pig + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/pig.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("oink?", "oink", "snurf"), + BB_EMOTE_HEAR = list("snorts."), + BB_EMOTE_SEE = list("sniffs around."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/pig/pig1.ogg', 'sound/mobs/non-humanoids/pig/pig2.ogg'), + BB_SPEAK_CHANCE = 3, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/pig, - ) diff --git a/code/modules/mob/living/basic/farm_animals/pony.bt.json b/code/modules/mob/living/basic/farm_animals/pony.bt.json new file mode 100644 index 000000000000..88a87c3274e3 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/pony.bt.json @@ -0,0 +1,43 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/pony", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_TAMED", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_skittish_combat" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_brawler_combat" + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/pony.dm b/code/modules/mob/living/basic/farm_animals/pony.dm index 3e9ad63ad399..ad34e95fabb3 100644 --- a/code/modules/mob/living/basic/farm_animals/pony.dm +++ b/code/modules/mob/living/basic/farm_animals/pony.dm @@ -65,12 +65,13 @@ AddElement(/datum/element/ridable, /datum/component/riding/creature/pony) visible_message(span_notice("[src] snorts happily.")) new /obj/effect/temp_visual/heart(loc) - - ai_controller.replace_planning_subtrees(list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/random_speech/pony/tamed, - )) + var/static/list/tamed_emotes = list( + BB_EMOTE_HEAR = list("snorts."), + BB_EMOTE_SEE = list("snorts."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/pony/snort.ogg'), + BB_SPEAK_CHANCE = 3, + ) + ai_controller.override_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, tamed_emotes) if(unique_tamer) my_owner = WEAKREF(tamer) @@ -124,21 +125,19 @@ . |= SHOVE_CAN_STAGGER /datum/ai_controller/basic_controller/pony + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/pony.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("whinnies!"), + BB_EMOTE_SEE = list("horses around."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/pony/whinny01.ogg', 'sound/mobs/non-humanoids/pony/whinny02.ogg', 'sound/mobs/non-humanoids/pony/whinny03.ogg'), + BB_SPEAK_CHANCE = 3, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/pony, - ) // A stronger horse is required for our strongest cowboys. /mob/living/basic/pony/syndicate diff --git a/code/modules/mob/living/basic/farm_animals/rabbit.bt.json b/code/modules/mob/living/basic/farm_animals/rabbit.bt.json new file mode 100644 index 000000000000..5414e42ca1bb --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/rabbit.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/rabbit", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_and_speak" +} diff --git a/code/modules/mob/living/basic/farm_animals/rabbit.dm b/code/modules/mob/living/basic/farm_animals/rabbit.dm index 42c3f0832222..c9433bbe923c 100644 --- a/code/modules/mob/living/basic/farm_animals/rabbit.dm +++ b/code/modules/mob/living/basic/farm_animals/rabbit.dm @@ -59,17 +59,18 @@ name = "bunny" /datum/ai_controller/basic_controller/rabbit + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/rabbit.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Mrrp.", "CHIRP!", "Mrrp?"), + BB_EMOTE_HEAR = list("hops."), + BB_EMOTE_SEE = list("hops around.", "bounces up and down."), + BB_SPEAK_CHANCE = 10, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/rabbit, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - ) /// The easter subtype of rabbits, will lay eggs and say Eastery catchphrases. @@ -100,11 +101,15 @@ ) /datum/ai_controller/basic_controller/rabbit/easter - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/rabbit/easter, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - ) + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Hop into Easter!", "Come get your eggs!", "Prizes for everyone!"), + BB_EMOTE_HEAR = list("hops."), + BB_EMOTE_SEE = list("hops around.", "bounces up and down."), + BB_SPEAK_CHANCE = 10, + ), + ) /// Same deal as the standard easter subtype, but these ones are able to brave the cold of space with their handy gas mask. @@ -120,8 +125,12 @@ unsuitable_cold_damage = 0 // Zero because we are meant to survive in space. /datum/ai_controller/basic_controller/rabbit/easter/space - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/rabbit/easter/space, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - ) + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Hmph mmph mmmph!", "Mmphe mmphe mmphe!", "Hmm mmm mmm!"), + BB_EMOTE_HEAR = list("hops."), + BB_EMOTE_SEE = list("hops around.", "bounces up and down."), + BB_SPEAK_CHANCE = 10, + ), + ) diff --git a/code/modules/mob/living/basic/farm_animals/sheep.bt.json b/code/modules/mob/living/basic/farm_animals/sheep.bt.json new file mode 100644 index 000000000000..d1d6c6a99733 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/sheep.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/sheep", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_and_speak" +} diff --git a/code/modules/mob/living/basic/farm_animals/sheep.dm b/code/modules/mob/living/basic/farm_animals/sheep.dm index bbbf7e588753..3b02f1517068 100644 --- a/code/modules/mob/living/basic/farm_animals/sheep.dm +++ b/code/modules/mob/living/basic/farm_animals/sheep.dm @@ -80,14 +80,16 @@ update_appearance(UPDATE_ICON) /datum/ai_controller/basic_controller/sheep + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/sheep.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("baaa", "baaaAAAAAH!", "baaah"), + BB_EMOTE_HEAR = list("bleats."), + BB_EMOTE_SEE = list("shakes her head.", "stares into the distance."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/sheep/sheep1.ogg', 'sound/mobs/non-humanoids/sheep/sheep2.ogg', 'sound/mobs/non-humanoids/sheep/sheep3.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/sheep, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - ) diff --git a/code/modules/mob/living/basic/festivus_pole.bt.json b/code/modules/mob/living/basic/festivus_pole.bt.json new file mode 100644 index 000000000000..c3d84d6af3f2 --- /dev/null +++ b/code/modules/mob/living/basic/festivus_pole.bt.json @@ -0,0 +1,122 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/festivus_pole", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "ability_key": "BB_FESTIVE_APC" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": "10" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/apc", + "targeting_strategy": "/datum/targeting_strategy/chargeable_apc", + "vision_range": 6 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/festivus_pole.dm b/code/modules/mob/living/basic/festivus_pole.dm index 19a3cd2bf583..593f0e34fb7d 100644 --- a/code/modules/mob/living/basic/festivus_pole.dm +++ b/code/modules/mob/living/basic/festivus_pole.dm @@ -51,19 +51,13 @@ grant_actions_by_list(list(/datum/action/cooldown/mob_cooldown/charge_apc = BB_FESTIVE_APC)) /datum/ai_controller/basic_controller/festivus_pole + behavior_tree_json = "code/modules/mob/living/basic/festivus_pole.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_LOW_PRIORITY_HUNTING_TARGET = null, // APCs ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/find_and_hunt_target/look_for_apcs, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /mob/living/basic/festivus/attack_hand(mob/living/carbon/human/user, list/modifiers) . = ..() @@ -84,41 +78,4 @@ if(apc_target.cell) apc_target.cell.give(FESTIVUS_RECHARGE_VALUE) -/datum/ai_planning_subtree/find_and_hunt_target/look_for_apcs - hunting_behavior = /datum/ai_behavior/hunt_target/apcs - hunt_targets = list(/obj/machinery/power/apc) - hunt_range = 6 - - -/datum/ai_planning_subtree/find_and_hunt_target/look_for_apcs - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/apcs - hunting_behavior = /datum/ai_behavior/hunt_target/apcs - hunt_targets = list(/obj/machinery/power/apc) - hunt_range = 6 - -/datum/ai_behavior/hunt_target/apcs - hunt_cooldown = 15 SECONDS - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/apcs/target_caught(mob/living/basic/hunter, obj/machinery/power/apc/hunted) - var/datum/action/cooldown/mob_cooldown/charge_ability = hunter.ai_controller.blackboard[BB_FESTIVE_APC] - if(isnull(charge_ability)) - return - charge_ability.Activate(hunted) - - -/datum/ai_behavior/find_hunt_target/apcs - -/datum/ai_behavior/find_hunt_target/apcs/valid_dinner(mob/living/source, obj/machinery/power/apc/dinner, radius) - if(istype(dinner, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/apc_target = dinner - if(!apc_target.cell) - return FALSE - var/obj/item/stock_parts/power_store/cell/apc_cell = apc_target.cell - if(apc_cell.charge == apc_cell.maxcharge) //if its full charge we no longer feed it - return FALSE - - return can_see(source, dinner, radius) - #undef FESTIVUS_RECHARGE_VALUE diff --git a/code/modules/mob/living/basic/heretic/flesh_stalker.dm b/code/modules/mob/living/basic/heretic/flesh_stalker.dm index 5cee158463ac..c7c9eb77c54f 100644 --- a/code/modules/mob/living/basic/heretic/flesh_stalker.dm +++ b/code/modules/mob/living/basic/heretic/flesh_stalker.dm @@ -25,18 +25,10 @@ /// Changes shape and lies in wait when it has no target, uses EMP and attacks once it does /datum/ai_controller/basic_controller/stalker + behavior_tree_json = "code/modules/mob/living/basic/heretic/stalker.bt.json" ai_traits = CAN_ACT_IN_STASIS blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/shapechange_ambush, - /datum/ai_planning_subtree/use_mob_ability, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/heretic/raw_prophet.bt.json b/code/modules/mob/living/basic/heretic/raw_prophet.bt.json new file mode 100644 index 000000000000..66da01c6cf66 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/raw_prophet.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/raw_prophet", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_ranged_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/heretic/raw_prophet.dm b/code/modules/mob/living/basic/heretic/raw_prophet.dm index 8c5da925c064..9773fdc5d6cd 100644 --- a/code/modules/mob/living/basic/heretic/raw_prophet.dm +++ b/code/modules/mob/living/basic/heretic/raw_prophet.dm @@ -85,16 +85,9 @@ /// Walk and attack people, blind them when we can /datum/ai_controller/basic_controller/raw_prophet + behavior_tree_json = "code/modules/mob/living/basic/heretic/raw_prophet.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/heretic/rust_walker.bt.json b/code/modules/mob/living/basic/heretic/rust_walker.bt.json new file mode 100644 index 000000000000..4f69b8a51d9c --- /dev/null +++ b/code/modules/mob/living/basic/heretic/rust_walker.bt.json @@ -0,0 +1,126 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/rust_walker", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_turf_has_trait", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "trait": "TRAIT_RUSTY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/rust" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/heretic/rust_walker.dm b/code/modules/mob/living/basic/heretic/rust_walker.dm index a2cdfc432505..24e826cfdc51 100644 --- a/code/modules/mob/living/basic/heretic/rust_walker.dm +++ b/code/modules/mob/living/basic/heretic/rust_walker.dm @@ -54,39 +54,9 @@ /// Converts unconverted terrain, sprays pocket sand around /datum/ai_controller/basic_controller/rust_walker + behavior_tree_json = "code/modules/mob/living/basic/heretic/rust_walker.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/rust - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/use_mob_ability/rust_walker, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/// Moves a lot if healthy and on rust (to find more tiles to rust) or unhealthy and not on rust (to find healing rust) -/// Still moving in random directions though we're not really seeking it out -/datum/idle_behavior/idle_random_walk/rust - -/datum/idle_behavior/idle_random_walk/rust/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/our_mob = controller.pawn - var/turf/our_turf = get_turf(our_mob) - if (HAS_TRAIT(our_turf, TRAIT_RUSTY)) - walk_chance = (our_mob.health < our_mob.maxHealth) ? 10 : 50 - else - walk_chance = (our_mob.health < our_mob.maxHealth) ? 50 : 10 - return ..() - -/// Use if we're not stood on rust right now -/datum/ai_planning_subtree/use_mob_ability/rust_walker - -/datum/ai_planning_subtree/use_mob_ability/rust_walker/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/turf/our_turf = get_turf(controller.pawn) - if (HAS_TRAIT(our_turf, TRAIT_RUSTY)) - return - return ..() diff --git a/code/modules/mob/living/basic/heretic/stalker.bt.json b/code/modules/mob/living/basic/heretic/stalker.bt.json new file mode 100644 index 000000000000..aabce4504221 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/stalker.bt.json @@ -0,0 +1,147 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/stalker", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_inside_mob", + "vars": { + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": true, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability/shapeshift" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_inside_mob", + "vars": { + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": false, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_at_least", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_BASIC_MOB_HAS_TARGET_TIME", + "minimum": "8 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability/shapeshift" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/heretic/star_gazer.bt.json b/code/modules/mob/living/basic/heretic/star_gazer.bt.json new file mode 100644 index 000000000000..80167946253b --- /dev/null +++ b/code/modules/mob/living/basic/heretic/star_gazer.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/star_gazer", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/heretic/star_gazer.dm b/code/modules/mob/living/basic/heretic/star_gazer.dm index 6ea05af3665f..e230ec1ba5a1 100644 --- a/code/modules/mob/living/basic/heretic/star_gazer.dm +++ b/code/modules/mob/living/basic/heretic/star_gazer.dm @@ -131,7 +131,7 @@ target.apply_damage(damage = 5, damagetype = BURN) var/datum/targeting_strategy/target_confirmer = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY]) for(var/mob/living/nearby_mob in range(1, src)) - if(target == nearby_mob || !target_confirmer?.can_attack(src, nearby_mob)) + if(target == nearby_mob || !target_confirmer?.is_valid_target(src, nearby_mob)) continue nearby_mob.apply_status_effect(/datum/status_effect/star_mark) nearby_mob.apply_damage(10) @@ -402,30 +402,10 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/attack_obstacle_in_path/pet_target/star_gazer, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/star_gazer, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/attack_obstacle_in_path/star_gazer - attack_behaviour = /datum/ai_behavior/attack_obstructions/star_gazer - -/datum/ai_planning_subtree/attack_obstacle_in_path/pet_target/star_gazer - attack_behaviour = /datum/ai_behavior/attack_obstructions/star_gazer - -/datum/ai_behavior/attack_obstructions/star_gazer - action_cooldown = 0.4 SECONDS - can_attack_turfs = TRUE - can_attack_dense_objects = TRUE + behavior_tree_json = "code/modules/mob/living/basic/heretic/star_gazer.bt.json" /datum/pet_command/attack/star_gazer speech_commands = list("attack", "sic", "kill", "slash them") command_feedback = "stares!" pointed_reaction = "stares intensely!" refuse_reaction = "..." - attack_behaviour = /datum/ai_behavior/basic_melee_attack diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.json b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.json new file mode 100644 index 000000000000..fb3ed54b6eb3 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.json @@ -0,0 +1,54 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/ice_demon", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/ice_demon_flee_from_fire" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/ice_demon_combat" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.json b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.json new file mode 100644 index 000000000000..7ab3b485eb52 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.json @@ -0,0 +1,18 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/ice_demon/afterimage", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/ice_demon_flee_from_fire" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm index 642061ed782d..155896915cc8 100644 --- a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/ice_demon + behavior_tree_json = "code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_RANGED_SKIRMISH_MAX_DISTANCE = 7, @@ -9,104 +10,14 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/flee_target/ice_demon, - /datum/ai_planning_subtree/ranged_skirmish/ice_demon, - /datum/ai_planning_subtree/maintain_distance/cover_minimum_distance, - /datum/ai_planning_subtree/teleport_away_from_target, - /datum/ai_planning_subtree/find_and_hunt_target/teleport_destination, - /datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages, - ) - -/datum/ai_planning_subtree/teleport_away_from_target - ability_key = BB_DEMON_TELEPORT_ABILITY - -/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination - target_key = BB_TELEPORT_DESTINATION - hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport - finding_behavior = /datum/ai_behavior/find_valid_teleport_location - hunt_targets = list(/turf/open) - hunt_range = 3 - finish_planning = FALSE - -/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - if(controller.blackboard_key_exists(BB_ESCAPE_DESTINATION)) - controller.clear_blackboard_key(BB_TELEPORT_DESTINATION) - return - var/datum/action/cooldown/ability = controller.blackboard[BB_DEMON_TELEPORT_ABILITY] - if(!ability?.IsAvailable()) - return - return ..() - -/datum/ai_behavior/find_valid_teleport_location - -/datum/ai_behavior/find_valid_teleport_location/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - var/list/possible_turfs = list() - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - for(var/turf/open/potential_turf in oview(hunt_range, target)) //we check for turfs around the target - if(potential_turf.is_blocked_turf()) - continue - if(!can_see(target, potential_turf, hunt_range)) - continue - possible_turfs += potential_turf - - if(!length(possible_turfs)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(hunting_target_key, pick(possible_turfs)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport - hunt_cooldown = 2 SECONDS - ability_key = BB_DEMON_TELEPORT_ABILITY - behavior_flags = NONE - -/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages - ability_key = BB_DEMON_CLONE_ABILITY - -/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.health / living_pawn.maxHealth > 0.5) //only use this ability when under half health - return - return ..() - -/datum/ai_planning_subtree/flee_target/ice_demon - -/datum/ai_planning_subtree/flee_target/ice_demon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) - return - if(!iscarbon(target)) - return - var/mob/living/carbon/human_target = target - - for(var/obj/held_item in human_target.held_items) - if(!is_type_in_list(held_item, controller.blackboard[BB_LIST_SCARY_ITEMS])) - continue - if(!held_item.light_on) - continue - var/datum/action/cooldown/slip_ability = controller.blackboard[BB_DEMON_SLIP_ABILITY] - if(slip_ability?.IsAvailable()) - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_DEMON_SLIP_ABILITY) - return ..() +/datum/ai_controller/basic_controller/ice_demon/afterimage + behavior_tree_json = "code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.json" -/datum/ai_planning_subtree/ranged_skirmish/ice_demon - min_range = 0 +/// Flees from a target holding a lit scary item, slipping them on the way out. +/datum/bt_node/subtree/ice_demon_flee_from_fire + behavior_tree_json = "code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.json" -/datum/ai_controller/basic_controller/ice_demon/afterimage - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/flee_target/ice_demon, //even the afterimages are afraid of flames! - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) +/datum/bt_node/subtree/ice_demon_combat + behavior_tree_json = "code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.json" diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.json b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.json new file mode 100644 index 000000000000..9a5556b1ece4 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.json @@ -0,0 +1,127 @@ +{ + "dm_type": "/datum/bt_node/subtree/ice_demon_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 1 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_DEMON_TELEPORT_ABILITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_furthest_turf_from_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "set_key": "BB_ESCAPE_DESTINATION", + "range": 7 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_DEMON_TELEPORT_ABILITY", + "target_key": "BB_ESCAPE_DESTINATION" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_health_below", + "vars": { + "health_threshold": 75 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_DEMON_CLONE_ABILITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_DEMON_CLONE_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_DEMON_TELEPORT_ABILITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_valid_teleport_location", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "set_key": "BB_TELEPORT_DESTINATION", + "range": 3 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_DEMON_TELEPORT_ABILITY", + "target_key": "BB_TELEPORT_DESTINATION" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "max_range": 9 + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.json b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.json new file mode 100644 index 000000000000..ee77b66b8cb2 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.json @@ -0,0 +1,37 @@ +{ + "dm_type": "/datum/bt_node/subtree/ice_demon_flee_from_fire", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_holding_lit_item", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET", + "item_types_key": "BB_LIST_SCARY_ITEMS" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_DEMON_SLIP_ABILITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_DEMON_SLIP_ABILITY" + } + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.json b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.json new file mode 100644 index 000000000000..d8de7a1f0a6b --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.json @@ -0,0 +1,284 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/ice_whelp", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WHELP_WIDESPREAD_FIRE", + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 3 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WHELP_STRAIGHTLINE_FIRE", + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 7 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TARGET_CANNIBAL" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_CANNIBAL", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_TARGET_CANNIBAL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TARGET_CANNIBAL" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TARGET_ROCK" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_ROCK", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_TARGET_ROCK", + "combat_mode": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_WHELP_SCULPT_COOLDOWN", + "cooldown_duration": "5 MINUTES" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TARGET_ROCK" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TARGET_TREE" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_TREE", + "required_dist": 2 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_WHELP_STRAIGHTLINE_FIRE", + "target_key": "BB_TARGET_TREE", + "maximum_distance": 2 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_WHELP_BURN_COOLDOWN", + "cooldown_duration": "2 MINUTES" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_CANNIBAL", + "target_source": "/datum/target_source/oview_single_type/ice_whelp", + "targeting_strategy": "/datum/targeting_strategy/dead_mob/not_pulled", + "vision_range": 10 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_WHELP_SCULPT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_ROCK", + "target_source": "/datum/target_source/oview_single_type/icy_rock", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "d" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_WHELP_STRAIGHTLINE_FIRE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_TREE", + "target_source": "/datum/target_source/oview_single_type/flora_tree", + "targeting_strategy": "/datum/targeting_strategy/non_stump_tree", + "vision_range": 9 + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm index 7d4c74747a33..66e99d25bd2d 100644 --- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm @@ -47,7 +47,6 @@ AddElement(/datum/element/footstep, FOOTSTEP_MOB_HEAVY) grant_actions_by_list(innate_actions) - ai_controller.set_blackboard_key(BB_TARGETED_ACTION, ai_controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE]) /mob/living/basic/mining/ice_whelp/early_melee_attack(atom/target, list/modifiers, ignore_cooldown) . = ..() diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm index 34a4b3631363..c84ad2f62222 100644 --- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm @@ -5,154 +5,4 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/ice_whelp, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/ice_whelp, - /datum/ai_planning_subtree/sculpt_statues, - /datum/ai_planning_subtree/find_and_hunt_target/corpses/ice_whelp, - /datum/ai_planning_subtree/burn_trees, - ) - -/// Cancel melee attacks when we have our breath weapon -/datum/ai_planning_subtree/basic_melee_attack_subtree/ice_whelp - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/ice_whelp - -/// Cancel melee attacks when we have our breath weapon -/datum/ai_behavior/basic_melee_attack/ice_whelp - -/datum/ai_behavior/basic_melee_attack/ice_whelp/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - var/datum/action/cooldown/breath_weapon = controller.blackboard[BB_TARGETED_ACTION] - if (breath_weapon?.IsAvailable()) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - return ..() - -/// Find other tasty dragons -/datum/ai_planning_subtree/find_and_hunt_target/corpses/ice_whelp - target_key = BB_TARGET_CANNIBAL - finding_behavior = /datum/ai_behavior/find_hunt_target/corpses/dragon_corpse - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/dragon_cannibalise - hunt_targets = list(/mob/living/basic/mining/ice_whelp) - hunt_range = 10 - -/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse - -/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse/valid_dinner(mob/living/source, mob/living/dinner, radius) - if(dinner.pulledby) //someone already got him before us - return FALSE - return ..() - -/// Eat other dragons -/datum/ai_behavior/hunt_target/interact_with_target/dragon_cannibalise - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/hunt_target/interact_with_target/dragon_cannibalise/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key) - var/mob/living/target = controller.blackboard[target_key] - if(QDELETED(target) || target.stat != DEAD || target.pulledby) //we were too slow - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - return ..() - -/datum/ai_behavior/cannibalize/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -///subtree to find icy rocks and create sculptures out of them -/datum/ai_planning_subtree/sculpt_statues - -/datum/ai_planning_subtree/sculpt_statues/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_TARGET_ROCK)) - controller.queue_behavior(/datum/ai_behavior/sculpt_statue, BB_TARGET_ROCK) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_TARGET_ROCK, /obj/structure/flora/rock/icy) - -/datum/ai_behavior/sculpt_statue - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = 5 MINUTES - -/datum/ai_behavior/sculpt_statue/setup(datum/ai_controller/controller, target_key) - . = ..() - var/obj/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/sculpt_statue/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - if(!controller.ai_interact(target = target_key, combat_mode = FALSE)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/sculpt_statue/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/// Only use ability if we are within range -/datum/ai_planning_subtree/targeted_mob_ability/ice_whelp - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/ice_whelp - -/datum/ai_planning_subtree/targeted_mob_ability/ice_whelp/additional_ability_checks(datum/ai_controller/controller, datum/action/cooldown/using_action) - var/atom/target = controller.blackboard[target_key] - var/range_to_target = get_dist(controller.pawn, target) - return range_to_target < /datum/action/cooldown/mob_cooldown/fire_breath/ice::fire_range - -/// Select appropriate ability based on range -/datum/ai_behavior/targeted_mob_ability/ice_whelp - -/datum/ai_behavior/targeted_mob_ability/ice_whelp/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) - var/mob/living/target = controller.blackboard[target_key] - if (isnull(target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/dist_to_target = get_dist(controller.pawn, target) - var/datum/action/cooldown/mob_cooldown/fire_breath/ice/short_range_ability = controller.blackboard[BB_WHELP_WIDESPREAD_FIRE] - if (isnull(short_range_ability) || dist_to_target > short_range_ability.fire_range) - controller.set_blackboard_key(BB_TARGETED_ACTION, controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE]) - return ..() - - controller.set_blackboard_key(BB_TARGETED_ACTION, controller.blackboard[BB_WHELP_WIDESPREAD_FIRE]) - return ..() - -///subtree to look for trees and burn them with our flamethrower -/datum/ai_planning_subtree/burn_trees - -/datum/ai_planning_subtree/burn_trees/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/datum/action/cooldown/using_action = controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE] - if (!using_action?.IsAvailable()) - return - - if(controller.blackboard_key_exists(BB_TARGET_TREE)) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees, BB_WHELP_STRAIGHTLINE_FIRE, BB_TARGET_TREE) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/set_target_tree, BB_TARGET_TREE) - -/datum/ai_behavior/set_target_tree - -/datum/ai_behavior/set_target_tree/perform(seconds_per_tick, datum/ai_controller/controller, tree_key) - var/mob/living_pawn = controller.pawn - var/list/possible_trees = list() - - for(var/obj/structure/flora/tree/possible_tree in oview(9, living_pawn)) - if(istype(possible_tree, /obj/structure/flora/tree/stump)) //no leaves to burn - continue - possible_trees += possible_tree - - if(!length(possible_trees)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(tree_key, pick(possible_trees)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 2 - action_cooldown = 2 MINUTES - -/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees/setup(datum/ai_controller/controller, ability_key, target_key) - . = ..() - var/obj/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) + behavior_tree_json = "code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.json" diff --git a/code/modules/mob/living/basic/icemoon/polar_bear/polar.bt.json b/code/modules/mob/living/basic/icemoon/polar_bear/polar.bt.json new file mode 100644 index 000000000000..25907280dd25 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/polar_bear/polar.bt.json @@ -0,0 +1,40 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/polar", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat", + "bindings": { + "bbrbyj7y": "/datum/bt_node/ai_behavior/random_speech/bear" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enrage" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/polar_bear/polar_bear.dm b/code/modules/mob/living/basic/icemoon/polar_bear/polar_bear.dm index 5d838db8fe8a..a13bdc5faec6 100644 --- a/code/modules/mob/living/basic/icemoon/polar_bear/polar_bear.dm +++ b/code/modules/mob/living/basic/icemoon/polar_bear/polar_bear.dm @@ -53,6 +53,7 @@ faction = list(FACTION_NEUTRAL) /datum/ai_controller/basic_controller/polar + behavior_tree_json = "code/modules/mob/living/basic/icemoon/polar_bear/polar.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, @@ -60,12 +61,3 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/enrage, - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/bear, - ) diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf.bt.json b/code/modules/mob/living/basic/icemoon/wolf/wolf.bt.json new file mode 100644 index 000000000000..930b82e9049a --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/wolf/wolf.bt.json @@ -0,0 +1,120 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/wolf", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm b/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm index 28618bfebcaf..a30270070c97 100644 --- a/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm +++ b/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm @@ -16,27 +16,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk + behavior_tree_json = "code/modules/mob/living/basic/icemoon/wolf/wolf.bt.json" - //reinforcements needs to be skipped over entirely on tamed wolves because it causes them to attack their owner and then themselves - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/call_reinforcements/wolf, - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/call_reinforcements/wolf - -/datum/ai_planning_subtree/call_reinforcements/wolf/decide_to_call(datum/ai_controller/controller) - //only call reinforcements if the person who just smacked us isn't a friend to avoid hitting them once, then killing ourselves if we've been tamed - if (controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) && istype(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET], /mob)) - return !(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] in controller.blackboard[BB_FRIENDS_LIST]) - else - return FALSE diff --git a/code/modules/mob/living/basic/illusion/escape.bt.json b/code/modules/mob/living/basic/illusion/escape.bt.json new file mode 100644 index 000000000000..5fbf7dc4fb3e --- /dev/null +++ b/code/modules/mob/living/basic/illusion/escape.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/illusion/escape", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pick_retaliate_target" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/illusion/illlusion_ai.dm b/code/modules/mob/living/basic/illusion/illlusion_ai.dm index 48270fc25838..60580df19f0b 100644 --- a/code/modules/mob/living/basic/illusion/illlusion_ai.dm +++ b/code/modules/mob/living/basic/illusion/illlusion_ai.dm @@ -2,42 +2,20 @@ /// For the time being however, the AI is very simple and doesn't rely on any advanced tactics. Just go to thing it was assigned to attack and attack it (if assigned, else wander around) /// However, the action we undergo is based on the subtype of illusion we are and that's done on the mob subtype level. /datum/ai_controller/basic_controller/illusion + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = DEFAULT_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Escape subtype of illusions are made to flee from threats rather than attack them. They do not undergo any retaliation behavior. /// We also want to account for the possibility of new threats attacking us and fleeing from those too, more randomness is ideal. /datum/ai_controller/basic_controller/illusion/escape - blackboard = list( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, // we don't need the special illusion one here - BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - ) - - ai_traits = DEFAULT_AI_FLAGS - ai_movement = /datum/ai_movement/basic_avoidance - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - ) + behavior_tree_json = "code/modules/mob/living/basic/illusion/escape.bt.json" /// Retaliate subtypes of escape illusions can fight back against threats that attack them, making them more dangerous. /datum/ai_controller/basic_controller/illusion/escape/retaliate - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/illusion/retaliate.bt.json" diff --git a/code/modules/mob/living/basic/illusion/illusion.dm b/code/modules/mob/living/basic/illusion/illusion.dm index 97a61e653f0c..3ae32ca3a483 100644 --- a/code/modules/mob/living/basic/illusion/illusion.dm +++ b/code/modules/mob/living/basic/illusion/illusion.dm @@ -26,7 +26,7 @@ /// Prob of getting a clone on attack var/multiply_chance = 0 /// The blackboard key we want to set for our target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET + var/target_key = BB_CURRENT_TARGET /mob/living/basic/illusion/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/basic/illusion/retaliate.bt.json b/code/modules/mob/living/basic/illusion/retaliate.bt.json new file mode 100644 index 000000000000..a9e61267291d --- /dev/null +++ b/code/modules/mob/living/basic/illusion/retaliate.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/illusion/escape/retaliate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pick_retaliate_target" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/jungle/human_trap.bt.json b/code/modules/mob/living/basic/jungle/human_trap.bt.json new file mode 100644 index 000000000000..ac0c6499b8c4 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/human_trap.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/human_trap", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_ranged_combat" +} diff --git a/code/modules/mob/living/basic/jungle/leaper/leaper.bt.json b/code/modules/mob/living/basic/jungle/leaper/leaper.bt.json new file mode 100644 index 000000000000..2dda6824c590 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/leaper/leaper.bt.json @@ -0,0 +1,122 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/leaper", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_LEAPER_BUBBLE", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_LEAPER_FLOP", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_LEAPER_VOLLEY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_LEAPER_SUMMON", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm b/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm index 1e7a6dee29ee..27c517ff4825 100644 --- a/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm +++ b/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm @@ -5,40 +5,7 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/targeted_mob_ability/pointed_bubble, - /datum/ai_planning_subtree/targeted_mob_ability/flop, - /datum/ai_planning_subtree/targeted_mob_ability/volley, - /datum/ai_planning_subtree/targeted_mob_ability/summon, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/go_for_swim, - ) - -/datum/ai_planning_subtree/targeted_mob_ability/pointed_bubble - ability_key = BB_LEAPER_BUBBLE - finish_planning = FALSE - -/datum/ai_planning_subtree/targeted_mob_ability/flop - ability_key = BB_LEAPER_FLOP - finish_planning = FALSE - -/datum/ai_planning_subtree/targeted_mob_ability/flop/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/current_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(isclosedturf(current_target) || isspaceturf(current_target)) - return - return ..() - -/datum/ai_planning_subtree/targeted_mob_ability/volley - ability_key = BB_LEAPER_VOLLEY - finish_planning = FALSE - -/datum/ai_planning_subtree/targeted_mob_ability/summon - ability_key = BB_LEAPER_SUMMON - finish_planning = FALSE + behavior_tree_json = "code/modules/mob/living/basic/jungle/leaper/leaper.bt.json" /datum/pet_command/use_ability/flop command_name = "Flop" diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.json b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.json new file mode 100644 index 000000000000..333cf00241ca --- /dev/null +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.json @@ -0,0 +1,96 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mega_arachnid", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/mega_arachnid_combat" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SURVEILLANCE_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SURVEILLANCE_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_SURVEILLANCE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_SURVEILLANCE_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/climb_tree" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SURVEILLANCE_TARGET", + "target_source": "/datum/target_source/oview_typed/surveillance_equipment", + "targeting_strategy": "/datum/targeting_strategy/working_machine", + "vision_range": 7 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CLIMBED_TREE", + "target_source": "/datum/target_source/oview_single_type/flora_tree", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + ] +} diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm index fbe737eb2b2a..15a47f283cab 100644 --- a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm @@ -1,79 +1,15 @@ /datum/ai_controller/basic_controller/mega_arachnid + behavior_tree_json = "code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_BASIC_MOB_FLEE_DISTANCE = 5, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/arachnid_restrain, - /datum/ai_planning_subtree/basic_melee_attack_subtree/mega_arachnid, - /datum/ai_planning_subtree/flee_target/mega_arachnid, - /datum/ai_planning_subtree/climb_trees, - /datum/ai_planning_subtree/find_and_hunt_target/destroy_surveillance, - ) - -///destroy surveillance objects to boost our stealth -/datum/ai_planning_subtree/find_and_hunt_target/destroy_surveillance - target_key = BB_SURVEILLANCE_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/find_active_surveillance - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target - hunt_targets = list(/obj/machinery/camera, /obj/machinery/light) - hunt_range = 7 - -/datum/ai_behavior/find_hunt_target/find_active_surveillance - -/datum/ai_behavior/find_hunt_target/find_active_camera/valid_dinner(mob/living/source, obj/machinery/dinner, radius) - if(dinner.machine_stat & BROKEN) - return FALSE - - return can_see(source, dinner, radius) - -///spray slippery acid as we flee! -/datum/ai_planning_subtree/flee_target/mega_arachnid - flee_behaviour = /datum/ai_behavior/run_away_from_target/mega_arachnid - -/datum/ai_planning_subtree/flee_target/mega_arachnid/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return - var/datum/action/cooldown/slip_acid = controller.blackboard[BB_ARACHNID_SLIP] - - if(!QDELETED(slip_acid) && slip_acid.IsAvailable()) - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_ARACHNID_SLIP) - - return ..() - -/datum/ai_behavior/run_away_from_target/mega_arachnid - clear_failed_targets = FALSE - -///only engage in melee combat against cuffed targets, otherwise keep throwing restraints at them -/datum/ai_planning_subtree/basic_melee_attack_subtree/mega_arachnid - ///minimum health our target must be before we can attack them - var/minimum_health = 50 - -/datum/ai_planning_subtree/basic_melee_attack_subtree/mega_arachnid/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(!ishuman(target)) - return ..() - - var/mob/living/carbon/human_target = target - if(!human_target.legcuffed && human_target.health > minimum_health) - return - return ..() +/datum/target_source/oview_typed/surveillance_equipment + typecache = list(/obj/machinery/camera = TRUE, /obj/machinery/light = TRUE) -/datum/ai_planning_subtree/targeted_mob_ability/arachnid_restrain - ability_key = BB_ARACHNID_RESTRAIN -/// only fire ability at humans if they are not cuffed -/datum/ai_planning_subtree/targeted_mob_ability/arachnid_restrain/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[target_key] - if(!ishuman(target)) - return - var/mob/living/carbon/human_target = target - if(human_target.legcuffed) - return - return ..() +/datum/bt_node/subtree/mega_arachnid_combat + behavior_tree_json = "code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.json" diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.json b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.json new file mode 100644 index 000000000000..bb4cdb6b8655 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.json @@ -0,0 +1,147 @@ +{ + "dm_type": "/datum/bt_node/subtree/mega_arachnid_combat", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_ARACHNID_SLIP" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_ARACHNID_SLIP" + } + } + } + ] + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_type", + "vars": { + "target_type": "/mob/living/carbon/human" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_legcuffed", + "vars": { + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_ARACHNID_RESTRAIN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_ARACHNID_RESTRAIN", + "target_key": "BB_CURRENT_TARGET" + } + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_type", + "vars": { + "target_type": "/mob/living/carbon/human", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_legcuffed", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_health_below_fraction", + "vars": { + "key": "BB_CURRENT_TARGET", + "fraction": 0.5 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + } +} diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling.bt.json b/code/modules/mob/living/basic/jungle/seedling/seedling.bt.json new file mode 100644 index 000000000000..8fff5f8770d2 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/seedling/seedling.bt.json @@ -0,0 +1,286 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/seedling", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/override_id_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_reagent", + "vars": { + "invert": false, + "key": "BB_WATERCAN_TARGET", + "reagent_type": "/datum/reagent/water" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HYDROPLANT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HYDROPLANT_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_HYDROPLANT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_HYDROPLANT_TARGET" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_SOLARBEAM_ABILITY", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BEAMABLE_HYDROPLANT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BEAMABLE_HYDROPLANT_TARGET", + "required_dist": 2 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_SOLARBEAM_ABILITY", + "target_key": "BB_BEAMABLE_HYDROPLANT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_BEAMABLE_HYDROPLANT_TARGET" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/item_inside_pawn", + "vars": { + "key": "BB_WATERCAN_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_reagent", + "vars": { + "invert": true, + "key": "BB_WATERCAN_TARGET", + "reagent_type": "/datum/reagent/water" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + } + } + ] + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/item_inside_pawn", + "vars": { + "invert": true, + "key": "BB_WATERCAN_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_WATERCAN_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_WATERCAN_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_WATERCAN_TARGET" + } + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/item_inside_pawn", + "vars": { + "invert": true, + "key": "BB_WATERCAN_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_WATERCAN_TARGET", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_single_type/watering_can" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/item_inside_pawn", + "vars": { + "key": "BB_WATERCAN_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_reagent", + "vars": { + "invert": true, + "key": "BB_WATERCAN_TARGET", + "reagent_type": "/datum/reagent/water" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview", + "targeting_strategy": "/datum/targeting_strategy/water_dispenser" + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HYDROPLANT_TARGET", + "target_source": "/datum/target_source/oview", + "targeting_strategy": "/datum/targeting_strategy/treatable_hydro", + "time_between_perform": "5 SECONDS" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_SOLARBEAM_ABILITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_BEAMABLE_HYDROPLANT_TARGET", + "target_source": "/datum/target_source/oview", + "targeting_strategy": "/datum/targeting_strategy/beamable_hydro", + "time_between_perform": "4 SECONDS" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm index fe4f3204f26c..18705d45e71b 100644 --- a/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm @@ -5,167 +5,15 @@ BB_WEEDLEVEL_THRESHOLD = 3, BB_WATERLEVEL_THRESHOLD = 90, ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_and_hunt_target/watering_can, - /datum/ai_planning_subtree/find_and_hunt_target/fill_watercan, - /datum/ai_planning_subtree/find_and_hunt_target/treat_hydroplants, - /datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants, - ) - -/datum/ai_planning_subtree/find_and_hunt_target/watering_can - target_key = BB_WATERCAN_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target - hunt_targets = list(/obj/item/reagent_containers/cup/watering_can) - hunt_range = 7 - -/datum/ai_planning_subtree/find_and_hunt_target/watering_can/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn) //we already have what we came for! - return - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/treat_hydroplants - target_key = BB_HYDROPLANT_TARGET - finding_behavior = /datum/ai_behavior/find_and_set/treatable_hydro - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/treat_hydroplant - hunt_targets = list(/obj/machinery/hydroponics) - hunt_range = 7 - -/datum/ai_behavior/find_and_set/treatable_hydro - action_cooldown = 5 SECONDS - -/datum/ai_behavior/find_and_set/treatable_hydro/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/possible_trays = list() - var/mob/living/living_pawn = controller.pawn - var/waterlevel_threshold = controller.blackboard[BB_WATERLEVEL_THRESHOLD] - var/weedlevel_threshold = controller.blackboard[BB_WEEDLEVEL_THRESHOLD] - var/watering_can = locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn - - for(var/obj/machinery/hydroponics/hydro in oview(search_range, controller.pawn)) - if(isnull(hydro.myseed)) - continue - if(hydro.waterlevel < waterlevel_threshold && watering_can) - possible_trays += hydro - continue - if(hydro.weedlevel > weedlevel_threshold || hydro.plant_status == HYDROTRAY_PLANT_DEAD) - possible_trays += hydro - continue - - if(possible_trays.len) - return pick(possible_trays) - -/datum/ai_behavior/hunt_target/interact_with_target/treat_hydroplant - hunt_cooldown = 2 SECONDS - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/interact_with_target/treat_hydroplant/target_caught(mob/living/living_pawn, obj/machinery/hydroponics/hydro_target) - if(QDELETED(hydro_target) || QDELETED(hydro_target.myseed)) - return - - if(hydro_target.plant_status == HYDROTRAY_PLANT_DEAD) - living_pawn.manual_emote("weeps...") //weep over the dead plants - return ..() - - -/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants - target_key = BB_BEAMABLE_HYDROPLANT_TARGET - finding_behavior = /datum/ai_behavior/find_and_set/beamable_hydroplants - hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam - hunt_targets = list(/obj/machinery/hydroponics) - hunt_range = 7 - -/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/datum/action/cooldown/solar_ability = controller.blackboard[BB_SOLARBEAM_ABILITY] - if(QDELETED(solar_ability) || !solar_ability.IsAvailable()) - return - return ..() - -/datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 2 - action_cooldown = 1 MINUTES - ability_key = BB_SOLARBEAM_ABILITY - -/datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam/setup(datum/ai_controller/controller, target_key, ability_key) - . = ..() - var/obj/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/find_and_set/beamable_hydroplants - action_cooldown = 15 SECONDS - -/datum/ai_behavior/find_and_set/beamable_hydroplants/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/possible_trays = list() - - for(var/obj/machinery/hydroponics/hydro in oview(search_range, controller.pawn)) - if(isnull(hydro.myseed)) - continue - if(hydro.plant_health < hydro.myseed.endurance) - possible_trays += hydro - - if(possible_trays.len) - return pick(possible_trays) - -/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/suitable_dispenser - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/water_source - hunt_targets = list(/obj/structure/sink, /obj/structure/reagent_dispensers) - hunt_range = 7 - -/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/obj/item/reagent_containers/can = locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn - - if(isnull(can)) - return - if(locate(/datum/reagent/water) in can.reagents.reagent_list) - return - - return ..() - -/datum/ai_behavior/find_hunt_target/suitable_dispenser - -/datum/ai_behavior/find_hunt_target/suitable_dispenser/valid_dinner(mob/living/source, obj/structure/water_source, radius) - if(!(locate(/datum/reagent/water) in water_source.reagents.reagent_list)) - return FALSE - - return can_see(source, water_source, radius) - -/datum/ai_behavior/hunt_target/interact_with_target/water_source - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - always_reset_target = TRUE - hunt_cooldown = 5 SECONDS + behavior_tree_json = "code/modules/mob/living/basic/jungle/seedling/seedling.bt.json" /datum/ai_controller/basic_controller/seedling/meanie blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/seedling_rapid, - /datum/ai_planning_subtree/targeted_mob_ability/solarbeam, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/targeted_mob_ability/seedling_rapid - ability_key = BB_RAPIDSEEDS_ABILITY - finish_planning = FALSE - -/datum/ai_planning_subtree/targeted_mob_ability/solarbeam - ability_key = BB_SOLARBEAM_ABILITY - finish_planning = FALSE + behavior_tree_json = "code/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.json" ///pet commands /datum/pet_command/use_ability/solarbeam diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.json b/code/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.json new file mode 100644 index 000000000000..d050e7bf8f0b --- /dev/null +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.json @@ -0,0 +1,106 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/seedling/meanie", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_RAPIDSEEDS_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_SOLARBEAM_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/jungle/venus_human_trap.dm b/code/modules/mob/living/basic/jungle/venus_human_trap.dm index 3d2538b3c5c1..b3dc776c33b9 100644 --- a/code/modules/mob/living/basic/jungle/venus_human_trap.dm +++ b/code/modules/mob/living/basic/jungle/venus_human_trap.dm @@ -256,18 +256,11 @@ vines -= vine /datum/ai_controller/basic_controller/human_trap + behavior_tree_json = "code/modules/mob/living/basic/jungle/human_trap.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/continue_planning, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) #undef FINAL_BUD_GROWTH_ICON diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.json b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.json new file mode 100644 index 000000000000..e3d26ddf95ba --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.json @@ -0,0 +1,116 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/basilisk", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_trait", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": true, + "key": "TRAIT_OVERWATCHED" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "min_distance": 2 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "max_range": 9 + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm index 3d6380d25be7..4c2f73a2949e 100644 --- a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm +++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm @@ -76,17 +76,10 @@ ranged_attacks.projectile_type = projectile_type /datum/ai_controller/basic_controller/basilisk + behavior_tree_json = "code/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_AGGRO_RANGE = 5, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.json b/code/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.json new file mode 100644 index 000000000000..224a83c4cc1e --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.json @@ -0,0 +1,143 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bileworm", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_BILEWORM_DEVOUR" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/mob_stat_at_least", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_BASIC_MOB_EXECUTION_TARGET", + "min_stat": "UNCONSCIOUS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_BILEWORM_DEVOUR", + "target_key": "BB_BASIC_MOB_EXECUTION_TARGET", + "maximum_distance": 16 + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": false, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_BILEWORM_RESURFACE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bileworm_should_resurface", + "vars": { + "target_key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute", + "vars": { + "ability_key": "BB_BILEWORM_RESURFACE", + "target_key": "BB_CURRENT_TARGET" + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_BILEWORM_SPEW_BILE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute", + "vars": { + "ability_key": "BB_BILEWORM_SPEW_BILE", + "target_key": "BB_CURRENT_TARGET" + } + } + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm index d3a45a89a762..136811add24e 100644 --- a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm @@ -29,7 +29,7 @@ to_chat(burrower, span_warning("Couldn't burrow anywhere near the target!")) if(burrower.ai_controller?.ai_status == AI_STATUS_ON) //this is a valid reason to give up on a target - burrower.ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + burrower.ai_controller.clear_blackboard_key(BB_CURRENT_TARGET) return if (istype(burrower, /mob/living/basic/mining/bileworm) && !force) diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm index cd2450018cf3..3f7f24cdb4af 100644 --- a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm @@ -1,54 +1,24 @@ /datum/ai_controller/basic_controller/bileworm + behavior_tree_json = "code/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/bileworm, BB_TARGET_PRIORITY_STRATEGY = /datum/target_priority_strategy/mining, BB_BILEWORM_FLEE_DISTANCE = 3, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/bileworm_attack, - /datum/ai_planning_subtree/bileworm_execute, + BB_TARGET_MINIMUM_STAT = UNCONSCIOUS, ) /datum/targeting_strategy/basic/bileworm ignore_sight = TRUE -/datum/ai_planning_subtree/bileworm_attack - -/datum/ai_planning_subtree/bileworm_attack/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - - var/datum/action/cooldown/mob_cooldown/resurface = controller.blackboard[BB_BILEWORM_RESURFACE] - var/datum/action/cooldown/mob_cooldown/bile = controller.blackboard[BB_BILEWORM_SPEW_BILE] - - if(resurface?.IsAvailable() && (controller.blackboard[BB_BILEWORM_SCARED] || get_dist(controller.pawn, controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) <= controller.blackboard[BB_BILEWORM_FLEE_DISTANCE])) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_plan_execute, BB_BILEWORM_RESURFACE, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - if(bile?.IsAvailable()) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_plan_execute, BB_BILEWORM_SPEW_BILE, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING //focus on the fight - -/datum/ai_planning_subtree/bileworm_execute - -/datum/ai_planning_subtree/bileworm_execute/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - var/atom/movable/target = controller.blackboard[BB_BASIC_MOB_EXECUTION_TARGET] - if(QDELETED(target) || !isliving(target)) - return - - var/datum/action/cooldown/mob_cooldown/devour = controller.blackboard[BB_BILEWORM_DEVOUR] - - if(!(devour?.IsAvailable())) - return - - var/mob/living/living_target = target - if(living_target.stat < UNCONSCIOUS) - return - - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target, BB_BILEWORM_DEVOUR, BB_BASIC_MOB_EXECUTION_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING //focus on devouring this fool +/// Passes when the worm should burrow and reposition: it's been scared, or its target has gotten within flee distance. +/datum/bt_node/decorator/bileworm_should_resurface + /// Blackboard key holding the atom we want to keep our distance from. + var/target_key = BB_CURRENT_TARGET + +/datum/bt_node/decorator/bileworm_should_resurface/check_condition(datum/ai_controller/controller) + if(controller.blackboard[BB_BILEWORM_SCARED]) + return TRUE + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + return get_dist(controller.pawn, target) <= controller.blackboard[BB_BILEWORM_FLEE_DISTANCE] diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm index 41a1a0f228a3..61f0088b2a21 100644 --- a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm @@ -7,7 +7,7 @@ background_icon_state = "bg_demon" overlay_icon_state = "bg_demon_border" click_to_activate = TRUE - cooldown_time = 5 SECONDS + cooldown_time = 3 SECONDS melee_cooldown_time = 0 /// How far does our beam go? var/beam_range = 10 diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.json b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.json new file mode 100644 index 000000000000..ea42be930a4d --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.json @@ -0,0 +1,122 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/brimdemon", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/brimbeam", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 9 + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_cardinal", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 2, + "maximum_distance": 9 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm index b60d7ab4a906..78af06d27424 100644 --- a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm @@ -9,41 +9,11 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/no_target - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic, - /datum/ai_planning_subtree/move_to_cardinal/brimdemon, - /datum/ai_planning_subtree/targeted_mob_ability/brimbeam, - ) - -/datum/ai_planning_subtree/move_to_cardinal/brimdemon - move_behaviour = /datum/ai_behavior/move_to_cardinal/brimdemon - -/datum/ai_behavior/move_to_cardinal/brimdemon - minimum_distance = 2 - -/datum/ai_behavior/move_to_cardinal/brimdemon/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(!succeeded) - return - var/mob/living/target = controller.blackboard[target_key] - var/datum/action/cooldown/ability = controller.blackboard[BB_TARGETED_ACTION] - if(QDELETED(target) || QDELETED(controller.pawn) || !ability?.IsAvailable()) - return - ability.InterceptClickOn(clicker = controller.pawn, target = target) - -/datum/ai_planning_subtree/targeted_mob_ability/brimbeam - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/brimbeam - -/datum/ai_behavior/targeted_mob_ability/brimbeam - /// Don't shoot if too far away - var/max_target_distance = 9 + behavior_tree_json = "code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.json" -/datum/ai_behavior/targeted_mob_ability/brimbeam/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) +/// Brimdemon's beam only fires when the target is lined up on a cardinal direction. +/datum/bt_node/ai_behavior/targeted_mob_ability/brimbeam/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/target = controller.blackboard[target_key] - if (QDELETED(target) || !(get_dir(controller.pawn, target) in GLOB.cardinals) || get_dist(controller.pawn, target) > max_target_distance) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(QDELETED(target) || !(get_dir(controller.pawn, target) in GLOB.cardinals)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED return ..() diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.json b/code/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.json new file mode 100644 index 000000000000..9f009491b52a --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.json @@ -0,0 +1,93 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/babygrub", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/dig_away_from_danger" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/grub_eat_target", + "bindings": { + "bjwb8dxm": "BB_ORE_TARGET", + "bj13tsp3": "BB_ORE_TARGET", + "b6t594ql": "BB_ORE_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_FOUND_MOM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_MOM", + "required_dist": 1, + "finish_on_arrival": true + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mom" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.json b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.json new file mode 100644 index 000000000000..f445db7953f8 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.json @@ -0,0 +1,238 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/goldgrub", + "type": "selector", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] + }, + { + "type": "subtree", + "subtype": "", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/dig_away_from_danger" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/burrow_through_ground" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/grub_eat_target", + "bindings": { + "b6t594ql": "BB_BOULDER_TARGET", + "bj13tsp3": "BB_BOULDER_TARGET", + "bjwb8dxm": "BB_BOULDER_TARGETBB_BOULDER_TARGET" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/grub_eat_target", + "bindings": { + "b6t594ql": "BB_ORE_TARGET", + "bj13tsp3": "BB_ORE_TARGET", + "bjwb8dxm": "BB_ORE_TARGET" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/grub_eat_target", + "bindings": { + "b6t594ql": "BB_VENT_TARGET", + "bj13tsp3": "BB_VENT_TARGET", + "bjwb8dxm": "BB_VENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_TARGET_MINERAL_WALL" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_MINING_COOLDOWN", + "cooldown_duration": "4 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/mine_wall", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/grab_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_boulder" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore_vent" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MINING_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mineral_wall", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_grub_egg" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm index 0f0cbbed3da4..e50003731f04 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm @@ -1,5 +1,5 @@ -#define BURROW_RANGE 5 /datum/ai_controller/basic_controller/goldgrub + ai_traits = parent_type::ai_traits | RUN_WHILE_UNWATCHED blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -8,20 +8,7 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/burrow_through_ground, - /datum/ai_planning_subtree/dig_away_from_danger, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores, - /datum/ai_planning_subtree/find_and_hunt_target/break_boulders, - /datum/ai_planning_subtree/find_and_hunt_target/harvest_vents, - /datum/ai_planning_subtree/find_and_hunt_target/baby_egg, - /datum/ai_planning_subtree/mine_walls, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.json" /datum/ai_controller/basic_controller/babygrub blackboard = list( @@ -33,34 +20,77 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/dig_away_from_danger, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/look_for_adult, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.json" + +/datum/pet_command/grub_spit + command_name = "Spit" + radial_icon = 'icons/obj/ore.dmi' + radial_icon_state = "uranium" + command_desc = "Ask your grub pet to spit out its ores." + speech_commands = list("spit", "ores") -/datum/ai_planning_subtree/burrow_through_ground +/datum/pet_command/grub_spit/execute_action(datum/ai_controller/controller) + var/datum/action/cooldown/spit_ability = controller.blackboard[BB_SPIT_ABILITY] + if(!spit_ability?.IsAvailable()) + return + controller.set_blackboard_key(BB_PET_ACTIVE_ABILITY, spit_ability) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/untargeted_ability) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) -/datum/ai_planning_subtree/burrow_through_ground/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(is_jaunting(controller.pawn) && controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/burrow_through_ground, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/grub_spit/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to spit its ores!" -/datum/ai_behavior/burrow_through_ground - action_cooldown = 10 SECONDS -/datum/ai_behavior/burrow_through_ground/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - if(!is_jaunting(controller.pawn) || QDELETED(target)) +#define BURROW_RANGE 5 + + +/** + * Burrows underground (or stays underground) when danger is present or a storm is approaching. + * Returns RUNNING while underground+storm (blocks everything). + * Returns FAILURE when no action needed so the selector passes through. + */ +/datum/bt_node/ai_behavior/dig_away_from_danger + +/datum/bt_node/ai_behavior/dig_away_from_danger/perform(seconds_per_tick, datum/ai_controller/controller) + var/currently_underground = is_jaunting(controller.pawn) + var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] + var/datum/action/cooldown/dig_ability = controller.blackboard[BB_BURROW_ABILITY] + + if(currently_underground && storm_approaching) + return AI_BEHAVIOR_DELAY // Stay underground while storm approaches RUNNING blocks everything + + if(!dig_ability?.IsAvailable()) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/has_target = controller.blackboard_key_exists(BB_CURRENT_TARGET) + + if(currently_underground && !has_target) + // No target/danger while underground emerge + INVOKE_ASYNC(dig_ability, TYPE_PROC_REF(/datum/action, Trigger)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + if(storm_approaching || has_target) + // Go underground to escape + INVOKE_ASYNC(dig_ability, TYPE_PROC_REF(/datum/action, Trigger)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + +/** + * While jaunting (underground) with a target, moves the grub's phased form further away. + * Returns FAILURE when not jaunting so the selector passes through. + */ +/datum/bt_node/ai_behavior/burrow_through_ground + time_between_perform = 10 SECONDS + +/datum/bt_node/ai_behavior/burrow_through_ground/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn - var/atom/movable/phased = living_pawn.loc + var/atom/target = controller.blackboard[BB_CURRENT_TARGET] + if(!is_jaunting(living_pawn) || QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/atom/movable/phased = living_pawn.loc var/list/turfs_list = RANGE_TURFS(BURROW_RANGE, phased) var/current_max_distance = 0 var/turf/selected_turf @@ -68,12 +98,10 @@ for(var/turf/possible_turf as anything in turfs_list) if(!ismineralturf(possible_turf) && !isasteroidturf(possible_turf)) continue - var/distance_to_target = get_dist(possible_turf, target) if(distance_to_target > current_max_distance) current_max_distance = distance_to_target selected_turf = possible_turf - if(distance_to_target == BURROW_RANGE) break @@ -81,152 +109,106 @@ return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED phased.forceMove(selected_turf) - return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY - -///consume food! -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores - target_key = BB_ORE_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/hunt_ores - finding_behavior = /datum/ai_behavior/find_hunt_target/hunt_ores - hunt_targets = list(/obj/item/stack/ore) - hunt_chance = 90 - hunt_range = 9 - -/datum/ai_behavior/find_hunt_target/hunt_ores - -/datum/ai_behavior/find_hunt_target/hunt_ores/valid_dinner(mob/living/basic/source, obj/item/stack/ore/target, radius) - var/list/forbidden_ore = source.ai_controller.blackboard[BB_ORE_IGNORE_TYPES] - - if(is_type_in_list(target, forbidden_ore)) - return FALSE - - if(!isturf(target.loc)) - return FALSE - - var/obj/item/pet_target = source.ai_controller.blackboard[BB_CURRENT_PET_TARGET] - if(target == pet_target) //we are currently fetching this ore for master, dont eat it! - return FALSE - - return can_see(source, target, radius) - -/datum/ai_behavior/hunt_target/interact_with_target/hunt_ores - always_reset_target = TRUE - -///break boulders so that we can find more food! -/datum/ai_planning_subtree/find_and_hunt_target/harvest_vents - target_key = BB_VENT_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target //We call the ore vent's produce_boulder() proc here to produce a single boulder. - finding_behavior = /datum/ai_behavior/find_hunt_target/harvest_vents - hunt_targets = list(/obj/structure/ore_vent) - hunt_chance = 25 - hunt_range = 15 - -/datum/ai_behavior/find_hunt_target/harvest_vents - -/datum/ai_behavior/find_hunt_target/harvest_vents/valid_dinner(mob/living/basic/source, obj/structure/target, radius) - if(target in source) - return FALSE - - var/turf/vent_turf = target.drop_location() - var/counter = 0 - for(var/obj/item/boulder in vent_turf.contents) - counter++ - if(counter > MAX_BOULDERS_PER_VENT) //Too many items currently on the vent - return FALSE - - return can_see(source, target, radius) - -///break boulders so that we can find more food! -/datum/ai_planning_subtree/find_and_hunt_target/break_boulders - target_key = BB_BOULDER_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target //We process boulders once every tap, so we dont need to do anything special here - finding_behavior = /datum/ai_behavior/find_hunt_target/break_boulders - hunt_targets = list(/obj/item/boulder) - hunt_chance = 100 //If we can, we should always break boulders. - hunt_range = 9 - -/datum/ai_behavior/find_hunt_target/break_boulders - -/datum/ai_behavior/find_hunt_target/break_boulders/valid_dinner(mob/living/basic/source, obj/item/boulder/target, radius) - if(target in source) - return FALSE - - var/obj/item/pet_target = source.ai_controller.blackboard[BB_CURRENT_PET_TARGET] - if(target == pet_target) //we are currently fetching this ore for master, dont eat it! - return FALSE - return can_see(source, target, radius) - -///find our child's egg and pull it! -/datum/ai_planning_subtree/find_and_hunt_target/baby_egg - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/grub_egg - finding_behavior = /datum/ai_behavior/find_hunt_target - hunt_targets = list(/obj/item/food/egg/green/grub_egg) - hunt_chance = 75 - hunt_range = 9 - -/datum/ai_planning_subtree/find_and_hunt_target/baby_egg - -/datum/ai_planning_subtree/find_and_hunt_target/baby_egg/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.pulling) //we are already pulling something - return - return ..() - -/datum/ai_behavior/hunt_target/grub_egg - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/grub_egg/target_caught(mob/living/hunter, obj/item/target) - hunter.start_pulling(target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -///only dig away if storm is coming or if humans are around -/datum/ai_planning_subtree/dig_away_from_danger +/// Finds an ore pile. Sets BB_ORE_TARGET. Skips forbidden types and fetch targets. +/datum/bt_node/ai_behavior/find_ore + time_between_perform = 5 SECONDS + var/range = 9 -/datum/ai_planning_subtree/dig_away_from_danger/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/currently_underground = is_jaunting(controller.pawn) - var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] - - //dont do anything until the storm passes - if(currently_underground && storm_approaching) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/find_ore/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/grub_pawn = controller.pawn + var/list/forbidden = controller.blackboard[BB_ORE_IGNORE_TYPES] + var/pet_target = controller.blackboard[BB_CURRENT_PET_TARGET] + for(var/obj/item/stack/ore/candidate in oview(range, grub_pawn)) + if(is_type_in_list(candidate, forbidden) || !isturf(candidate.loc)) + continue + if(candidate == pet_target) + continue + if(!can_see(grub_pawn, candidate, range)) + continue + controller.set_blackboard_key(BB_ORE_TARGET, candidate) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Finds a boulder the goldgrub can break. Sets BB_BOULDER_TARGET. +/datum/bt_node/ai_behavior/find_boulder + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/find_boulder/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/grub_pawn = controller.pawn + var/pet_target = controller.blackboard[BB_CURRENT_PET_TARGET] + for(var/obj/item/boulder/candidate in oview(9, grub_pawn)) + if(candidate == pet_target) + continue + if(!can_see(grub_pawn, candidate, 9)) + continue + controller.set_blackboard_key(BB_BOULDER_TARGET, candidate) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/datum/action/cooldown/dig_ability = controller.blackboard[BB_BURROW_ABILITY] +/// Finds an ore vent to harvest. Sets BB_VENT_TARGET. +/datum/bt_node/ai_behavior/find_ore_vent + time_between_perform = 10 SECONDS - if(!dig_ability.IsAvailable()) - return +/datum/bt_node/ai_behavior/find_ore_vent/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(25, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/grub_pawn = controller.pawn + for(var/obj/structure/ore_vent/candidate in oview(15, grub_pawn)) + if(candidate in grub_pawn) + continue + var/turf/vent_turf = candidate.drop_location() + var/counter = 0 + var/too_many = FALSE + for(var/obj/item/boulder in vent_turf.contents) + counter++ + if(counter > MAX_BOULDERS_PER_VENT) + too_many = TRUE + break + if(too_many || !can_see(grub_pawn, candidate, 15)) + continue + controller.set_blackboard_key(BB_VENT_TARGET, candidate) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/has_target = controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) +/// Finds a grub egg to protect/pull. Sets BB_LOW_PRIORITY_HUNTING_TARGET. +/datum/bt_node/ai_behavior/find_grub_egg + time_between_perform = 10 SECONDS - //a storm is coming or someone is nearby, its time to escape - if(currently_underground) - if(has_target) - return - controller.queue_behavior(/datum/ai_behavior/use_mob_ability/burrow, BB_BURROW_ABILITY) - return SUBTREE_RETURN_FINISH_PLANNING - if(storm_approaching || has_target) - controller.queue_behavior(/datum/ai_behavior/use_mob_ability/burrow, BB_BURROW_ABILITY) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/find_grub_egg/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(75, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/grub_pawn = controller.pawn + if(grub_pawn.pulling) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + for(var/obj/item/food/egg/green/grub_egg/egg in oview(9, grub_pawn)) + if(!can_see(grub_pawn, egg, 9)) + continue + controller.set_blackboard_key(BB_LOW_PRIORITY_HUNTING_TARGET, egg) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED -/datum/ai_behavior/use_mob_ability/burrow - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION +/// Eats/interacts with a target at the given blackboard key. Must be adjacent. +/datum/bt_node/ai_behavior/grub_eat + var/target_key + time_between_perform = 3 SECONDS -/datum/pet_command/grub_spit - command_name = "Spit" - radial_icon = 'icons/obj/ore.dmi' - radial_icon_state = "uranium" - command_desc = "Ask your grub pet to spit out its ores." - speech_commands = list("spit", "ores") +/datum/bt_node/ai_behavior/grub_eat/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!controller.pawn.Adjacent(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, FALSE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/pet_command/grub_spit/execute_action(datum/ai_controller/controller) - var/datum/action/cooldown/spit_ability = controller.blackboard[BB_SPIT_ABILITY] - if(!spit_ability?.IsAvailable()) - return - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_SPIT_ABILITY) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/grub_eat/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) -/datum/pet_command/grub_spit/retrieve_command_text(atom/living_pet, atom/target) - return "signals [living_pet] to spit its ores!" +/datum/bt_node/subtree/grub_eat_target + behavior_tree_json = "code/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.json" #undef BURROW_RANGE diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.json b/code/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.json new file mode 100644 index 000000000000..13dc419c1094 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.json @@ -0,0 +1,44 @@ +{ + "dm_type": "/datum/bt_node/subtree/grub_eat_target", + "bindings": { + "b6t594ql": { + "label": "target_key", + "default": "null" + }, + "bj13tsp3": { + "label": "target_key", + "default": "" + }, + "bjwb8dxm": { + "label": "target_key", + "default": "" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "$b6t594ql" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "$bj13tsp3", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/grub_eat", + "vars": { + "target_key": "$bjwb8dxm" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath.bt.json b/code/modules/mob/living/basic/lavaland/goliath/goliath.bt.json new file mode 100644 index 000000000000..e6a5703bc924 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath.bt.json @@ -0,0 +1,171 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/goliath", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/goliath_tentacles", + "vars": { + "ability_key": "BB_GOLIATH_TENTACLES", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "sequence", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_GOLIATH_HOLE_TARGET", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/goliath_find_diggable_turf", + "vars": { + "target_key": "BB_GOLIATH_HOLE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_GOLIATH_HOLE_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/goliath_dig", + "vars": { + "target_key": "BB_GOLIATH_HOLE_TARGET" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": true + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm index 60dfd7a15d07..639d60fe530f 100644 --- a/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm @@ -9,117 +9,52 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/targeted_mob_ability/goliath_tentacles, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/goliath, - /datum/ai_planning_subtree/goliath_find_diggable_turf, - /datum/ai_planning_subtree/goliath_dig, - ) - -/datum/ai_planning_subtree/basic_melee_attack_subtree/goliath - operational_datums = list(/datum/component/ai_target_timer) - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/goliath + behavior_tree_json = "code/modules/mob/living/basic/lavaland/goliath/goliath.bt.json" -/// Go for the tentacles if they're available -/datum/ai_behavior/basic_melee_attack/goliath +/// Use tentacles on the current target only after tracking them for MIN_TIME_TO_TENTACLE, and only if not already leg-grappled +/datum/bt_node/ai_behavior/targeted_mob_ability/goliath_tentacles + var/min_target_time = MIN_TIME_TO_TENTACLE -/datum/ai_behavior/basic_melee_attack/goliath/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, health_ratio_key) - var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 - if (time_on_target < MIN_TIME_TO_TENTACLE) - return ..() +/datum/bt_node/ai_behavior/targeted_mob_ability/goliath_tentacles/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/target = controller.blackboard[target_key] - // Interrupt attack chain to use tentacles, unless the target is already tentacled - if (ismecha(target) || (isliving(target) && !target.get_item_by_slot(ITEM_SLOT_LEGCUFFED))) - var/datum/action/cooldown/using_action = controller.blackboard[BB_GOLIATH_TENTACLES] - if (using_action?.IsAvailable()) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - return ..() - -/datum/ai_planning_subtree/targeted_mob_ability/goliath_tentacles - ability_key = BB_GOLIATH_TENTACLES - operational_datums = list(/datum/component/ai_target_timer) - -/datum/ai_planning_subtree/targeted_mob_ability/goliath_tentacles/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[target_key] - if (!(isliving(target) || ismecha(target)) || (isliving(target) && target.get_item_by_slot(ITEM_SLOT_LEGCUFFED))) - return // Target can be an item or already grabbed, we don't want to tentacle those + if(!(isliving(target) || ismecha(target))) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(isliving(target) && target.get_item_by_slot(ITEM_SLOT_LEGCUFFED)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 - if (time_on_target < MIN_TIME_TO_TENTACLE) - return // We need to spend some time acquiring our target first + if(time_on_target < min_target_time) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED return ..() -/// If we got nothing better to do, find a turf we can search for tasty roots and such -/datum/ai_planning_subtree/goliath_find_diggable_turf - -/datum/ai_planning_subtree/goliath_find_diggable_turf/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - controller.queue_behavior(/datum/ai_behavior/goliath_find_diggable_turf) - -/datum/ai_behavior/goliath_find_diggable_turf - action_cooldown = 2 SECONDS - /// Where do we store the target data +/// Randomly picks a nearby undig asteroid turf to dig and stores it in target_key +/datum/bt_node/ai_behavior/goliath_find_diggable_turf + time_between_perform = 2 SECONDS var/target_key = BB_GOLIATH_HOLE_TARGET - /// How far do we look for turfs? var/scan_range = 3 -/datum/ai_behavior/goliath_find_diggable_turf/perform(seconds_per_tick, datum/ai_controller/controller) - var/turf/target_turf = controller.blackboard[target_key] - if (is_valid_turf(target_turf)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - +/datum/bt_node/ai_behavior/goliath_find_diggable_turf/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/pawn = controller.pawn var/list/nearby_turfs = RANGE_TURFS(scan_range, pawn) - var/turf/check_turf = pick(nearby_turfs) // This isn't an efficient search algorithm but we don't need it to be - if (!is_valid_turf(check_turf)) - // Otherwise they won't perform idle wanderin + var/turf/open/misc/asteroid/check_turf = pick(nearby_turfs) + if(!istype(check_turf) || check_turf.dug) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED controller.set_blackboard_key(target_key, check_turf) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/// Return true if this is a turf we can dig -/datum/ai_behavior/goliath_find_diggable_turf/proc/is_valid_turf(turf/check_turf) - if (!isasteroidturf(check_turf)) - return FALSE - var/turf/open/misc/asteroid/floor = check_turf - return !floor.dug - -/datum/ai_planning_subtree/goliath_dig - /// Where did we store the target data +/// Melee-attacks the stored hole target turf and clears the key when done +/datum/bt_node/ai_behavior/goliath_dig + time_between_perform = 3 MINUTES var/target_key = BB_GOLIATH_HOLE_TARGET -/datum/ai_planning_subtree/goliath_dig/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!controller.blackboard_key_exists(target_key)) - return - controller.queue_behavior(/datum/ai_behavior/goliath_dig, target_key) - return SUBTREE_RETURN_FINISH_PLANNING - -/// If we got nothing better to do, dig a little hole -/datum/ai_behavior/goliath_dig - action_cooldown = 3 MINUTES - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/goliath_dig/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target_turf = controller.blackboard[target_key] - if (QDELETED(target_turf)) - return - set_movement_target(controller, target_turf) - -/datum/ai_behavior/goliath_dig/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/goliath_dig/perform(seconds_per_tick, datum/ai_controller/controller) var/turf/target_turf = controller.blackboard[target_key] var/mob/living/basic/basic_mob = controller.pawn - if(!target_turf.IsReachableBy(basic_mob)) - return AI_BEHAVIOR_DELAY + if(isnull(target_turf) || !target_turf.IsReachableBy(basic_mob)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED basic_mob.melee_attack(target_turf) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/goliath_dig/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/goliath_dig/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.json b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.json new file mode 100644 index 000000000000..6351fb95695d --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.json @@ -0,0 +1,81 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/gutlunch/gutlunch_baby", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_FOUND_MOM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_MOM", + "required_dist": 1, + "finish_on_arrival": true + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_parent", + "vars": { + "mom_types_key": "BB_FIND_MOM_TYPES", + "found_mom_key": "BB_FOUND_MOM" + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.json b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.json new file mode 100644 index 000000000000..8b412de5dcc2 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.json @@ -0,0 +1,96 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/gutlunch/gutlunch_milk", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_TROUGH_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TROUGH_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target/food_trough", + "vars": { + "target_key": "BB_TROUGH_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": false + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_CHECK_HUNGRY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TROUGH_TARGET", + "target_source": "/datum/target_source/oview_single_type/gutlunch_trough", + "targeting_strategy": "/datum/targeting_strategy/trough_with_ore", + "vision_range": 9 + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.json b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.json new file mode 100644 index 000000000000..5b32a3d8dc73 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.json @@ -0,0 +1,53 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/gutlunch/gutlunch_warrior", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "5 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat", + "bindings": { + "b2jvnm5d": "TRUE" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_ashwalkers" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm index 800f4c351aa5..96aed06f1467 100644 --- a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm @@ -1,7 +1,7 @@ #define MAXIMUM_GUTLUNCH_POP 20 /datum/ai_controller/basic_controller/gutlunch ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk + behavior_tree_json = ABSTRACT_AI_CLASS /datum/ai_controller/basic_controller/gutlunch/gutlunch_warrior blackboard = list( @@ -10,99 +10,22 @@ BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/mining/gutlunch/milk), BB_BABIES_CHILD_TYPES = list(/mob/living/basic/mining/gutlunch/grub), BB_MAX_CHILDREN = 5, + BB_FUCKS = TRUE, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/befriend_ashwalkers, - /datum/ai_planning_subtree/make_babies/gutlunch, - ) - -/datum/ai_planning_subtree/make_babies/gutlunch/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(GLOB.gutlunch_count >= MAXIMUM_GUTLUNCH_POP) - return - return ..() - -///find ashwalkers and add them to the list of masters -/datum/ai_planning_subtree/befriend_ashwalkers - -/datum/ai_planning_subtree/befriend_ashwalkers/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - controller.queue_behavior(/datum/ai_behavior/befriend_ashwalkers) - -/datum/ai_behavior/befriend_ashwalkers - action_cooldown = 5 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/befriend_ashwalkers/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/living_pawn = controller.pawn - - for(var/mob/living/potential_friend in oview(9, living_pawn)) - if(!isashwalker(potential_friend)) - continue - if(living_pawn.has_ally(REF(potential_friend))) - continue - living_pawn.befriend(potential_friend) - to_chat(potential_friend, span_nicegreen("[living_pawn] looks at you with endearing eyes!")) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - + behavior_tree_json = "code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.json" /datum/ai_controller/basic_controller/gutlunch/gutlunch_baby blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FIND_MOM_TYPES = list(/mob/living/basic/mining/gutlunch/milk), ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/look_for_adult, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.json" /datum/ai_controller/basic_controller/gutlunch/gutlunch_milk blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_and_hunt_target/food_trough - ) - -///consume food! -/datum/ai_planning_subtree/find_and_hunt_target/food_trough - target_key = BB_TROUGH_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/food_trough - finding_behavior = /datum/ai_behavior/find_hunt_target/food_trough - hunt_targets = list(/obj/structure/ore_container/food_trough/gutlunch_trough) - hunt_chance = 75 - hunt_range = 9 - - -/datum/ai_planning_subtree/find_and_hunt_target/food_trough/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_CHECK_HUNGRY]) - return - return ..() - -/datum/ai_behavior/find_hunt_target/food_trough - -/datum/ai_behavior/find_hunt_target/food_trough/valid_dinner(mob/living/basic/source, obj/target, radius) - if(isnull(target)) - return FALSE - - if(isnull(locate(/obj/item/stack/ore) in target)) - return FALSE - - return can_see(source, target, radius) - -/datum/ai_behavior/hunt_target/interact_with_target/food_trough - always_reset_target = TRUE - behavior_combat_mode = FALSE + behavior_tree_json = "code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.json" /datum/pet_command/mine_walls command_name = "Mine" @@ -121,10 +44,7 @@ return ..() /datum/pet_command/mine_walls/execute_action(datum/ai_controller/controller) - if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) - controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_mineral_wall, BB_CURRENT_PET_TARGET) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/mine_walls) /datum/pet_command/mine_walls/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to start mining!" @@ -138,4 +58,53 @@ return FALSE return ..() +/// Interacts with the food trough to eat ore, then clears the hungry flag. +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/food_trough + always_reset_target = TRUE + behavior_combat_mode = FALSE + +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/food_trough/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(succeeded) + controller.clear_blackboard_key(BB_CHECK_HUNGRY) + +///Find nearby ashwalkers. we love lizards. +/datum/bt_node/ai_behavior/befriend_ashwalkers + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/befriend_ashwalkers/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + for(var/mob/living/potential_friend in oview(9, living_pawn)) + if(!isashwalker(potential_friend) || living_pawn.has_ally(REF(potential_friend))) + continue + living_pawn.befriend(potential_friend) + to_chat(potential_friend, span_nicegreen("[living_pawn] looks at you with endearing eyes!")) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + +/// Searches for and moves to a parent mob (of types in BB_FIND_MOM_TYPES), sets BB_FOUND_MOM. +/datum/bt_node/ai_behavior/find_parent + var/mom_types_key + var/found_mom_key + +/datum/bt_node/ai_behavior/find_parent/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living_pawn = controller.pawn + var/list/mom_types = controller.blackboard[mom_types_key] + if(!length(mom_types)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + for(var/mob/mother in oview(7, living_pawn)) + if(!is_type_in_list(mother, mom_types)) + continue + controller.set_blackboard_key(found_mom_key, mother) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +// + +/// Mine walls pet command subtree: find mineral wall -> move to it -> mine it -> clear command. +/datum/bt_node/subtree/pet_command/mine_walls + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.json" + + #undef MAXIMUM_GUTLUNCH_POP diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.json b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.json new file mode 100644 index 000000000000..6a99b899d664 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/hivelord", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_ranged_combat" +} diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm index 08599831a51c..8a8d234ebec5 100644 --- a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm @@ -60,7 +60,7 @@ /mob/living/basic/mining/hivelord/proc/complete_spawn(turf/spawn_turf) var/mob/living/brood = new death_spawn_type(spawn_turf) SET_FACTION_AND_ALLIES_FROM(brood, src) - brood.ai_controller?.set_blackboard_key(ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + brood.ai_controller?.set_blackboard_key(ai_controller.blackboard[BB_CURRENT_TARGET]) brood.dir = get_dir(src, spawn_turf) /mob/living/basic/mining/hivelord/RangedAttack(atom/atom_target, modifiers) diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm b/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm index aecabc823737..afb54229538e 100644 --- a/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm @@ -1,15 +1,9 @@ /// Basically just keep away and shit out worms /datum/ai_controller/basic_controller/hivelord + behavior_tree_json = "code/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_AGGRO_RANGE = 5, // Only get mad at people nearby ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/targeted_mob_ability, - ) diff --git a/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm index 146d2fba82f3..080a40b0e9cb 100644 --- a/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm +++ b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm @@ -11,7 +11,7 @@ melee_cooldown_time = 0 shared_cooldown = NONE /// If a mob is not clicked directly, inherit targeting data from this blackboard key and setting it upon this target key - var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET + var/ai_target_key = BB_CURRENT_TARGET /// What are we actually spawning? var/spawn_type = /mob/living/basic/hivelord_brood /// Do we automatically fire with no cooldown when damaged? diff --git a/code/modules/mob/living/basic/lavaland/legion/legion.bt.json b/code/modules/mob/living/basic/lavaland/legion/legion.bt.json new file mode 100644 index 000000000000..451fa812723c --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion.bt.json @@ -0,0 +1,102 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/legion", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/legion" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm index 8d5082d4c088..438a1977a521 100644 --- a/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm +++ b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm @@ -1,5 +1,6 @@ /// Keep away and launch skulls at every opportunity, prioritising injured allies /datum/ai_controller/basic_controller/legion + behavior_tree_json = "code/modules/mob/living/basic/lavaland/legion/legion.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/legion, BB_TARGET_PRIORITY_STRATEGY = /datum/target_priority_strategy/mining, @@ -8,35 +9,21 @@ BB_RANGED_SKIRMISH_MIN_DISTANCE = 4, BB_RANGED_SKIRMISH_MAX_DISTANCE = 6, ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/random_speech/legion, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/targeted_mob_ability, - ) /// Chase and attack whatever we are targeting, if it's friendly we will heal them /datum/ai_controller/basic_controller/legion_brood + behavior_tree_json = "code/modules/mob/living/basic/lavaland/legion/legion_brood.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/legion, BB_TARGET_PRIORITY_STRATEGY = /datum/target_priority_strategy/mining/low_node_priority, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Target nearby friendlies if they are hurt (and are not themselves Legions) /datum/targeting_strategy/basic/legion + custom_faction_check = TRUE /datum/targeting_strategy/basic/legion/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) if (!living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly)) @@ -48,39 +35,3 @@ return TRUE return the_target.stat == DEAD || the_target.health >= the_target.maxHealth -/// Don't run away from friendlies -/datum/ai_planning_subtree/flee_target/legion - -/datum/ai_planning_subtree/flee_target/legion/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[target_key] - if (QDELETED(target) || target.faction_check_atom(controller.pawn)) - return // Only flee if we have a hostile target - return ..() - -/// Make spooky sounds, if we have a corpse inside then impersonate them -/datum/ai_planning_subtree/random_speech/legion - speech_chance = 1 - speak = list("Come...", "Legion...", "Why...?") - emote_hear = list("groans.", "wails.", "whimpers.") - emote_see = list("twitches.", "shudders.") - /// Stuff to specifically say into a radio - var/list/radio_speech = list("Come...", "Why...?") - -/datum/ai_planning_subtree/random_speech/legion/speak(datum/ai_controller/controller) - var/mob/living/carbon/human/victim = controller.blackboard[BB_LEGION_CORPSE] - if (QDELETED(victim) || prob(30)) - return ..() - - if(HAS_MIND_TRAIT(victim, TRAIT_MIMING)) // mimes cant talk - return - - var/list/remembered_speech = controller.blackboard[BB_LEGION_RECENT_LINES] || list() - - if (length(remembered_speech) && prob(50)) // Don't spam the radio - controller.queue_behavior(/datum/ai_behavior/perform_speech, pick(remembered_speech)) - return - - var/obj/item/radio/mob_radio = locate() in victim.contents - if (QDELETED(mob_radio)) - return ..() // No radio, just talk funny - controller.queue_behavior(/datum/ai_behavior/perform_speech_radio, pick(radio_speech + remembered_speech), mob_radio, list(RADIO_CHANNEL_SUPPLY, RADIO_CHANNEL_COMMON)) diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_brood.bt.json b/code/modules/mob/living/basic/lavaland/legion/legion_brood.bt.json new file mode 100644 index 000000000000..c75a7dcbbe11 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion_brood.bt.json @@ -0,0 +1,81 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/legion_brood", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm index dd2bbe18de2b..ad58998fde40 100644 --- a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm +++ b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm @@ -112,7 +112,7 @@ return // Inherit our creator's target and reinforcement requests - ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, creator.ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + ai_controller.set_blackboard_key(BB_CURRENT_TARGET, creator.ai_controller.blackboard[BB_CURRENT_TARGET]) ai_controller.set_blackboard_key(BB_MINING_MOB_REINFORCEMENTS_REQUESTS, creator.ai_controller.blackboard[BB_MINING_MOB_REINFORCEMENTS_REQUESTS]) /// Reference handling diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.json b/code/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.json new file mode 100644 index 000000000000..6e8e16f2a27a --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.json @@ -0,0 +1,119 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/legion_monkey", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/consider_venting" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": "10" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_ENTRY_VENT_TARGET", + "target_source": "/datum/target_source/oview_single_type/vent_pump", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 7 + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/legion" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm b/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm index 1fc569e51f8f..44b5b2009286 100644 --- a/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm +++ b/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm @@ -42,6 +42,7 @@ /// Opportunistically hops in and out of vents, if it can find one and is not biting someone. /datum/ai_controller/basic_controller/legion_monkey + behavior_tree_json = "code/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, @@ -51,14 +52,3 @@ ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - - // We understand that vents are nice little hidey holes through epigenetic inheritance, so we'll use them. - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/legion, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/opportunistic_ventcrawler, - ) diff --git a/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm index 2f464fb08432..889d0347be40 100644 --- a/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm +++ b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm @@ -11,7 +11,7 @@ melee_cooldown_time = 0 shared_cooldown = NONE /// If a mob is not clicked directly, inherit targeting data from this blackboard key and setting it upon this target key - var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET + var/ai_target_key = BB_CURRENT_TARGET /// What are we actually spawning? var/spawn_type = /mob/living/basic/mining/legion_brood /// How far can we fire? diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.json b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.json new file mode 100644 index 000000000000..bc32d519c2b7 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.json @@ -0,0 +1,131 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/lobstrosity", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index ecf69e02a9a9..fe66b6cb247a 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -270,16 +270,14 @@ command_feedback = "growl" pointed_reaction = "and growls" pet_ability_key = BB_TARGETED_ACTION - ability_behavior = /datum/ai_behavior/pet_use_ability/then_attack/long_ranged /datum/pet_command/use_ability/lob_charge/set_command_target(mob/living/parent, atom/target) if (!target) return FALSE var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(parent.ai_controller.blackboard[targeting_strategy_key]) - if(!targeter?.can_attack(parent, target)) + if(!targeter?.is_valid_target(parent, target)) parent.balloon_alert_to_viewers("shakes head!") return FALSE return ..() /datum/pet_command/use_ability/lob_charge/shrimp - ability_behavior = /datum/ai_behavior/pet_use_ability/then_attack/short_ranged diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm index bd9b2addff62..b922843b0ee2 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm @@ -19,25 +19,15 @@ BB_ONLY_FISH_WHILE_HUNGRY = TRUE, BB_TARGET_PRIORITY_TRAIT = TRAIT_SCARY_FISHERMAN, BB_OWNER_SELF_HARM_RESPONSES = SHRIMP_HARM_RESPONSES, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/hoard_fingers, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/targeted_mob_ability/lobster, - /datum/ai_planning_subtree/flee_target/lobster, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/fish/fish_from_turfs, - /datum/ai_planning_subtree/find_fingers, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.json" /datum/ai_controller/basic_controller/lobstrosity/TryPossessPawn(atom/new_pawn) . = ..() @@ -58,282 +48,19 @@ BB_BASIC_MOB_FLEE_DISTANCE = 4, BB_TARGET_PRIORITY_TRAIT = TRAIT_SCARY_FISHERMAN, BB_OWNER_SELF_HARM_RESPONSES = SHRIMP_HARM_RESPONSES, - ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/hoard_fingers, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/targeted_mob_ability/lobster, - /datum/ai_planning_subtree/flee_target/lobster, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/fish/fish_from_turfs, - /datum/ai_planning_subtree/find_fingers, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ///A subtype of juvenile lobster AI that has the target_retaliate behaviour instead of simple_find_target /datum/ai_controller/basic_controller/lobstrosity/juvenile/calm - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/hoard_fingers, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/targeted_mob_ability/lobster/juvenile, - /datum/ai_planning_subtree/flee_target/lobster, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/fish/fish_from_turfs, - /datum/ai_planning_subtree/find_fingers, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.json" ///A subtype of juvenile lobster AI that has the capricious_retaliate behaviour instead of simple_find_target /datum/ai_controller/basic_controller/lobstrosity/juvenile/capricious - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/hoard_fingers, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/capricious_retaliate, - /datum/ai_planning_subtree/targeted_mob_ability/lobster/juvenile, - /datum/ai_planning_subtree/flee_target/lobster, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/fish/fish_from_turfs, - /datum/ai_planning_subtree/find_fingers, - ) - -/datum/ai_planning_subtree/basic_melee_attack_subtree/lobster - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/lobster - -/datum/ai_planning_subtree/basic_melee_attack_subtree/lobster/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(!isliving(target)) - return ..() - if (!controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return - if (!isnull(controller.blackboard[BB_LOBSTROSITY_TARGET_LIMB])) - return - if(controller.blackboard[BB_LOBSTROSITY_NAIVE_HUNTER] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - return //juvenile lobstrosities fear me. - var/mob/living/living_pawn = controller.pawn - if (DOING_INTERACTION_WITH_TARGET(living_pawn, target)) - return - return ..() - -/datum/ai_behavior/basic_melee_attack/lobster - -/datum/ai_behavior/basic_melee_attack/lobster/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - var/mob/living/target = controller.blackboard[target_key] - if (isnull(target) || !istype(target)) - return ..() - var/is_vulnerable = FALSE - if(controller.blackboard[BB_LOBSTROSITY_NAIVE_HUNTER]) - if(HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - //Trigger lobstrosity PTSD. Don't clear the target so we can run away. - controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - if(target.body_position == LYING_DOWN) - is_vulnerable = TRUE - if(!is_vulnerable) - for (var/trait in controller.blackboard[BB_LOBSTROSITY_EXPLOIT_TRAITS]) - if (!HAS_TRAIT(target, trait)) - continue - is_vulnerable = TRUE - break - if (!is_vulnerable) - controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) - if (!controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - // We don't want to clear our target - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - return ..() - -/datum/ai_planning_subtree/flee_target/lobster - flee_behaviour = /datum/ai_behavior/run_away_from_target/lobster - -/datum/ai_planning_subtree/flee_target/lobster/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/target = controller.blackboard[target_key] - if(!QDELETED(target) && controller.blackboard[BB_LOBSTROSITY_NAIVE_HUNTER] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - return ..() - var/datum/action/cooldown/using_action = controller.blackboard[BB_TARGETED_ACTION] - if (using_action?.IsAvailable()) - return FALSE - return ..() - -/datum/ai_behavior/run_away_from_target/lobster - clear_failed_targets = FALSE - -/datum/ai_behavior/run_away_from_target/lobster/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return ..() - - var/is_naive = controller.blackboard[BB_LOBSTROSITY_NAIVE_HUNTER] - var/is_scary = HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN) - - if(!is_naive || !is_scary) //the lobstrosity isn't naive/young and the target isn't a scary fisherman. - if(isliving(target)) - var/mob/living/living_target = target - if(is_naive && living_target.body_position == LYING_DOWN) - controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - for (var/trait in controller.blackboard[BB_LOBSTROSITY_EXPLOIT_TRAITS]) - if (!HAS_TRAIT(target, trait)) - continue - controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/mob/living/us = controller.pawn - if (us.pulling == target) - us.stop_pulling() // If we're running away from someone, best not to bring them with us - - return ..() - -/// Don't use charge ability on an adjacent target, and make sure you're visible before you start -/datum/ai_planning_subtree/targeted_mob_ability/lobster - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/min_range - -/datum/ai_planning_subtree/targeted_mob_ability/lobster/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target) || in_range(controller.pawn, target)) - return - if(controller.blackboard[BB_LOBSTROSITY_NAIVE_HUNTER] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - return - return ..() - - -/datum/ai_planning_subtree/targeted_mob_ability/lobster/juvenile - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/min_range/short - -/// Look for loose arms lying around -/datum/ai_planning_subtree/find_fingers - /// Where do we store target limb data? - var/target_key = BB_LOBSTROSITY_TARGET_LIMB - /// What are we actually looking for? - var/desired_type = /obj/item/bodypart/arm - -/datum/ai_planning_subtree/find_fingers/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(/datum/ai_behavior/find_and_set, target_key, desired_type) - -/// If you see an arm, grab it and run -/datum/ai_planning_subtree/hoard_fingers - /// Where do we store target limb data? - var/target_key = BB_LOBSTROSITY_TARGET_LIMB - -/datum/ai_planning_subtree/hoard_fingers/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/atom/current_target = controller.blackboard[target_key] - if (QDELETED(current_target)) - return - - var/mob/living/living_pawn = controller.pawn - if (living_pawn.pulling != current_target) - controller.queue_behavior(/datum/ai_behavior/grab_fingers, target_key) - else - controller.queue_behavior(/datum/ai_behavior/hoard_fingers, target_key) - return SUBTREE_RETURN_FINISH_PLANNING - -/// If our target is an arm then move over and drag it -/datum/ai_behavior/grab_fingers - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/grab_fingers/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/current_target = controller.blackboard[target_key] - if (QDELETED(current_target)) - return FALSE - set_movement_target(controller, current_target) - -/datum/ai_behavior/grab_fingers/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/current_target = controller.blackboard[target_key] - if (QDELETED(current_target)) - return AI_BEHAVIOR_DELAY - var/mob/living/living_pawn = controller.pawn - living_pawn.start_pulling(current_target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/// How far we'll try to go before eating an arm -#define FLEE_TO_RANGE 9 -/// How many times we'll attempt to move before giving up -#define MAX_LOBSTROSITY_PATIENCE 15 - -/// If we are dragging an arm then run away until we are out of range and feast -/datum/ai_behavior/hoard_fingers - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// We store a counter at this key we increment on every movement until we are overwhelmed with hunger - var/patience_key = BB_LOBSTROSITY_FINGER_LUST - -/datum/ai_behavior/hoard_fingers/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/current_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (QDELETED(current_target)) - set_movement_target(controller, get_turf(controller.pawn)) - return - var/perform_flags = target_step_away(controller, current_target, target_key) - if (perform_flags & AI_BEHAVIOR_SUCCEEDED) - finish_action(controller, TRUE, target_key) - else if(perform_flags & AI_BEHAVIOR_FAILED) - finish_action(controller, FALSE, target_key) - -/// Find the next step to take away from the current target -/datum/ai_behavior/hoard_fingers/proc/target_step_away(datum/ai_controller/controller, atom/current_target, target_key) - var/turf/next_step = get_step_away(controller.pawn, current_target) - if (!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) - set_movement_target(controller, next_step) - return NONE - var/list/all_dirs = GLOB.alldirs.Copy() - all_dirs -= get_dir(controller.pawn, next_step) - all_dirs -= get_dir(controller.pawn, current_target) - shuffle_inplace(all_dirs) - for (var/dir in all_dirs) - next_step = get_step(controller.pawn, dir) - if (!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) - set_movement_target(controller, next_step) - return NONE - return AI_BEHAVIOR_FAILED - -/datum/ai_behavior/hoard_fingers/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/current_patience = controller.blackboard[patience_key] + 1 - if (current_patience >= MAX_LOBSTROSITY_PATIENCE) - if(eat_fingers(controller, target_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(patience_key, current_patience) - var/mob/living/living_pawn = controller.pawn - if (isnull(living_pawn.pulling)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/atom/current_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (QDELETED(current_target) || !can_see(controller.pawn, current_target, FLEE_TO_RANGE)) - if(eat_fingers(controller, target_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(target_step_away(controller, current_target, target_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/// Finally consume those delicious digits -/datum/ai_behavior/hoard_fingers/proc/eat_fingers(datum/ai_controller/controller, target_key) - var/mob/living/basic/living_pawn = controller.pawn - var/atom/fingers = controller.blackboard[target_key] - if (QDELETED(fingers) || living_pawn.pulling != fingers) - return AI_BEHAVIOR_FAILED - controller.ai_interact(target = fingers) - return AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/hoard_fingers/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.set_blackboard_key(patience_key, 0) - controller.clear_blackboard_key(target_key) - controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.json" -#undef FLEE_TO_RANGE -#undef MAX_LOBSTROSITY_PATIENCE #undef SHRIMP_HARM_RESPONSES diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.json b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.json new file mode 100644 index 000000000000..584477480386 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/lobstrosity/juvenile/calm", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.json b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.json new file mode 100644 index 000000000000..7f5bcc6f817e --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/lobstrosity/juvenile/capricious", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_capricious_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/bard.bt.json b/code/modules/mob/living/basic/lavaland/mook/bard.bt.json new file mode 100644 index 000000000000..9ea04b0d887e --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/bard.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mook/bard", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_mook_behavior", + "bindings": { + "b1kiiayf": "/datum/bt_node/subtree/bard_play_music", + "b0ymbjo1": "/datum/bt_node/subtree/bard_find_targets" + } +} diff --git a/code/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.json b/code/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.json new file mode 100644 index 000000000000..d1fa50af8080 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.json @@ -0,0 +1,56 @@ +{ + "dm_type": "/datum/bt_node/subtree/bard_find_targets", + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_MOOK_TRIBAL_CHIEF" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "target_source": "/datum/target_source/oview_single_type/tribal_chief", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "long_range_friendship": true, + "forget_target": false + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOOK_MUSIC_AUDIENCE", + "target_source": "/datum/target_source/near_village_humans", + "targeting_strategy": "/datum/targeting_strategy/conscious_human", + "time_between_perform": "1 SECONDS" + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.json b/code/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.json new file mode 100644 index 000000000000..b29f041e5e82 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.json @@ -0,0 +1,55 @@ +{ + "dm_type": "/datum/bt_node/subtree/bard_play_music", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_play_instrument", + "bindings": { + "bulu13xf": "75" + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_MOOK_MUSIC_AUDIENCE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_MOOK_MUSIC_AUDIENCE", + "required_dist": 2, + "finish_on_arrival": false + } + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target", + "vars": { + "target_key": "BB_HOME_VILLAGE", + "walk_chance": 75 + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.json b/code/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.json new file mode 100644 index 000000000000..07346360c179 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.json @@ -0,0 +1,68 @@ +{ + "dm_type": "/datum/bt_node/subtree/chief_find_targets", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_deposit_position" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore", + "vars": { + "time_between_perform": "1 SECONDS", + "range": 1 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_MATERIAL_STAND_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MATERIAL_STAND_TARGET", + "target_source": "/datum/target_source/oview_single_type/ore_stand", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.json b/code/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.json new file mode 100644 index 000000000000..2f786c350ea0 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.json @@ -0,0 +1,53 @@ +{ + "dm_type": "/datum/bt_node/subtree/chief_issue_commands", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_COMMAND_COOLDOWN", + "cooldown_duration": "10 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/issue_pet_command", + "vars": { + "command_list_key": "BB_MOOK_COMMANDS", + "command_type": "/datum/pet_command/attack", + "target_key": "BB_CURRENT_TARGET", + "commandable_mob_type": "/mob/living/basic/mining/mook", + "command_distance": 7 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_ORE_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/issue_pet_command", + "vars": { + "command_list_key": "BB_MOOK_COMMANDS", + "command_type": "/datum/pet_command/fetch", + "target_key": "BB_ORE_TARGET", + "commandable_mob_type": "/mob/living/basic/mining/mook", + "command_distance": 7 + } + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.json b/code/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.json new file mode 100644 index 000000000000..f77afce86f4e --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.json @@ -0,0 +1,150 @@ +{ + "dm_type": "/datum/bt_node/subtree/chief_manage_village", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_DEPOSIT_POSITION" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEPOSIT_POSITION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_DEPOSIT_POSITION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_MATERIAL_STAND_TARGET" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_MOOK_BONFIRE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_MOOK_BONFIRE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_MOOK_BONFIRE_TARGET", + "always_reset_target": true, + "behavior_combat_mode": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": true, + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_ORE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ORE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up_item_virtual", + "vars": { + "target_key": "BB_ORE_TARGET", + "storage_key": "BB_SIMPLE_CARRY_ITEM" + } + } + ] + } + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target", + "vars": { + "target_key": "BB_HOME_VILLAGE", + "walk_chance": 75 + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOOK_BONFIRE_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/bonfire_targets", + "targeting_strategy": "/datum/targeting_strategy/unlit_bonfire" + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.json b/code/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.json new file mode 100644 index 000000000000..ceb511fd5b25 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.json @@ -0,0 +1,178 @@ +{ + "dm_type": "/datum/bt_node/subtree/generic_mook_behavior", + "bindings": { + "b1kiiayf": { + "label": "Idle Behavior", + "default": "/datum/bt_node/subtree" + }, + "b0ymbjo1": { + "label": "Targetting Behavior", + "default": "/datum/bt_node/subtree" + }, + "bk7ixti3": { + "label": "High Priority Behavior", + "default": "/datum/bt_node/subtree" + } + }, + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_HOME_VILLAGE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_village" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_same_z_as_key", + "vars": { + "key": "BB_HOME_VILLAGE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_farther_than_from_key", + "vars": { + "anchor_key": "BB_HOME_VILLAGE", + "distance_key": "BB_MAXIMUM_DISTANCE_TO_VILLAGE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/mook_has_flee_reason", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_INJURED_MOOK" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_MOOK_JUMP_ABILITY" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TARGET_MINERAL_WALL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_MOOK_MUSIC_AUDIENCE" + } + } + ] + } + } + } + }, + { + "type": "subtree", + "subtype": "$bk7ixti3" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_MOOK_LEAP_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "$b1kiiayf" + } + ] + }, + { + "type": "subtree", + "subtype": "$b0ymbjo1" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/go_mining.bt.json b/code/modules/mob/living/basic/lavaland/mook/go_mining.bt.json new file mode 100644 index 000000000000..df73c3e1a160 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/go_mining.bt.json @@ -0,0 +1,213 @@ +{ + "dm_type": "/datum/bt_node/subtree/go_mining", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_DEPOSIT_POSITION" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEPOSIT_POSITION", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_DEPOSIT_POSITION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_MATERIAL_STAND_TARGET" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": true, + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_ORE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ORE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up_item_virtual", + "vars": { + "target_key": "BB_ORE_TARGET", + "storage_key": "BB_SIMPLE_CARRY_ITEM" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_TARGET_MINERAL_WALL" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/mine_wall", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL", + "time_between_perform": "1 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_MOOK_MINING_COOLDOWN", + "cooldown_duration": "10 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore", + "vars": { + "time_between_perform": "0.5 SECONDS" + } + } + ] + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/calculate_wander_destination" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_WANDER_DESTINATION", + "required_dist": 0 + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_deposit_position" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOOK_MINING_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mineral_wall/mook", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL" + } + } + } + ] + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/heal_injured.bt.json b/code/modules/mob/living/basic/lavaland/mook/heal_injured.bt.json new file mode 100644 index 000000000000..dd6d274c6348 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/heal_injured.bt.json @@ -0,0 +1,42 @@ +{ + "dm_type": "/datum/bt_node/subtree/heal_injured", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_INJURED_MOOK" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_INJURED_MOOK" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "always_reset_target": true, + "target_key": "BB_INJURED_MOOK" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target", + "vars": { + "target_key": "BB_HOME_VILLAGE", + "walk_chance": 75 + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/mook.bt.json b/code/modules/mob/living/basic/lavaland/mook/mook.bt.json new file mode 100644 index 000000000000..a129f8b83d5a --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mook", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_mook_behavior", + "bindings": { + "b0ymbjo1": "/datum/bt_node/subtree/worker_find_targets", + "b1kiiayf": "/datum/bt_node/subtree/go_mining" + } +} diff --git a/code/modules/mob/living/basic/lavaland/mook/mook.dm b/code/modules/mob/living/basic/lavaland/mook/mook.dm index 966c1bfba185..2ecc54895ef6 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook.dm @@ -44,6 +44,8 @@ /datum/pet_command/attack, /datum/pet_command/fetch, ) + /// Things we want to find to heal + var/static/list/heal_targets = list(/mob/living/basic/mining/mook/worker) /mob/living/basic/mining/mook/Initialize(mapload) . = ..() @@ -64,6 +66,8 @@ grant_healer_abilities() AddComponent(/datum/component/obeys_commands, pet_commands) + ai_controller?.set_blackboard_key(BB_MOOK_HEAL_TARGETS, typecacheof(heal_targets)) + /// Returns a list of actions and blackboard keys to pass into `grant_actions_by_list`. /mob/living/basic/mining/mook/proc/get_innate_abilities() @@ -84,6 +88,7 @@ /mob/living/basic/mining/mook/Entered(atom/movable/mover) if(istype(mover, /obj/item/stack/ore)) held_ore = mover + ai_controller?.set_blackboard_key(BB_SIMPLE_CARRY_ITEM, mover) update_appearance(UPDATE_OVERLAYS) return ..() @@ -187,7 +192,7 @@ if(istype(intruder, /mob/living/basic/mining/mook)) return for(var/mob/living/basic/mining/mook/villager in oview(src, 9)) - villager.ai_controller?.set_blackboard_key_assoc(BB_BASIC_MOB_RETALIATE_LIST, intruder, world.time) + villager.ai_controller?.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, intruder, world.time) /mob/living/basic/mining/mook/worker @@ -276,10 +281,12 @@ var/static/mutable_appearance/chief_active = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_leap") ///overlay in our warmup state var/static/mutable_appearance/chief_warmup = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_warmup") + var/static/list/bonfire_targets = list(/obj/structure/bonfire) /mob/living/basic/mining/mook/worker/tribal_chief/Initialize(mapload) . = ..() update_appearance() + ai_controller?.set_blackboard_key(BB_BONFIRE_TARGETS, typecacheof(bonfire_targets)) /mob/living/basic/mining/mook/worker/tribal_chief/update_overlays() . = ..() diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm index 92f6156a7059..d3d20f1802b1 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm @@ -5,6 +5,7 @@ GLOBAL_LIST_INIT(mook_commands, list( )) /datum/ai_controller/basic_controller/mook + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/mook.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, BB_BLACKLIST_MINERAL_TURFS = list(/turf/closed/mineral/gibtonite, /turf/closed/mineral/strong), @@ -13,21 +14,11 @@ GLOBAL_LIST_INIT(mook_commands, list( ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/look_for_village, - /datum/ai_planning_subtree/targeted_mob_ability/leap, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/material_stand, - /datum/ai_planning_subtree/use_mob_ability/mook_jump, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook, - /datum/ai_planning_subtree/mine_walls/mook, - /datum/ai_planning_subtree/wander_away_from_village, - ) - can_idle = FALSE // these guys are intended to operate even if nobody's around + // these guys are intended to operate even if nobody's around + ai_traits = DEFAULT_AI_FLAGS | RUN_WHILE_UNWATCHED + +/datum/targeting_strategy/basic/mook + custom_faction_check = TRUE ///check for faction if not a ash walker, otherwise just attack /datum/targeting_strategy/basic/mook/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) @@ -36,389 +27,35 @@ GLOBAL_LIST_INIT(mook_commands, list( return ..() -/datum/ai_planning_subtree/targeted_mob_ability/leap - ability_key = BB_MOOK_LEAP_ABILITY - -/datum/ai_planning_subtree/use_mob_ability/mook_jump - ability_key = BB_MOOK_JUMP_ABILITY - -///jump towards the village when we have found ore or there is a storm coming -/datum/ai_planning_subtree/use_mob_ability/mook_jump/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] - var/mob/living/living_pawn = controller.pawn - var/obj/effect/home = controller.blackboard[BB_HOME_VILLAGE] - if(QDELETED(home)) - return - if(get_dist(living_pawn, home) < controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE]) - return - if(home.z != living_pawn.z) - return - if(!storm_approaching && !(locate(/obj/item/stack/ore) in living_pawn)) - return - - controller.clear_blackboard_key(BB_TARGET_MINERAL_WALL) - return ..() - -///hunt ores that we will haul off back to the village -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook - -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(locate(/obj/item/stack/ore) in living_pawn) - return - return ..() - -///deposit ores into the stand! -/datum/ai_planning_subtree/find_and_hunt_target/material_stand - target_key = BB_MATERIAL_STAND_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/material_stand - finding_behavior = /datum/ai_behavior/find_hunt_target - hunt_targets = list(/obj/structure/ore_container/material_stand) - hunt_range = 9 - -/datum/ai_planning_subtree/find_and_hunt_target/material_stand/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(!locate(/obj/item/stack/ore) in living_pawn) - return - return ..() - -/datum/ai_behavior/hunt_target/interact_with_target/material_stand - required_distance = 0 - always_reset_target = TRUE - behavior_combat_mode = FALSE - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - -///try to face the counter when depositing ores -/datum/ai_behavior/hunt_target/interact_with_target/material_stand/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) - . = ..() - var/atom/hunt_target = controller.blackboard[hunting_target_key] - if (QDELETED(hunt_target)) - return FALSE - var/list/possible_turfs = list() - var/list/directions = list(SOUTH, SOUTHEAST) - - for(var/direction in directions) - var/turf/bottom_turf = get_step(hunt_target, direction) - if(!bottom_turf.is_blocked_turf()) - possible_turfs += bottom_turf - - if(!length(possible_turfs)) - return FALSE - set_movement_target(controller, pick(possible_turfs)) - -///look for our village -/datum/ai_planning_subtree/look_for_village - -/datum/ai_planning_subtree/look_for_village/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_HOME_VILLAGE)) - return - - controller.queue_behavior(/datum/ai_behavior/find_village, BB_HOME_VILLAGE) - -/datum/ai_behavior/find_village - -/datum/ai_behavior/find_village/perform(seconds_per_tick, datum/ai_controller/controller, village_key) - - var/obj/effect/landmark/home_marker = locate(/obj/effect/landmark/mook_village) in GLOB.landmarks_list - if(isnull(home_marker)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(village_key, home_marker) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -///explore the lands away from the village to look for ore -/datum/ai_planning_subtree/wander_away_from_village - -/datum/ai_planning_subtree/wander_away_from_village/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] - ///if we have ores to deposit or a storm is approaching, dont wander away - if(storm_approaching || (locate(/obj/item/stack/ore) in living_pawn)) - return - - if(controller.blackboard_key_exists(BB_HOME_VILLAGE)) - controller.queue_behavior(/datum/ai_behavior/wander, BB_HOME_VILLAGE) - -/datum/ai_behavior/wander - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 0 - /// distance we will wander away from the village - var/wander_distance = 9 - -/datum/ai_behavior/wander/setup(datum/ai_controller/controller, village_key) - . = ..() - var/mob/living/living_pawn = controller.pawn - var/obj/effect/target = controller.blackboard[village_key] - if(QDELETED(target)) - return FALSE - - if(target.z != living_pawn.z) - return FALSE - - var/list/angle_directions = list() - for(var/direction in GLOB.alldirs) - angle_directions += dir2angle(direction) - - var/angle_to_home = get_angle(living_pawn, target) - angle_directions -= angle_to_home - angle_directions -= (angle_to_home + 45) - angle_directions -= (angle_to_home - 45) - shuffle_inplace(angle_directions) - - var/turf/wander_destination = get_turf(living_pawn) - for(var/angle in angle_directions) - var/turf/test_turf = get_furthest_turf(living_pawn, angle, target) - if(isnull(test_turf)) - continue - var/distance_from_target = get_dist(target, test_turf) - if(distance_from_target <= get_dist(target, wander_destination)) - continue - wander_destination = test_turf - if(distance_from_target == wander_distance) - break - - set_movement_target(controller, wander_destination) - -/datum/ai_behavior/wander/proc/get_furthest_turf(atom/source, angle, atom/target) - var/turf/return_turf - for(var/i in 1 to wander_distance) - var/turf/test_destination = get_ranged_target_turf_direct(source, target, range = i, offset = angle) - if(test_destination.is_blocked_turf(source_atom = source)) - break - return_turf = test_destination - return return_turf - -/datum/ai_behavior/wander/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_planning_subtree/mine_walls/mook - find_wall_behavior = /datum/ai_behavior/find_mineral_wall/mook - -/datum/ai_planning_subtree/mine_walls/mook/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] - if(storm_approaching || locate(/obj/item/stack/ore) in living_pawn) - return - return ..() - -/datum/ai_behavior/find_mineral_wall/mook - -/datum/ai_behavior/find_mineral_wall/mook/check_if_mineable(datum/ai_controller/controller, turf/target_wall) - var/list/forbidden_turfs = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] - if(is_type_in_list(target_wall, forbidden_turfs)) - return FALSE - return ..() - ///bard mook plays nice music for the village /datum/ai_controller/basic_controller/mook/bard + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/bard.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, BB_MAXIMUM_DISTANCE_TO_VILLAGE = 10, BB_STORM_APPROACHING = FALSE, BB_SONG_LINES = MOOK_SONG, + BB_INSTRUMENT_UP_ASS = TRUE, ) - idle_behavior = /datum/idle_behavior/walk_near_target/mook_village - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/look_for_village, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/play_music_for_visitor, - /datum/ai_planning_subtree/use_mob_ability/mook_jump, - /datum/ai_planning_subtree/generic_play_instrument, - ) - - -///find an audience to follow and play music for! -/datum/ai_planning_subtree/play_music_for_visitor - -/datum/ai_planning_subtree/play_music_for_visitor/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_MOOK_MUSIC_AUDIENCE)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/music_audience, BB_MOOK_MUSIC_AUDIENCE, /mob/living/carbon/human) - return - var/atom/home = controller.blackboard[BB_HOME_VILLAGE] - if(isnull(home)) - return - - var/atom/human_target = controller.blackboard[BB_MOOK_MUSIC_AUDIENCE] - if(get_dist(human_target, home) > controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE] || controller.blackboard[BB_STORM_APPROACHING]) - controller.clear_blackboard_key(BB_MOOK_MUSIC_AUDIENCE) - return - - controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_MOOK_MUSIC_AUDIENCE) - -/datum/ai_behavior/find_and_set/music_audience - -/datum/ai_behavior/find_and_set/music_audience/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/atom/home = controller.blackboard[BB_HOME_VILLAGE] - for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) - if(target.stat > UNCONSCIOUS || !target.mind) - continue - if(isnull(home) || get_dist(target, home) > controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE]) - continue - return target - -/datum/idle_behavior/walk_near_target/mook_village - target_key = BB_HOME_VILLAGE ///healer mooks guard the village from intruders and heal the miner mooks when they come home /datum/ai_controller/basic_controller/mook/support + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/support.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, BB_MAXIMUM_DISTANCE_TO_VILLAGE = 10, BB_STORM_APPROACHING = FALSE, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) - idle_behavior = /datum/idle_behavior/walk_near_target/mook_village - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/look_for_village, - /datum/ai_planning_subtree/acknowledge_chief, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/use_mob_ability/mook_jump, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/injured_mooks, - ) - -///tree to find and register our leader -/datum/ai_planning_subtree/acknowledge_chief/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_MOOK_TRIBAL_CHIEF)) - return - controller.queue_behavior(/datum/ai_behavior/find_and_set/find_chief, BB_MOOK_TRIBAL_CHIEF, /mob/living/basic/mining/mook/worker/tribal_chief) - -/datum/ai_behavior/find_and_set/find_chief/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/chief = locate(locate_path) in oview(search_range, controller.pawn) - if(isnull(chief)) - return null - var/mob/living/living_pawn = controller.pawn - living_pawn.befriend(chief) - return chief - -///find injured miner mooks after they come home from a long day of work -/datum/ai_planning_subtree/find_and_hunt_target/injured_mooks - target_key = BB_INJURED_MOOK - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/injured_mooks - finding_behavior = /datum/ai_behavior/find_hunt_target/injured_mooks - hunt_targets = list(/mob/living/basic/mining/mook/worker) - hunt_range = 9 - -///we only heal when the mooks are home during a storm -/datum/ai_planning_subtree/find_and_hunt_target/injured_mooks/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_STORM_APPROACHING]) - return ..() - - -/datum/ai_behavior/find_hunt_target/injured_mooks - -/datum/ai_behavior/find_hunt_target/injured_mooks/valid_dinner(mob/living/source, mob/living/injured_mook) - return (injured_mook.health < injured_mook.maxHealth) - -/datum/ai_behavior/hunt_target/interact_with_target/injured_mooks - always_reset_target = TRUE - hunt_cooldown = 10 SECONDS - ///the chief would rather command his mooks to attack people than attack them himself /datum/ai_controller/basic_controller/mook/tribal_chief + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, BB_STORM_APPROACHING = FALSE, ) - idle_behavior = /datum/idle_behavior/walk_near_target/mook_village - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/look_for_village, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/leap, - /datum/ai_planning_subtree/issue_commands, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/material_stand, - /datum/ai_planning_subtree/use_mob_ability/mook_jump, - /datum/ai_planning_subtree/find_and_hunt_target/bonfire, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief, - ) - -/datum/ai_planning_subtree/issue_commands - ///how far we look for a mook to command - var/command_distance = 5 - -/datum/ai_planning_subtree/issue_commands/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!locate(/mob/living/basic/mining/mook) in oview(command_distance, controller.pawn)) - return - if(controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_BASIC_MOB_CURRENT_TARGET, /datum/pet_command/attack) - return - var/atom/ore_target = controller.blackboard[BB_ORE_TARGET] - var/mob/living/living_pawn = controller.pawn - if(isnull(ore_target)) - return - if(get_dist(ore_target, living_pawn) <= 1) - return - - controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_ORE_TARGET, /datum/pet_command/fetch) - -/datum/ai_behavior/issue_commands - action_cooldown = 5 SECONDS - -/datum/ai_behavior/issue_commands/perform(seconds_per_tick, datum/ai_controller/controller, target_key, command_path) - var/mob/living/basic/living_pawn = controller.pawn - var/atom/target = controller.blackboard[target_key] - - if(isnull(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/datum/pet_command/to_command = locate(command_path) in GLOB.mook_commands - if(isnull(to_command)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/issue_command = pick(to_command.speech_commands) - living_pawn.say(issue_command, forced = "controller") - living_pawn._pointed(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - -///find an ore, only pick it up when a mook brings it close to us -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief - -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(locate(/obj/item/stack/ore) in living_pawn) - return - - var/atom/target_ore = controller.blackboard[BB_ORE_TARGET] - - if(isnull(target_ore)) - return ..() - - if(!isturf(target_ore.loc)) //picked up by someone else - controller.clear_blackboard_key(BB_ORE_TARGET) - return - - if(get_dist(target_ore, living_pawn) > 1) - return - - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/bonfire - target_key = BB_MOOK_BONFIRE_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/bonfire - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/bonfire - hunt_targets = list(/obj/structure/bonfire) - hunt_range = 9 - - -/datum/ai_behavior/find_hunt_target/bonfire - -/datum/ai_behavior/find_hunt_target/bonfire/valid_dinner(mob/living/source, obj/structure/bonfire/fire, radius) - if(fire.burning) - return FALSE - - return can_see(source, fire, radius) - -/datum/ai_behavior/hunt_target/interact_with_target/bonfire - always_reset_target = TRUE +/datum/ai_controller/basic_controller/mook/tribal_chief/New(atom/new_pawn) + . = ..() + set_blackboard_key(BB_MOOK_COMMANDS, GLOB.mook_commands) diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_bt.dm b/code/modules/mob/living/basic/lavaland/mook/mook_bt.dm new file mode 100644 index 000000000000..d1ba42a14fe6 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_bt.dm @@ -0,0 +1,156 @@ + +///hey buddy I went to mook village and everyone there knew you +/datum/bt_node/ai_behavior/find_village + /// Blackboard key to store the found landmark. + var/target_key = BB_HOME_VILLAGE + +/datum/bt_node/ai_behavior/find_village/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/effect/landmark/home = locate(/obj/effect/landmark/mook_village) in GLOB.landmarks_list + if(isnull(home)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + controller.set_blackboard_key(target_key, home) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/// Mook-specific mineral wall finder that skips turfs in BB_BLACKLIST_MINERAL_TURFS. +/datum/bt_node/ai_behavior/find_mineral_wall/mook + +/datum/bt_node/ai_behavior/find_mineral_wall/mook/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/list/forbidden = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] + if(is_type_in_list(target_wall, forbidden)) + return FALSE + return ..() + +///Wander in a random direction to find ore +/datum/bt_node/ai_behavior/calculate_wander_destination + /// Blackboard key holding the anchor atom to wander away from. + var/anchor_key = BB_HOME_VILLAGE/// Blackboard key to write the chosen turf into. + var/destination_key = BB_WANDER_DESTINATION + /// How far we try to wander from the anchor. + var/wander_distance = 9 + +/datum/bt_node/ai_behavior/calculate_wander_destination/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/atom/anchor = controller.blackboard[anchor_key] + if(QDELETED(anchor)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(anchor.z != living_pawn.z) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/list/angle_directions = list() + for(var/direction in GLOB.alldirs) + angle_directions += dir2angle(direction) + + var/angle_to_home = get_angle(living_pawn, anchor) + angle_directions -= angle_to_home + angle_directions -= (angle_to_home + 45) + angle_directions -= (angle_to_home - 45) + shuffle_inplace(angle_directions) + + var/turf/best = get_turf(living_pawn) + for(var/angle in angle_directions) + var/turf/candidate = _get_furthest_turf(living_pawn, angle, anchor) + if(isnull(candidate)) + continue + var/dist = get_dist(anchor, candidate) + if(dist <= get_dist(anchor, best)) + continue + best = candidate + if(dist >= wander_distance) + break + + controller.set_blackboard_key(destination_key, best) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/calculate_wander_destination/proc/_get_furthest_turf(atom/source, angle, atom/anchor) + var/turf/result + for(var/i in 1 to wander_distance) + var/turf/candidate = get_ranged_target_turf_direct(source, anchor, range = i, offset = angle) + if(candidate.is_blocked_turf(source_atom = source)) + break + result = candidate + return result + + +///gotta get south of the material stand +/datum/bt_node/ai_behavior/find_deposit_position + /// Blackboard key holding the /obj/structure/ore_container/material_stand target. + var/stand_key = BB_MATERIAL_STAND_TARGET + /// Blackboard key to write the chosen deposit turf into. + var/destination_key = BB_DEPOSIT_POSITION + +/datum/bt_node/ai_behavior/find_deposit_position/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/stand = controller.blackboard[stand_key] + if(QDELETED(stand)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/pawn = controller.pawn + var/list/candidates = list() + for(var/direction in list(SOUTH, SOUTHWEST, SOUTHEAST)) + var/turf/candidate = get_step(stand, direction) + if(!candidate.is_blocked_turf()) + candidates += candidate + var/turf/destination = get_closest_atom(/turf/, candidates, pawn) + if(isnull(destination)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + controller.set_blackboard_key(destination_key, destination) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +///OH SHIT STORM COMING (or maybe we found ore :3) +/datum/bt_node/decorator/mook_has_flee_reason + +/datum/bt_node/decorator/mook_has_flee_reason/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(\ + COMSIG_AI_BLACKBOARD_KEY_SET(BB_STORM_APPROACHING),\ + COMSIG_AI_BLACKBOARD_KEY_SET(BB_SIMPLE_CARRY_ITEM),\ + COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_SIMPLE_CARRY_ITEM),\ + ), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/mook_has_flee_reason/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(\ + COMSIG_AI_BLACKBOARD_KEY_SET(BB_STORM_APPROACHING),\ + COMSIG_AI_BLACKBOARD_KEY_SET(BB_SIMPLE_CARRY_ITEM),\ + COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_SIMPLE_CARRY_ITEM),\ + )) + +/datum/bt_node/decorator/mook_has_flee_reason/check_condition(datum/ai_controller/controller) + return controller.blackboard[BB_STORM_APPROACHING] || !isnull(controller.blackboard[BB_SIMPLE_CARRY_ITEM]) + + + +/datum/bt_node/subtree/generic_mook_behavior + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.json" + + +///Worker trees +/datum/bt_node/subtree/worker_find_targets + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.json" + +/datum/bt_node/subtree/go_mining + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/go_mining.bt.json" + + +///Bard trees +/datum/bt_node/subtree/bard_play_music + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.json" + +/datum/bt_node/subtree/bard_find_targets + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.json" + + +///Support trees +/datum/bt_node/subtree/heal_injured + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/heal_injured.bt.json" + +/datum/bt_node/subtree/support_find_targets + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.json" + +///Chief trees +/datum/bt_node/subtree/chief_issue_commands + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.json" + +/datum/bt_node/subtree/chief_manage_village + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.json" + + +/datum/bt_node/subtree/chief_find_targets + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.json" diff --git a/code/modules/mob/living/basic/lavaland/mook/support.bt.json b/code/modules/mob/living/basic/lavaland/mook/support.bt.json new file mode 100644 index 000000000000..ad0fbb631c1a --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/support.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mook/support", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_mook_behavior", + "bindings": { + "b1kiiayf": "/datum/bt_node/subtree/heal_injured", + "b0ymbjo1": "/datum/bt_node/subtree/support_find_targets" + } +} diff --git a/code/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.json b/code/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.json new file mode 100644 index 000000000000..c87cf49caa0b --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.json @@ -0,0 +1,63 @@ +{ + "dm_type": "/datum/bt_node/subtree/support_find_targets", + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_MOOK_TRIBAL_CHIEF" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "target_source": "/datum/target_source/oview_single_type/tribal_chief", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "long_range_friendship": true, + "forget_target": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_STORM_APPROACHING" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_INJURED_MOOK", + "target_source": "/datum/target_source/oview_typed/from_bb_key/mook_heal_targets", + "targeting_strategy": "/datum/targeting_strategy/injured_mob" + } + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.json b/code/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.json new file mode 100644 index 000000000000..3f526950dd0b --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mook/tribal_chief", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_mook_behavior", + "bindings": { + "b0ymbjo1": "/datum/bt_node/subtree/chief_find_targets", + "b1kiiayf": "/datum/bt_node/subtree/chief_manage_village", + "bk7ixti3": "/datum/bt_node/subtree/chief_issue_commands" + } +} diff --git a/code/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.json b/code/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.json new file mode 100644 index 000000000000..26616bcbe111 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.json @@ -0,0 +1,72 @@ +{ + "dm_type": "/datum/bt_node/subtree/worker_find_targets", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_MATERIAL_STAND_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MATERIAL_STAND_TARGET", + "target_source": "/datum/target_source/oview_single_type/ore_stand", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_MOOK_TRIBAL_CHIEF" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "target_source": "/datum/target_source/oview_single_type/tribal_chief", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "long_range_friendship": true, + "forget_target": false + } + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.json b/code/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.json new file mode 100644 index 000000000000..935c577753eb --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.json @@ -0,0 +1,73 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/node_drone", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": true, + "target_key": "BB_CURRENT_HUNTING_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/latch_onto", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "hunt_cooldown": "5 SECONDS" + } + } + ] + } + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/ore_vent", + "targeting_strategy": "/datum/targeting_strategy/ore_vent_unclaimed", + "vision_range": 7 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/node_drone/node_drone.dm b/code/modules/mob/living/basic/lavaland/node_drone/node_drone.dm index 0f1f0962559d..ad2f7b5ea752 100644 --- a/code/modules/mob/living/basic/lavaland/node_drone/node_drone.dm +++ b/code/modules/mob/living/basic/lavaland/node_drone/node_drone.dm @@ -51,6 +51,7 @@ /mob/living/basic/node_drone/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_MINING_AOE_IMMUNE, INNATE_TRAIT) + AddElement(/datum/element/ai_retaliate) /mob/living/basic/node_drone/death(gibbed) . = ..() @@ -146,43 +147,27 @@ return /// The node drone AI controller -// Generally, this is a very simple AI that will try to find a vent and latch onto it, unless attacked by a lavaland mob, who it will try to flee from. /datum/ai_controller/basic_controller/node_drone + behavior_tree_json = "code/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.json" blackboard = list( - BB_BASIC_MOB_FLEEING = FALSE, // Will flee when the vent lies undefended. - BB_CURRENT_HUNTING_TARGET = null, // Hunts for vents. - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, // Use this to find vents to run away from + BB_CURRENT_HUNTING_TARGET = null, + BB_CURRENT_TARGET_HIDING_LOCATION = null, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_FLEE_DISTANCE = 3, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = null - planning_subtrees = list( - // Priority is see if lavaland mobs are attacking us to flee from them. - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - // Fly you fool - /datum/ai_planning_subtree/flee_target/node_drone, - // Otherwise, look for and execute hunts for vents to latch onto. - /datum/ai_planning_subtree/find_and_hunt_target/look_for_vent, - ) - -// Node subtree to hunt down ore vents. -/datum/ai_planning_subtree/find_and_hunt_target/look_for_vent - hunting_behavior = /datum/ai_behavior/hunt_target/latch_onto/node_drone - hunt_targets = list(/obj/structure/ore_vent) - hunt_range = 7 // Hunt vents to the end of the earth. -// node drone behavior for buckling down on a vent. -/datum/ai_behavior/hunt_target/latch_onto/node_drone - hunt_cooldown = 5 SECONDS +/// Validates an ore vent as a valid hunt target: must exist and have no drone already latched. +/datum/targeting_strategy/ore_vent_unclaimed -// Evasion behavior. -/datum/ai_planning_subtree/flee_target/node_drone - flee_behaviour = /datum/ai_behavior/run_away_from_target/drone - -/datum/ai_behavior/run_away_from_target/drone - action_cooldown = 1 SECONDS - run_distance = 3 +/datum/targeting_strategy/ore_vent_unclaimed/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/structure/ore_vent/vent = target + return istype(vent) && isnull(vent.node) #undef FLY_IN_STATE diff --git a/code/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.json b/code/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.json new file mode 100644 index 000000000000..0147a5c5a023 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.json @@ -0,0 +1,108 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/baby_raptor", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "chance": 0.3 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/express_happiness" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "chance": 0.15 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_FOUND_MOM" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_MOM" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/look_to_parent" + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/raptor_find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_FOUND_MOM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mom" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.json b/code/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.json new file mode 100644 index 000000000000..6dd9e84fa56b --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.json @@ -0,0 +1,50 @@ +{ + "dm_type": "/datum/bt_node/subtree/care_for_young", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_RAPTOR_BABY" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.6 + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_RAPTOR_BABY" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_RAPTOR_BABY", + "combat_mode": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "\"grooms its baby!\"" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_RAPTOR_BABY" + } + } + ] + } + } +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.json new file mode 100644 index 000000000000..52a56e06a2e9 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.json @@ -0,0 +1,221 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/raptor", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/raptor_flee" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/raptor_heal_injured" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/raptor_food_trough" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "chance": 0.4 + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/play_with_owner" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/care_for_young" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/raptor_find_food" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "key": "BB_RAPTOR_PLAYFUL" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_OWNER_TARGET", + "targeting_strategy": "/datum/targeting_strategy/ally_mob", + "target_source": "/datum/target_source/oview_single_type/living_mob" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "key": "BB_BASIC_MOB_HEALER" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_INJURED_RAPTOR", + "targeting_strategy": "/datum/targeting_strategy/injured_mob/not_self/injured_raptor" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/retrieve_injured_rider", + "vars": { + "target_key": "BB_INJURED_RAPTOR" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "key": "BB_RAPTOR_MOTHERLY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_RAPTOR_BABY", + "targeting_strategy": "/datum/targeting_strategy/healthy_raptor_baby", + "target_source": "/datum/target_source/oview_raptor_babies" + } + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/express_happiness" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm deleted file mode 100644 index 777a76de1a68..000000000000 --- a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm +++ /dev/null @@ -1,38 +0,0 @@ -/datum/ai_behavior/hunt_target/interact_with_target/heal_raptor - always_reset_target = TRUE - -/datum/ai_behavior/find_hunt_target/injured_raptor - action_cooldown = 7.5 SECONDS - -/datum/ai_behavior/find_hunt_target/injured_raptor/valid_dinner(mob/living/source, mob/living/target, radius) - return (source != target && target.health < target.maxHealth) - -/datum/ai_behavior/find_hunt_target/raptor_baby/valid_dinner(mob/living/source, mob/living/target, radius) - if (!can_see(source, target, radius) || target.stat == DEAD || !istype(target, /mob/living/basic/raptor)) - return FALSE - var/mob/living/basic/raptor/raptor = target - return raptor.growth_stage == RAPTOR_BABY - -/datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off/care_for_young - -/datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off/care_for_young/target_caught(mob/living/hunter, atom/hunted) - hunter.manual_emote("grooms [hunted]!") - return ..() - -/datum/ai_behavior/find_hunt_target/raptor_trough - action_cooldown = 7.5 SECONDS - -/datum/ai_behavior/find_hunt_target/raptor_trough/valid_dinner(mob/living/source, atom/movable/trough, radius) - return !!(locate(/obj/item/stack/ore) in trough.contents) - -/datum/ai_behavior/find_injured_rider/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) - var/mob/living/living_mob = controller.pawn - if (!length(living_mob.buckled_mobs) || !isliving(living_mob.buckled_mobs[1])) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/rider = living_mob.buckled_mobs[1] - if (rider.stat == CONSCIOUS || rider.stat == DEAD || rider.health >= rider.maxHealth) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(hunting_target_key, rider) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_controller.dm b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_controller.dm index bae8bd0d2cdc..91cd704332ba 100644 --- a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_controller.dm +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_controller.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/raptor + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.json" blackboard = list( BB_INTERACTIONS_WITH_OWNER = list( "pecks", @@ -11,31 +12,13 @@ BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/raptor), BB_MAX_CHILDREN = 5, BB_RAPTOR_FLEE_THRESHOLD = 0.25, + BB_FUCKS = TRUE ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/find_and_hunt_target/heal_rider, - /datum/ai_planning_subtree/find_and_hunt_target/heal_raptors, - /datum/ai_planning_subtree/random_speech/blackboard, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/raptor_trough, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/find_and_hunt_target/care_for_young, - /datum/ai_planning_subtree/make_babies, - /datum/ai_planning_subtree/express_happiness, - /datum/ai_planning_subtree/find_and_hunt_target/play_with_owner/raptor, - ) /// Angry raptors with no faction check on retaliation /datum/ai_controller/basic_controller/raptor/aggressive + ai_movement = /datum/ai_movement/basic_avoidance blackboard = list( BB_INTERACTIONS_WITH_OWNER = list( "pecks", @@ -49,24 +32,7 @@ BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/raptor), BB_MAX_CHILDREN = 5, BB_RAPTOR_FLEE_THRESHOLD = 0.1, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/find_and_hunt_target/heal_raptors, - /datum/ai_planning_subtree/random_speech/blackboard, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target/hunt, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/raptor_trough, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/find_and_hunt_target/care_for_young, - /datum/ai_planning_subtree/make_babies, - /datum/ai_planning_subtree/express_happiness, - /datum/ai_planning_subtree/find_and_hunt_target/play_with_owner/raptor, + BB_FUCKS = TRUE ) /datum/ai_controller/basic_controller/raptor/TryPossessPawn(atom/new_pawn) @@ -84,24 +50,11 @@ SIGNAL_HANDLER REMOVE_TRAIT(pawn, TRAIT_MOB_DIFFICULT_TO_MOUNT, REF(src)) -/datum/ai_controller/basic_controller/raptor/on_mob_eat() - . = ..() - clear_blackboard_key(BB_RAPTOR_TROUGH_TARGET) - /datum/ai_controller/basic_controller/baby_raptor + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FIND_MOM_TYPES = list(/mob/living/basic/raptor), + BB_IGNORE_MOM_TYPES = list(/mob/living/basic/raptor/baby) ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/random_speech/blackboard, - /datum/ai_planning_subtree/find_and_hunt_target/raptor_trough, - /datum/ai_planning_subtree/express_happiness, - /datum/ai_planning_subtree/look_for_adult/raptor, - ) diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_subtrees.dm b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_subtrees.dm index 66f089240236..6d05cdeb4a36 100644 --- a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_subtrees.dm +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_subtrees.dm @@ -1,73 +1,18 @@ -/datum/ai_planning_subtree/find_and_hunt_target/heal_raptors - target_key = BB_INJURED_RAPTOR - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/heal_raptor - finding_behavior = /datum/ai_behavior/find_hunt_target/injured_raptor - hunt_targets = list(/mob/living/basic/raptor) - hunt_chance = 70 - hunt_range = 9 +/datum/bt_node/subtree/raptor_flee + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.json" -/datum/ai_planning_subtree/find_and_hunt_target/heal_raptors/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_BASIC_MOB_HEALER]) - return - return ..() +/datum/bt_node/subtree/raptor_heal_injured + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.json" -/datum/ai_planning_subtree/find_and_hunt_target/heal_rider - target_key = BB_INJURED_RAPTOR - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/heal_raptor - finding_behavior = /datum/ai_behavior/find_injured_rider - hunt_targets = list(/mob/living/carbon/human) +/datum/bt_node/subtree/raptor_food_trough + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.json" -/datum/ai_planning_subtree/find_and_hunt_target/heal_rider/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_BASIC_MOB_HEALER]) - return - return ..() +/datum/bt_node/subtree/raptor_find_food + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.json" -/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor - target_key = BB_BASIC_MOB_FLEE_TARGET +/datum/bt_node/subtree/care_for_young + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.json" -/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_RAPTOR_COWARD]) - return ..() - var/mob/living/basic/raptor/raptor = controller.pawn - if (raptor.health <= raptor.maxHealth * controller.blackboard[BB_RAPTOR_FLEE_THRESHOLD]) - return ..() - - if (!length(raptor.buckled_mobs)) - return - - var/mob/living/buckled_to = raptor.buckled_mobs[1] - // Flee if our owner is badly injured and out - if (buckled_to.stat != CONSCIOUS && buckled_to.stat != DEAD) - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/care_for_young - target_key = BB_RAPTOR_BABY - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off/care_for_young - finding_behavior = /datum/ai_behavior/find_hunt_target/raptor_baby - hunt_targets = list(/mob/living/basic/raptor) - hunt_chance = 75 - hunt_range = 9 - -/datum/ai_planning_subtree/find_and_hunt_target/care_for_young/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_RAPTOR_MOTHERLY]) - return - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/raptor_trough - target_key = BB_RAPTOR_TROUGH_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off - finding_behavior = /datum/ai_behavior/find_hunt_target/raptor_trough - hunt_targets = list(/obj/structure/ore_container/food_trough/raptor_trough) - hunt_chance = 80 - hunt_range = 9 - -/datum/ai_planning_subtree/find_and_hunt_target/raptor_trough/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(world.time < controller.blackboard[BB_NEXT_FOOD_EAT]) - return - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/play_with_owner/raptor/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_RAPTOR_PLAYFUL]) - return - return ..() +/datum/bt_node/subtree/play_with_owner + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.json" diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.json new file mode 100644 index 000000000000..1912562fe829 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.json @@ -0,0 +1,31 @@ +{ + "dm_type": "/datum/bt_node/subtree/raptor_find_food", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "cooldown_key": "BB_NEXT_EAT_FOOD" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": false, + "finish_on_primary": false, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_RAPTOR_TROUGH_TARGET", + "targeting_strategy": "/datum/targeting_strategy/raptor_trough", + "target_source": "/datum/target_source/oview_single_type/raptor_trough" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.json new file mode 100644 index 000000000000..182ead097935 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.json @@ -0,0 +1,41 @@ +{ + "dm_type": "/datum/bt_node/subtree/raptor_flee", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_RAPTOR_COWARDLY" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_health_below", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "health_blackboard_key": "BB_RAPTOR_FLEE_THRESHOLD" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/check_rider_stat", + "vars": { + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.json new file mode 100644 index 000000000000..84e80f387c42 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.json @@ -0,0 +1,36 @@ +{ + "dm_type": "/datum/bt_node/subtree/raptor_heal_injured", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_INJURED_RAPTOR" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_INJURED_RAPTOR" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_INJURED_RAPTOR", + "combat_mode": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_INJURED_RAPTOR" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.json new file mode 100644 index 000000000000..3293e410e7c4 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/raptor/aggressive", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_OWNER_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_OWNER_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/play_with_owner", + "vars": { + "cooldown_key": "BB_OWNER_TARGET" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.json new file mode 100644 index 000000000000..4c988ea6a27d --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.json @@ -0,0 +1,36 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/baby_raptor", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_RAPTOR_TROUGH_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_RAPTOR_TROUGH_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_RAPTOR_TROUGH_TARGET", + "combat_mode": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_RAPTOR_TROUGH_TARGET" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/tendril/tendril.bt.json b/code/modules/mob/living/basic/lavaland/tendril/tendril.bt.json new file mode 100644 index 000000000000..63f0c0769583 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/tendril/tendril.bt.json @@ -0,0 +1,124 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/tendril", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": false, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 7 + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_TENDRIL_SPIKES" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_TENDRIL_LASH" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TENDRIL_CHASER", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": true, + "vision_range": 5 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "target_loss_distance": 9, + "vision_range": 5 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/tendril/tendril.dm b/code/modules/mob/living/basic/lavaland/tendril/tendril.dm index d7758e6048c3..1c2766e706f0 100644 --- a/code/modules/mob/living/basic/lavaland/tendril/tendril.dm +++ b/code/modules/mob/living/basic/lavaland/tendril/tendril.dm @@ -132,7 +132,7 @@ GLOBAL_LIST_INIT(tendrils, list()) return var/beat_rate = HEARTBEAT_NORMAL - if (ai_controller?.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + if (ai_controller?.blackboard[BB_CURRENT_TARGET]) beat_rate = round(HEARTBEAT_FRANTIC + health / maxHealth * (HEARTBEAT_FAST - HEARTBEAT_FRANTIC), 0.05 SECONDS) if (beat_rate != soundloop.mid_length) diff --git a/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm b/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm index 5da4fcedf285..db605e350dae 100644 --- a/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm +++ b/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm @@ -1,40 +1,7 @@ /datum/ai_controller/basic_controller/tendril + behavior_tree_json = "code/modules/mob/living/basic/lavaland/tendril/tendril.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, BB_AGGRO_RANGE = 9, // Keeps an eye on you even if you flee - BB_AGGRO_GRAB_RANGE = 5, // Only aggros if you get real close and personal ) - - planning_subtrees = list( - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/tendril_chaser, - /datum/ai_planning_subtree/use_mob_ability/tendril_spikes, - /datum/ai_planning_subtree/use_mob_ability/tendril_lash, - ) - -/datum/ai_planning_subtree/targeted_mob_ability/tendril_chaser - ability_key = BB_TENDRIL_CHASER - operational_datums = list(/datum/component/ai_target_timer) - finish_planning = FALSE - -/datum/ai_planning_subtree/use_mob_ability/tendril_lash - ability_key = BB_TENDRIL_LASH - -/datum/ai_planning_subtree/use_mob_ability/tendril_lash/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (isnull(target) || get_dist(controller.pawn, target) > /obj/projectile/tentacle_lash::range) - return FALSE - return ..() - -/datum/ai_planning_subtree/use_mob_ability/tendril_spikes - ability_key = BB_TENDRIL_SPIKES - -/datum/ai_planning_subtree/use_mob_ability/tendril_spikes/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - var/datum/action/cooldown/mob_cooldown/tendril_cross_spikes/ability = controller.blackboard[ability_key] - if (isnull(target) || !istype(ability) || get_dist(controller.pawn, target) > ability.spike_range) - return FALSE - return ..() diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher.bt.json b/code/modules/mob/living/basic/lavaland/watcher/watcher.bt.json new file mode 100644 index 000000000000..b408d011c4db --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher.bt.json @@ -0,0 +1,133 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/watcher", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_trait", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": true, + "key": "TRAIT_OVERWATCHED" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_at_least", + "vars": { + "key": "BB_BASIC_MOB_HAS_TARGET_TIME", + "minimum": "5 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "max_range": 9 + } + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm index cd45eb9c6ecb..637f0f78bb92 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/watcher + behavior_tree_json = "code/modules/mob/living/basic/lavaland/watcher/watcher.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_PRIORITY_STRATEGY = /datum/target_priority_strategy/mining, @@ -7,34 +8,3 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/use_mob_ability/gaze, - /datum/ai_planning_subtree/ranged_skirmish/watcher, - ) - -/datum/ai_planning_subtree/use_mob_ability/gaze - finish_planning = TRUE - -/datum/ai_planning_subtree/use_mob_ability/gaze/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (!isliving(target)) - return // Don't do this if there's nothing hostile around or if our target is a mech - var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 - if (time_on_target < 5 SECONDS) - return // We need to spend some time acquiring our target first - return ..() - -/datum/ai_planning_subtree/ranged_skirmish/watcher - min_range = 0 - -/datum/ai_planning_subtree/ranged_skirmish/watcher/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (QDELETED(target) || HAS_TRAIT(target, TRAIT_OVERWATCHED)) - return // Don't bully people who are playing red light green light - return ..() diff --git a/code/modules/mob/living/basic/minebots/minebot.bt.json b/code/modules/mob/living/basic/minebots/minebot.bt.json new file mode 100644 index 000000000000..1e27d18584a6 --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot.bt.json @@ -0,0 +1,107 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/minebot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/minebot_combat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/minebot_mining" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MINER_FRIEND", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/rock_stoner" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_MINER_FRIEND", + "long_range_friendship": true + } + } + ] + }, + { + "type": "sequence", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_MINEBOT_CRIT_ALERT_COOLDOWN", + "cooldown_duration": "10 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_NEARBY_DEAD_MINER", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/unconscious_human" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/send_sos_message", + "vars": { + "target_key": "BB_NEARBY_DEAD_MINER" + } + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/minebots/minebot.dm b/code/modules/mob/living/basic/minebots/minebot.dm index 525f1450141b..1b4a15791870 100644 --- a/code/modules/mob/living/basic/minebots/minebot.dm +++ b/code/modules/mob/living/basic/minebots/minebot.dm @@ -110,7 +110,7 @@ for(var/obj/item/borg/upgrade/modkit/modkit as anything in stored_gun.modkits) . += span_notice("There is \a [modkit] installed, using [modkit.cost]% capacity.") - if(ai_controller && ai_controller.ai_status == AI_STATUS_IDLE) + if(ai_controller && ai_controller.ai_status == AI_STATUS_OFF && ai_controller.get_expected_ai_status() == AI_STATUS_ON) . += "The [src] appears to be in sleep mode. You can restore normal functions by tapping it." @@ -137,8 +137,8 @@ /mob/living/basic/mining_drone/attack_hand(mob/living/carbon/human/user, list/modifiers) if(!user.combat_mode) - if(ai_controller && ai_controller.ai_status == AI_STATUS_IDLE) - ai_controller.set_ai_status(AI_STATUS_ON) + if(ai_controller && ai_controller.ai_status == AI_STATUS_OFF) + ai_controller.reset_ai_status() //wakes a performance-slept bot, no-op if it is off for a real reason if(LAZYACCESS(modifiers, LEFT_CLICK)) //Lets Right Click be specifically for re-enabling their AI (and avoiding the UI popup), while Left Click simply does both. ui_interact(user) return diff --git a/code/modules/mob/living/basic/minebots/minebot_ai.dm b/code/modules/mob/living/basic/minebots/minebot_ai.dm index 53895358d2a6..fdb8e308079c 100644 --- a/code/modules/mob/living/basic/minebots/minebot_ai.dm +++ b/code/modules/mob/living/basic/minebots/minebot_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/minebot + behavior_tree_json = "code/modules/mob/living/basic/minebots/minebot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -19,110 +20,60 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/launch_missiles, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/befriend_miners, - /datum/ai_planning_subtree/defend_node, - /datum/ai_planning_subtree/minebot_maintain_distance, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot, - /datum/ai_planning_subtree/minebot_mining, - /datum/ai_planning_subtree/locate_dead_humans, - ) - -/datum/ai_planning_subtree/launch_missiles - -/datum/ai_planning_subtree/launch_missiles/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/datum/action/cooldown/missile_ability = controller.blackboard[BB_MINEBOT_MISSILE_ABILITY] - if(!missile_ability?.IsAvailable()) - return - if(!controller.blackboard_key_exists(BB_MINEBOT_MISSILE_TARGET)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/clear_bombing_zone, BB_MINEBOT_MISSILE_TARGET, /obj/effect/temp_visual/minebot_target, 7) - return - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target, BB_MINEBOT_MISSILE_ABILITY, BB_MINEBOT_MISSILE_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/find_and_set/clear_bombing_zone - -/datum/ai_behavior/find_and_set/clear_bombing_zone/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/obj/effect/temp_visual/minebot_target/target in oview(search_range, controller.pawn)) - if(isclosedturf(get_turf(target))) - continue - return target - return null -/datum/ai_planning_subtree/befriend_miners/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_MINER_FRIEND)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/miner_to_befriend, BB_MINER_FRIEND) - return - controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_MINER_FRIEND) +/datum/bt_node/subtree/minebot_combat + behavior_tree_json = "code/modules/mob/living/basic/minebots/minebot_combat.bt.json" -/datum/ai_behavior/find_and_set/miner_to_befriend +/datum/bt_node/subtree/minebot_mining + behavior_tree_json = "code/modules/mob/living/basic/minebots/minebot_mining.bt.json" -/datum/ai_behavior/find_and_set/miner_to_befriend/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) - if(HAS_TRAIT(target, TRAIT_ROCK_STONER)) - return target - return null +/// Mineral wall finder that skips turfs in the blacklist and the previously unreachable wall. +/datum/bt_node/ai_behavior/find_mineral_wall/minebot -/datum/ai_planning_subtree/defend_node/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[BB_DRONE_DEFEND] - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_DRONE_DEFEND, /mob/living/basic/node_drone) - return - var/mob/living/living_pawn = controller.pawn - if(!living_pawn.has_ally(target)) - controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_DRONE_DEFEND) - return - if(target.health < (target.maxHealth * 0.75) && controller.blackboard[BB_MINEBOT_REPAIR_DRONE]) - controller.queue_behavior(/datum/ai_behavior/repair_drone, BB_DRONE_DEFEND) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/find_mineral_wall/minebot/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/list/forbidden = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] + var/turf/previous_unreachable = controller.blackboard[BB_PREVIOUS_UNREACHABLE_WALL] + if(is_type_in_list(target_wall, forbidden) || target_wall == previous_unreachable) + return FALSE + controller.clear_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL) + return ..() -/datum/ai_behavior/repair_drone - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH +/// Mines a mineral turf at range using RangedAttack rather than ai_interact. +/datum/bt_node/ai_behavior/minebot_mine_turf + time_between_perform = 3 SECONDS + var/target_key = BB_TARGET_MINERAL_TURF -/datum/ai_behavior/repair_drone/setup(datum/ai_controller/controller, target_key) - . = ..() +/datum/bt_node/ai_behavior/minebot_mine_turf/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/living_pawn = controller.pawn var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/repair_drone/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] if(QDELETED(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/living_pawn = controller.pawn - living_pawn.say("REPAIRING [target]!") - controller.ai_interact(target = target) + if(check_obstacles_in_path(controller, target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!living_pawn.combat_mode) + living_pawn.set_combat_mode(TRUE) + living_pawn.RangedAttack(target) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/repair_drone/finish_action(datum/ai_controller/controller, success, target_key) +/datum/bt_node/ai_behavior/minebot_mine_turf/proc/check_obstacles_in_path(datum/ai_controller/controller, turf/target) + var/mob/living/source = controller.pawn + var/list/turfs_in_path = get_line(source, target) - target + for(var/turf/turf in turfs_in_path) + if(turf.is_blocked_turf(ignore_atoms = list(source))) + controller.set_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL, target) + return TRUE + return FALSE + +/datum/bt_node/ai_behavior/minebot_mine_turf/finish_action(datum/ai_controller/controller, succeeded) . = ..() - if(!success) - controller.clear_blackboard_key(target_key) - -///find dead humans and report their location on the radio -/datum/ai_planning_subtree/locate_dead_humans/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_NEARBY_DEAD_MINER)) - controller.queue_behavior(/datum/ai_behavior/send_sos_message, BB_NEARBY_DEAD_MINER) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_and_set/unconscious_human, BB_NEARBY_DEAD_MINER, /mob/living/carbon/human) - -/datum/ai_behavior/find_and_set/unconscious_human/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) - if(target.stat >= UNCONSCIOUS && target.mind) - return target - return null - -/datum/ai_behavior/send_sos_message - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = 2 MINUTES - -/datum/ai_behavior/send_sos_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + controller.clear_blackboard_key(target_key) + +/// Sends a radio SOS message for a dead or unconscious miner. Clears the target key on finish. +/datum/bt_node/ai_behavior/send_sos_message + time_between_perform = 2 MINUTES + var/target_key = BB_NEARBY_DEAD_MINER + +/datum/bt_node/ai_behavior/send_sos_message/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/carbon/target = controller.blackboard[target_key] var/mob/living/living_pawn = controller.pawn if(QDELETED(target) || is_station_level(target.z)) @@ -135,140 +86,85 @@ radio_implant.radio.talk_into(living_pawn, message, RADIO_CHANNEL_SUPPLY) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/send_sos_message/finish_action(datum/ai_controller/controller, success, target_key) +/datum/bt_node/ai_behavior/send_sos_message/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) -///operational datums is null because we dont use a ranged component, we use a gun in our contents -/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot - operational_datums = null - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/minebot - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) - return - var/mob/living/living_pawn = controller.pawn - if(!living_pawn.combat_mode) //we are not on attack mode - return - controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_planning_subtree/minebot_maintain_distance/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) - return - var/mob/living/living_pawn = controller.pawn - if(get_dist(living_pawn, target) <= controller.blackboard[BB_MINIMUM_SHOOTING_DISTANCE]) - controller.queue_behavior(/datum/ai_behavior/run_away_from_target/run_and_shoot/minebot, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/run_away_from_target/run_and_shoot/minebot +/// Moves adjacent to an allied node drone and repairs it if its health is below the threshold. +/// Fails if the drone is healthy, not allied, or repair is disabled. +/datum/bt_node/ai_behavior/repair_drone + var/target_key = BB_DRONE_DEFEND + var/repair_threshold = 0.75 -/datum/ai_behavior/run_away_from_target/run_and_shoot/minebot/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) - if(!controller.blackboard[BB_MINEBOT_PLANT_MINES]) - return ..() - var/datum/action/cooldown/mine_ability = controller.blackboard[BB_MINEBOT_LANDMINE_ABILITY] - mine_ability?.Trigger() - return ..() - -/datum/ai_behavior/basic_ranged_attack/minebot - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - avoid_friendly_fire = TRUE - ///if our target is closer than this distance, finish action - var/minimum_distance = 3 - -/datum/ai_behavior/basic_ranged_attack/minebot/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - minimum_distance = controller.blackboard[BB_MINIMUM_SHOOTING_DISTANCE] ? controller.blackboard[BB_MINIMUM_SHOOTING_DISTANCE] : initial(minimum_distance) - var/atom/target = controller.blackboard[target_key] +/datum/bt_node/ai_behavior/repair_drone/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] if(QDELETED(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!controller.blackboard[BB_MINEBOT_REPAIR_DRONE]) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/mob/living/living_pawn = controller.pawn - if(get_dist(living_pawn, target) <= minimum_distance) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -///mine walls if we are on automated mining mode -/datum/ai_planning_subtree/minebot_mining/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_AUTOMATED_MINING]) - return - if(controller.blackboard_key_exists(BB_TARGET_MINERAL_TURF)) - controller.queue_behavior(/datum/ai_behavior/minebot_mine_turf, BB_TARGET_MINERAL_TURF) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_mineral_wall/minebot, BB_TARGET_MINERAL_TURF) - -/datum/ai_behavior/find_mineral_wall/minebot - -/datum/ai_behavior/find_mineral_wall/minebot/check_if_mineable(datum/ai_controller/controller, turf/target_wall) - var/list/forbidden_turfs = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] - var/turf/previous_unreachable_wall = controller.blackboard[BB_PREVIOUS_UNREACHABLE_WALL] - if(is_type_in_list(target_wall, forbidden_turfs) || target_wall == previous_unreachable_wall) - return FALSE - controller.clear_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL) - return ..() - -/datum/ai_behavior/minebot_mine_turf - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 2 - action_cooldown = 3 SECONDS - -/datum/ai_behavior/minebot_mine_turf/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/minebot_mine_turf/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/basic/living_pawn = controller.pawn - var/turf/target = controller.blackboard[target_key] - - if(QDELETED(target)) + if(!living_pawn.has_ally(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(check_obstacles_in_path(controller, target)) + if(target.health >= target.maxHealth * repair_threshold) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), "REPAIRING [target]!") + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if(!living_pawn.combat_mode) - living_pawn.set_combat_mode(TRUE) +/// Turns off combat mode then interacts with a nearby ore to collect it. Clears the target key on finish. +/datum/bt_node/ai_behavior/collect_ore/minebot + var/target_key = BB_ORE_TARGET - living_pawn.RangedAttack(target) +/datum/bt_node/ai_behavior/collect_ore/minebot/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/stack/ore/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/living_pawn = controller.pawn + if(living_pawn.combat_mode) + living_pawn.set_combat_mode(FALSE) + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/minebot_mine_turf/proc/check_obstacles_in_path(datum/ai_controller/controller, turf/target) - var/mob/living/source = controller.pawn - var/list/turfs_in_path = get_line(source, target) - target - for(var/turf/turf in turfs_in_path) - if(turf.is_blocked_turf(ignore_atoms = list(source))) - controller.set_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL, target) - return TRUE - return FALSE - -/datum/ai_behavior/minebot_mine_turf/finish_action(datum/ai_controller/controller, success, target_key) +/datum/bt_node/ai_behavior/collect_ore/minebot/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) -///store ores in our body -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/consume_ores/minebot - hunt_chance = 100 +/// befriend_target variant that fails immediately if the target is already an ally used to gate the drone-defend block. +/datum/bt_node/ai_behavior/befriend_target/check_ally -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/automated_mining = controller.blackboard[BB_AUTOMATED_MINING] +/datum/bt_node/ai_behavior/befriend_target/check_ally/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target) || living_pawn.has_ally(living_target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return ..() - if(!automated_mining && living_pawn.combat_mode) //are we not on automated mining or collect mode? - return +/// BT-native ranged attack for the minebot. Avoids friendly fire. +/datum/bt_node/ai_behavior/basic_ranged_attack/minebot + avoid_friendly_fire = TRUE - return ..() +/// Accepts humans with TRAIT_ROCK_STONER (miners). +/datum/targeting_strategy/rock_stoner -/datum/ai_behavior/hunt_target/interact_with_target/consume_ores/minebot - hunt_cooldown = 2 SECONDS +/datum/targeting_strategy/rock_stoner/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!istype(target, /mob/living/carbon/human)) + return FALSE + return HAS_TRAIT(target, TRAIT_ROCK_STONER) -/datum/ai_behavior/hunt_target/interact_with_target/consume_ores/minebot/target_caught(mob/living/hunter, obj/item/stack/ore/hunted) - if(hunter.combat_mode) - hunter.set_combat_mode(FALSE) - return ..() +/// Accepts unconscious or dead humans that have a mind (i.e., real players/NPCs that need SOS). +/datum/targeting_strategy/unconscious_human + +/datum/targeting_strategy/unconscious_human/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!istype(target, /mob/living/carbon/human)) + return FALSE + var/mob/living/carbon/human/human_target = target + return human_target.stat >= UNCONSCIOUS && human_target.mind ///pet commands /datum/pet_command/free/minebot @@ -305,9 +201,8 @@ var/datum/action/cooldown/ability = controller.blackboard[ability_key] if(!ability?.IsAvailable()) return - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, ability_key) + INVOKE_ASYNC(ability, TYPE_PROC_REF(/datum/action, Trigger)) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING /datum/pet_command/minebot_ability/light command_name = "Toggle lights" @@ -329,8 +224,12 @@ /datum/pet_command/minebot_ability/dump/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to dump its ore!" +/datum/pet_command/minebot_ability/dump/execute_action(datum/ai_controller/controller) + controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) //else bro will just pick it up + return ..() + /datum/pet_command/attack/minebot - attack_behaviour = /datum/ai_behavior/basic_ranged_attack/minebot + attack_subtree = /datum/bt_node/subtree/pet_command/attack/minebot /datum/pet_command/attack/minebot/execute_action(datum/ai_controller/controller) controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) @@ -350,8 +249,8 @@ /datum/pet_command/protect_owner/minebot/set_command_target(mob/living/parent, atom/target) if(!parent.ai_controller.blackboard[BB_MINEBOT_AUTO_DEFEND]) return FALSE - if(!parent.ai_controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) && !QDELETED(target)) //we are already dealing with something, - parent.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target) + if(!parent.ai_controller.blackboard_key_exists(BB_CURRENT_TARGET) && !QDELETED(target)) //we are already dealing with something, + parent.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, target) return TRUE /datum/pet_command/protect_owner/minebot/execute_action(datum/ai_controller/controller) @@ -359,5 +258,3 @@ var/mob/living/living_pawn = controller.pawn living_pawn.set_combat_mode(TRUE) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - - diff --git a/code/modules/mob/living/basic/minebots/minebot_combat.bt.json b/code/modules/mob/living/basic/minebots/minebot_combat.bt.json new file mode 100644 index 000000000000..7a8e60c9fd19 --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot_combat.bt.json @@ -0,0 +1,112 @@ +{ + "dm_type": "/datum/bt_node/subtree/minebot_combat", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_MINEBOT_MISSILE_ABILITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_MINEBOT_MISSILE_ABILITY", + "target_key": "BB_MINEBOT_MISSILE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/minebot", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "min_dist_key": "BB_MINIMUM_SHOOTING_DISTANCE", + "max_dist_key": "BB_MINIMUM_SHOOTING_DISTANCE" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MINEBOT_MISSILE_TARGET", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MINEBOT_MISSILE_TARGET", + "target_source": "/datum/target_source/oview_single_type/minebot_target", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 7 + } + } + } + ] + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": false, + "key": "BB_MINEBOT_LANDMINE_ABILITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_MINEBOT_LANDMINE_ABILITY" + } + } + } + ] + } + ] + } +} diff --git a/code/modules/mob/living/basic/minebots/minebot_mining.bt.json b/code/modules/mob/living/basic/minebots/minebot_mining.bt.json new file mode 100644 index 000000000000..e8ad56080db2 --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot_mining.bt.json @@ -0,0 +1,193 @@ +{ + "dm_type": "/datum/bt_node/subtree/minebot_mining", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_MINEBOT_REPAIR_DRONE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DRONE_DEFEND" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_health_below_fraction", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DRONE_DEFEND", + "fraction": 0.75 + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DRONE_DEFEND", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/repair_drone", + "vars": { + "target_key": "BB_DRONE_DEFEND" + } + } + ] + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target/check_ally", + "vars": { + "target_key": "BB_DRONE_DEFEND", + "long_range_friendship": true, + "forget_target": false + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_ORE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ORE_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/collect_ore/minebot", + "vars": { + "target_key": "BB_ORE_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_AUTOMATED_MINING" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TARGET_MINERAL_TURF" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_MINERAL_TURF", + "required_dist": 2, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/minebot_mine_turf", + "vars": { + "target_key": "BB_TARGET_MINERAL_TURF" + } + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "time_between_perform": "5 SECONDS", + "target_key": "BB_DRONE_DEFEND", + "targeting_strategy": "/datum/targeting_strategy/anything", + "target_source": "/datum/target_source/oview_single_type/node_drone", + "vision_range": 9, + "revalidation_mode": "TARGET_REVALIDATE" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_AUTOMATED_MINING" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_ORE_TARGET", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_single_type/ore", + "vision_range": 2 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mineral_wall/minebot", + "vars": { + "target_key": "BB_TARGET_MINERAL_TURF" + } + } + ] + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/bread_cat_ai.dm b/code/modules/mob/living/basic/pets/cat/bread_cat_ai.dm deleted file mode 100644 index 655a6431fc2a..000000000000 --- a/code/modules/mob/living/basic/pets/cat/bread_cat_ai.dm +++ /dev/null @@ -1,62 +0,0 @@ -/datum/ai_controller/basic_controller/cat/bread - planning_subtrees = list( - /datum/ai_planning_subtree/find_and_hunt_target/turn_off_stove, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_mice, - /datum/ai_planning_subtree/find_and_hunt_target/find_cat_food, - /datum/ai_planning_subtree/haul_food_to_young, - /datum/ai_planning_subtree/random_speech/cats, - ) - -/datum/ai_planning_subtree/find_and_hunt_target/turn_off_stove - target_key = BB_STOVE_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/stove_target - finding_behavior = /datum/ai_behavior/find_hunt_target/stove - hunt_targets = list(/obj/machinery/oven/range) - hunt_range = 9 - -/datum/ai_behavior/find_hunt_target/stove - -/datum/ai_behavior/find_hunt_target/stove/valid_dinner(mob/living/source, obj/machinery/oven/range/stove, radius) - if(!length(stove.used_tray?.contents) || stove.open) - return FALSE - //something in there is still baking... - for(var/atom/baking in stove.used_tray) - if(HAS_TRAIT(baking, TRAIT_BAKEABLE)) - return FALSE - return TRUE - -/datum/ai_behavior/hunt_target/interact_with_target/stove_target - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/interact_with_target/stove_target/target_caught(mob/living/hunter, obj/machinery/oven/range/stove) - if(stove.open) - return - return ..() - -/datum/ai_controller/basic_controller/cat/cake - planning_subtrees = list( - /datum/ai_planning_subtree/find_and_hunt_target/turn_off_stove, - /datum/ai_planning_subtree/find_and_hunt_target/decorate_donuts, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_mice, - /datum/ai_planning_subtree/find_and_hunt_target/find_cat_food, - /datum/ai_planning_subtree/haul_food_to_young, - /datum/ai_planning_subtree/random_speech/cats, - ) - -/datum/ai_planning_subtree/find_and_hunt_target/decorate_donuts - target_key = BB_DONUT_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/decorate_donuts - finding_behavior = /datum/ai_behavior/find_hunt_target/decorate_donuts - hunt_targets = list(/obj/item/food/donut) - hunt_range = 9 - -/datum/ai_behavior/find_hunt_target/decorate_donuts/valid_dinner(mob/living/source, obj/item/food/donut/target, radius) - if(!target.is_decorated) - return FALSE - return can_see(source, target, radius) - -/datum/ai_behavior/hunt_target/decorate_donuts - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/decorate_donuts/target_caught(mob/living/hunter, atom/target) - hunter.spin(spintime = 4, speed = 1) diff --git a/code/modules/mob/living/basic/pets/cat/cat.bt.json b/code/modules/mob/living/basic/pets/cat/cat.bt.json new file mode 100644 index 000000000000..b8bb0004a3a6 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat.bt.json @@ -0,0 +1,275 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cat", + "type": "selector", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_FLEE_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_BASIC_MOB_FLEE_TARGET" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_reside_in_home" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_haul_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_hunt_mice" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_TRESSPASSER_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TRESSPASSER_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/territorial_struggle", + "vars": { + "target_key": "BB_TRESSPASSER_TARGET", + "cries_key": "BB_HOSTILE_MEOWS" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "invert": true, + "key": "BB_MOUSE_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/cats" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CAT_HOME", + "target_source": "/datum/target_source/oview_single_type/cat_house", + "targeting_strategy": "/datum/targeting_strategy/valid_cat_home" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_cat_tresspasser", + "vars": { + "target_key": "BB_TRESSPASSER_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOUSE_TARGET", + "target_source": "/datum/target_source/oview_single_type/mouse", + "targeting_strategy": "/datum/targeting_strategy/huntable_mouse", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/cat_food", + "vision_range": 9 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_ai.dm b/code/modules/mob/living/basic/pets/cat/cat_ai.dm index 1150d17f96da..faf465f186a6 100644 --- a/code/modules/mob/living/basic/pets/cat/cat_ai.dm +++ b/code/modules/mob/living/basic/pets/cat/cat_ai.dm @@ -5,288 +5,24 @@ BB_HOSTILE_MEOWS = list("Mawwww", "Mrewwww", "mhhhhng..."), BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/pet/cat), BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/cat/kitten), + BB_FUCKS = TRUE, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/reside_in_home, - /datum/ai_planning_subtree/flee_target/from_flee_key/cat_struggle, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_mice, - /datum/ai_planning_subtree/find_and_hunt_target/find_cat_food, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/haul_food_to_young, - /datum/ai_planning_subtree/territorial_struggle, - /datum/ai_planning_subtree/make_babies, - /datum/ai_planning_subtree/random_speech/cats, - ) - -/datum/ai_planning_subtree/reside_in_home - ///chance we enter our home - var/reside_chance = 5 - ///chance we leave our home - var/leave_home_chance = 15 - -/datum/ai_planning_subtree/reside_in_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - - if(controller.blackboard_key_exists(BB_CAT_HOME)) - controller.queue_behavior(/datum/ai_behavior/enter_cat_home, BB_CAT_HOME) - return - - if(istype(living_pawn.loc, /obj/structure/cat_house)) - if(SPT_PROB(leave_home_chance, seconds_per_tick)) - controller.set_blackboard_key(BB_CAT_HOME, living_pawn.loc) - return SUBTREE_RETURN_FINISH_PLANNING - - if(SPT_PROB(reside_chance, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_home, BB_CAT_HOME, /obj/structure/cat_house) - -/datum/ai_behavior/find_and_set/valid_home/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/obj/structure/cat_house/home in oview(search_range, controller.pawn)) - if(home.resident_cat) - continue - return home - - return null - -/datum/ai_behavior/enter_cat_home - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/enter_cat_home/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/enter_cat_home/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/obj/structure/cat_house/home = controller.blackboard[target_key] - var/mob/living/basic/living_pawn = controller.pawn - if(living_pawn == home.resident_cat || isnull(home.resident_cat)) - controller.ai_interact(target = home) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/enter_cat_home/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_planning_subtree/flee_target/from_flee_key/cat_struggle - flee_behaviour = /datum/ai_behavior/run_away_from_target/cat_struggle - -/datum/ai_behavior/run_away_from_target/cat_struggle - clear_failed_targets = TRUE - -/datum/ai_planning_subtree/territorial_struggle - ///chance we become hostile to another cat - var/hostility_chance = 5 - -/datum/ai_planning_subtree/territorial_struggle/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.gender != MALE || !SPT_PROB(hostility_chance, seconds_per_tick)) - return - if(controller.blackboard_key_exists(BB_TRESSPASSER_TARGET)) - controller.queue_behavior(/datum/ai_behavior/territorial_struggle, BB_TRESSPASSER_TARGET, BB_HOSTILE_MEOWS) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/cat_tresspasser, BB_TRESSPASSER_TARGET, /mob/living/basic/pet/cat) - -/datum/ai_behavior/find_and_set/cat_tresspasser/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/ignore_types = controller.blackboard[BB_BABIES_CHILD_TYPES] - for(var/mob/living/basic/pet/cat/potential_enemy in oview(search_range, controller.pawn)) - if(potential_enemy.gender != MALE) - continue - if(is_type_in_list(potential_enemy, ignore_types)) - continue - var/datum/ai_controller/basic_controller/enemy_controller = potential_enemy.ai_controller - if(isnull(enemy_controller)) - continue - //theyre already engaged in a battle, leave them alone! - if(enemy_controller.blackboard_key_exists(BB_TRESSPASSER_TARGET)) - continue - //u choose me and i choose u - enemy_controller.set_blackboard_key(BB_TRESSPASSER_TARGET, controller.pawn) - return potential_enemy - return null - -/datum/ai_behavior/territorial_struggle - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 5 SECONDS - ///chance the battle ends! - var/end_battle_chance = 25 - -/datum/ai_behavior/territorial_struggle/setup(datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/living_pawn = controller.pawn - var/mob/living/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - if(target.ai_controller?.blackboard[target_key] != living_pawn) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/territorial_struggle/perform(seconds_per_tick, datum/ai_controller/controller, target_key, cries_key) - . = ..() - var/mob/living/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - var/mob/living/living_pawn = controller.pawn - var/list/threaten_list = controller.blackboard[cries_key] - if(length(threaten_list)) - living_pawn.say(pick(threaten_list), forced = "ai_controller") - - if(!prob(end_battle_chance)) - return - - //50 50 chance we lose - var/datum/ai_controller/loser_controller = prob(50) ? controller : target.ai_controller - - loser_controller.set_blackboard_key(BB_BASIC_MOB_FLEE_TARGET, target) - target.ai_controller.clear_blackboard_key(BB_TRESSPASSER_TARGET) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/territorial_struggle/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice - target_key = BB_MOUSE_TARGET - hunting_behavior = /datum/ai_behavior/play_with_mouse - finding_behavior = /datum/ai_behavior/find_hunt_target/hunt_mice - hunt_targets = list(/mob/living/basic/mouse) - hunt_chance = 75 - hunt_range = 9 - -/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/list/items_we_carry = typecache_filter_list(living_pawn, controller.blackboard[BB_HUNTABLE_PREY]) - if(length(items_we_carry)) - return - return ..() - - -/datum/ai_behavior/find_hunt_target/hunt_mice/valid_dinner(mob/living/source, mob/living/mouse, radius) - if(mouse.stat == DEAD || mouse.mind) - return FALSE - return can_see(source, mouse, radius) - -//play as in kill -/datum/ai_behavior/play_with_mouse - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 10 SECONDS - ///chance we hunt the mouse! - var/consume_chance = 70 - -/datum/ai_behavior/play_with_mouse/setup(datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/play_with_mouse/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/basic/mouse/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - consume_chance = istype(target, /mob/living/basic/mouse/brown/tom) ? 5 : initial(consume_chance) - if(prob(consume_chance)) - target.splat() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/play_with_mouse/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - var/mob/living/living_pawn = controller.pawn - var/atom/target = controller.blackboard[target_key] - controller.clear_blackboard_key(target_key) - if(isnull(target) || QDELETED(living_pawn)) - return - var/manual_emote = "attempts to hunt [target]..." - var/end_result = success ? "and succeeds!" : "but fails!" - manual_emote += end_result - living_pawn.manual_emote(manual_emote) - -/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food - target_key = BB_CAT_FOOD_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/find_cat_food - finding_behavior = /datum/ai_behavior/find_hunt_target/find_cat_food - hunt_targets = list(/obj/item/fish, /obj/item/food/deadmouse, /obj/item/food/fishmeat) - hunt_chance = 75 - hunt_range = 9 - -/datum/ai_behavior/hunt_target/interact_with_target/find_cat_food - always_reset_target = TRUE - -/datum/ai_behavior/find_hunt_target/find_cat_food/valid_dinner(mob/living/source, atom/dinner, radius) - //this food is already near a kitten, let the kitten eat it - var/mob/living/nearby_kitten = locate(/mob/living/basic/pet/cat/kitten) in oview(2, dinner) - if(nearby_kitten && nearby_kitten != source) - return FALSE - return can_see(source, dinner, radius) - -/datum/ai_planning_subtree/haul_food_to_young/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_FOOD_TO_DELIVER)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_hands/given_list, BB_FOOD_TO_DELIVER, controller.blackboard[BB_HUNTABLE_PREY]) - return - if(!controller.blackboard_key_exists(BB_KITTEN_TO_FEED)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_kitten, BB_KITTEN_TO_FEED, /mob/living/basic/pet/cat/kitten) - return - - controller.queue_behavior(/datum/ai_behavior/deliver_food_to_kitten, BB_KITTEN_TO_FEED, BB_FOOD_TO_DELIVER) - -/datum/ai_behavior/find_and_set/valid_kitten - -/datum/ai_behavior/find_and_set/valid_kitten/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/kitten = locate(locate_path) in oview(search_range, controller.pawn) - //kitten already has food near it, go feed another hungry kitten - - if(isnull(kitten)) - return null - - var/list/nearby_food = typecache_filter_list(oview(2, kitten), controller.blackboard[BB_HUNTABLE_PREY]) - if(kitten.stat != DEAD && !length(nearby_food)) - return kitten - return null - -/datum/ai_behavior/deliver_food_to_kitten - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 5 SECONDS - -/datum/ai_behavior/deliver_food_to_kitten/setup(datum/ai_controller/controller, target_key, food_key) - . = ..() - var/mob/living/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/deliver_food_to_kitten/perform(seconds_per_tick, datum/ai_controller/controller, target_key, food_key) - var/mob/living/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_pawn = controller.pawn - var/atom/movable/food = controller.blackboard[food_key] - - if(isnull(food) || !(food in living_pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - food.forceMove(get_turf(living_pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/deliver_food_to_kitten/finish_action(datum/ai_controller/controller, success, target_key, food_key) - . = ..() - controller.clear_blackboard_key(target_key) - controller.clear_blackboard_key(food_key) + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat.bt.json" +/datum/ai_controller/basic_controller/cat/kitten + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_HUNGRY_MEOW = list("mrrp...", "mraw..."), + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_MAX_DISTANCE_TO_FOOD = 2, + ) + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/kitten.bt.json" +/datum/ai_controller/basic_controller/cat/bread + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_bread.bt.json" +/datum/ai_controller/basic_controller/cat/cake + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_cake.bt.json" diff --git a/code/modules/mob/living/basic/pets/cat/cat_bread.bt.json b/code/modules/mob/living/basic/pets/cat/cat_bread.bt.json new file mode 100644 index 000000000000..ca64dbb37b11 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_bread.bt.json @@ -0,0 +1,140 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cat/bread", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_turn_off_stove" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_haul_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_hunt_mice" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/cats" + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOUSE_TARGET", + "target_source": "/datum/target_source/oview_single_type/mouse", + "targeting_strategy": "/datum/targeting_strategy/huntable_mouse", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/cat_food", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_STOVE_TARGET", + "target_source": "/datum/target_source/oview_single_type/oven", + "targeting_strategy": "/datum/targeting_strategy/finished_stove", + "vision_range": 9 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOOD_TO_DELIVER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOOD_TO_DELIVER", + "target_source": "/datum/target_source/carried_huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOOD_TO_DELIVER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOOD_TO_DELIVER", + "target_source": "/datum/target_source/carried_huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_bt.dm b/code/modules/mob/living/basic/pets/cat/cat_bt.dm new file mode 100644 index 000000000000..b7822938c043 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_bt.dm @@ -0,0 +1,259 @@ +// Behavior tree behaviors, subtrees and decorators for the cat AI family. + +/// Passes when the cat is currently carrying a piece of huntable food. +/datum/bt_node/decorator/cat_holding_food + +/datum/bt_node/decorator/cat_holding_food/check_condition(datum/ai_controller/controller) + var/mob/living/basic/pet/cat/cat = controller.pawn + return istype(cat) && !isnull(cat.held_food) + +/// Pounces on a mouse, occasionally killing it. Movement is handled externally. +/datum/bt_node/ai_behavior/play_with_mouse + time_between_perform = 10 SECONDS + /// Blackboard key holding the mouse we are hunting. + var/target_key + /// Chance we actually splat the mouse. + var/consume_chance = 70 + +/datum/bt_node/ai_behavior/play_with_mouse/setup(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/play_with_mouse/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/mouse/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + var/chance = istype(target, /mob/living/basic/mouse/brown/tom) ? 5 : consume_chance + if(prob(chance)) + target.splat() + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/datum/bt_node/ai_behavior/play_with_mouse/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + var/mob/living/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + controller.clear_blackboard_key(target_key) + if(isnull(target) || QDELETED(living_pawn)) + return + var/manual_emote = "attempts to hunt [target]..." + manual_emote += succeeded ? "and succeeds!" : "but fails!" + living_pawn.manual_emote(manual_emote) + +/// Drops a piece of carried food at a kitten's feet. +/datum/bt_node/ai_behavior/deliver_food_to_kitten + time_between_perform = 5 SECONDS + /// Blackboard key holding the kitten to feed. + var/target_key + /// Blackboard key holding the food we are carrying. + var/food_key + +/datum/bt_node/ai_behavior/deliver_food_to_kitten/setup(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/deliver_food_to_kitten/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/living_pawn = controller.pawn + var/atom/movable/food = controller.blackboard[food_key] + if(isnull(food) || !(food in living_pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + food.forceMove(get_turf(living_pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/deliver_food_to_kitten/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + controller.clear_blackboard_key(food_key) + +/// Engages a rival tom in a territorial yowling contest until one of them backs down +/datum/bt_node/ai_behavior/territorial_struggle + time_between_perform = 5 SECONDS + /// Blackboard key holding our rival. + var/target_key + /// Blackboard key holding the list of threatening cries. + var/cries_key + /// Chance the battle ends on each perform. + var/end_battle_chance = 25 + +/datum/bt_node/ai_behavior/territorial_struggle/setup(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + // Only fight if our rival is still locked onto us. + if(target.ai_controller?.blackboard[target_key] != living_pawn) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/territorial_struggle/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + var/mob/living/living_pawn = controller.pawn + var/list/threaten_list = controller.blackboard[cries_key] + if(length(threaten_list)) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), pick(threaten_list), forced = "ai_controller") + + if(!prob(end_battle_chance)) + return AI_BEHAVIOR_DELAY + + // 50/50 chance we lose. + var/datum/ai_controller/loser_controller = prob(50) ? controller : target.ai_controller + loser_controller.set_blackboard_key(BB_BASIC_MOB_FLEE_TARGET, target) + target.ai_controller?.clear_blackboard_key(target_key) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/territorial_struggle/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +/// Finds a rival male cat to fight, marking ourselves as their target too so the fight is mutual. +/datum/bt_node/ai_behavior/find_cat_tresspasser + time_between_perform = 5 SECONDS + /// Blackboard key to store the rival in. + var/target_key + /// How far to look for a rival. + var/search_range = 9 + +/datum/bt_node/ai_behavior/find_cat_tresspasser/setup(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + return living_pawn.gender == MALE + +/datum/bt_node/ai_behavior/find_cat_tresspasser/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/ignore_types = controller.blackboard[BB_BABIES_CHILD_TYPES] + for(var/mob/living/basic/pet/cat/potential_enemy in oview(search_range, living_pawn)) + if(potential_enemy.gender != MALE) + continue + if(is_type_in_list(potential_enemy, ignore_types)) + continue + var/datum/ai_controller/enemy_controller = potential_enemy.ai_controller + if(isnull(enemy_controller)) + continue + // They're already engaged in a battle, leave them alone! + if(enemy_controller.blackboard_key_exists(BB_TRESSPASSER_TARGET)) + continue + // You choose me and I choose you. + enemy_controller.set_blackboard_key(BB_TRESSPASSER_TARGET, living_pawn) + controller.set_blackboard_key(target_key, potential_enemy) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Points at a target and meows for food. Used to beg humans and call kittens to dinner. +/datum/bt_node/ai_behavior/beacon_for_food + time_between_perform = 5 SECONDS + /// Blackboard key holding the atom we are pointing at. + var/target_key + /// Blackboard key holding the list of hungry meows. + var/meows_key + +/datum/bt_node/ai_behavior/beacon_for_food/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/beacon_for_food/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/living_pawn = controller.pawn + var/list/meowing_list = controller.blackboard[meows_key] + if(length(meowing_list)) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), pick(meowing_list), forced = "ai_controller") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob/living, _pointed), target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/beacon_for_food/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +/// Finds a conscious human carrying food worth begging for. +/datum/bt_node/ai_behavior/find_human_to_beg + /// Blackboard key to store the human in. + var/target_key + /// How far to look for a human. + var/search_range = 9 + +/datum/bt_node/ai_behavior/find_human_to_beg/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/locate_items = controller.blackboard[BB_HUNTABLE_PREY] + for(var/mob/living/carbon/human/human_target in oview(search_range, living_pawn)) + if(human_target.stat != CONSCIOUS || isnull(human_target.mind)) + continue + for(var/obj/item/held_item in human_target.held_items) + if(is_type_in_typecache(held_item, locate_items)) + controller.set_blackboard_key(target_key, human_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Leaves the cat house we are currently residing in, occasionally. +/datum/bt_node/ai_behavior/leave_cat_home + /// Chance per attempt that we decide to leave. + var/leave_home_chance = 15 + +/datum/bt_node/ai_behavior/leave_cat_home/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/structure/cat_house/home = controller.pawn.loc + if(!istype(home)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!prob(leave_home_chance)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), home, FALSE) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Celebrates around a decorated donut with a spin. +/datum/bt_node/ai_behavior/hunt_target/decorate_donuts + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/decorate_donuts/target_caught(mob/living/hunter, atom/hunted) + hunter.spin(spintime = 4, speed = 1) + +/// Enters (or exits if already resident) a cat house keyed in target_key. +/datum/bt_node/ai_behavior/enter_cat_home + var/target_key + +/datum/bt_node/ai_behavior/enter_cat_home/setup(datum/ai_controller/controller) + var/obj/structure/cat_house/home = controller.blackboard[target_key] + return !QDELETED(home) + +/datum/bt_node/ai_behavior/enter_cat_home/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/structure/cat_house/home = controller.blackboard[target_key] + if(QDELETED(home)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/basic/pet/cat/living_pawn = controller.pawn + if(living_pawn == home.resident_cat || isnull(home.resident_cat)) + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), home, FALSE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/datum/bt_node/ai_behavior/enter_cat_home/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +// Subtree types the trees themselves live in the matching .bt.json files. + +/// Block other behaviors while residing in a cat house; occasionally leave. +/datum/bt_node/subtree/cat_reside_in_home + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.json" + +/// Find a mouse and pounce on it. +/datum/bt_node/subtree/cat_hunt_mice + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.json" + +/// Find dropped food on the ground and eat it. +/datum/bt_node/subtree/cat_find_food + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_find_food.bt.json" + +/// Carry food we are holding to a hungry kitten. +/datum/bt_node/subtree/cat_haul_food + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_haul_food.bt.json" + +/// Turn off a finished oven. +/datum/bt_node/subtree/cat_turn_off_stove + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.json" + +/// Spin to decorate nearby donuts. +/datum/bt_node/subtree/cat_decorate_donuts + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.json" diff --git a/code/modules/mob/living/basic/pets/cat/cat_cake.bt.json b/code/modules/mob/living/basic/pets/cat/cat_cake.bt.json new file mode 100644 index 000000000000..a9abb2507d95 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_cake.bt.json @@ -0,0 +1,154 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cat/cake", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_turn_off_stove" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_decorate_donuts" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_haul_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_hunt_mice" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/cats" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOUSE_TARGET", + "target_source": "/datum/target_source/oview_single_type/mouse", + "targeting_strategy": "/datum/targeting_strategy/huntable_mouse", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/cat_food", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_STOVE_TARGET", + "target_source": "/datum/target_source/oview_single_type/oven", + "targeting_strategy": "/datum/targeting_strategy/finished_stove", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DONUT_TARGET", + "target_source": "/datum/target_source/oview_single_type/donut", + "targeting_strategy": "/datum/targeting_strategy/decorated_donut", + "vision_range": 9 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOOD_TO_DELIVER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOOD_TO_DELIVER", + "target_source": "/datum/target_source/carried_huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOOD_TO_DELIVER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOOD_TO_DELIVER", + "target_source": "/datum/target_source/carried_huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.json b/code/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.json new file mode 100644 index 000000000000..70bab944828c --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.json @@ -0,0 +1,29 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_decorate_donuts", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_DONUT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DONUT_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/decorate_donuts", + "vars": { + "target_key": "BB_DONUT_TARGET" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_find_food.bt.json b/code/modules/mob/living/basic/pets/cat/cat_find_food.bt.json new file mode 100644 index 000000000000..7d5201039ad3 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_find_food.bt.json @@ -0,0 +1,30 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_find_food", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CAT_FOOD_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "always_reset_target": true + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_haul_food.bt.json b/code/modules/mob/living/basic/pets/cat/cat_haul_food.bt.json new file mode 100644 index 000000000000..cbe66fcacf60 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_haul_food.bt.json @@ -0,0 +1,41 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_haul_food", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cat_holding_food", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_KITTEN_TO_FEED", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOOD_TO_DELIVER" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_KITTEN_TO_FEED", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/deliver_food_to_kitten", + "vars": { + "target_key": "BB_KITTEN_TO_FEED", + "food_key": "BB_FOOD_TO_DELIVER" + } + } + ] + } + } + } +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.json b/code/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.json new file mode 100644 index 000000000000..1235d8b981d1 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.json @@ -0,0 +1,36 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_hunt_mice", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cat_holding_food", + "vars": { + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MOUSE_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_MOUSE_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/play_with_mouse", + "vars": { + "target_key": "BB_MOUSE_TARGET" + } + } + ] + } + } +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.json b/code/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.json new file mode 100644 index 000000000000..efd2ef664add --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.json @@ -0,0 +1,55 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_reside_in_home", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_loc_is_type", + "vars": { + "loc_type": "/obj/structure/cat_house", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/leave_cat_home" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CAT_HOME", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CAT_HOME", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enter_cat_home", + "vars": { + "target_key": "BB_CAT_HOME" + } + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.json b/code/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.json new file mode 100644 index 000000000000..b7f84d677d96 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.json @@ -0,0 +1,30 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_turn_off_stove", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_STOVE_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_STOVE_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_STOVE_TARGET", + "always_reset_target": true + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/pets/cat/kitten.bt.json b/code/modules/mob/living/basic/pets/cat/kitten.bt.json new file mode 100644 index 000000000000..7809ec2efff8 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/kitten.bt.json @@ -0,0 +1,151 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cat/kitten", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HUMAN_BEG_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/beacon_for_food", + "vars": { + "target_key": "BB_HUMAN_BEG_TARGET", + "meows_key": "BB_HUNGRY_MEOW" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CAT_FOOD_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_farther_than_from_key", + "vars": { + "anchor_key": "BB_CAT_FOOD_TARGET", + "distance_key": "BB_MAX_DISTANCE_TO_FOOD" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/beacon_for_food", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "meows_key": "BB_HUNGRY_MEOW" + } + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "always_reset_target": true + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/cats" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/cat_food", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_human_to_beg", + "vars": { + "target_key": "BB_HUMAN_BEG_TARGET" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/kitten_ai.dm b/code/modules/mob/living/basic/pets/cat/kitten_ai.dm deleted file mode 100644 index 952c7aabd68c..000000000000 --- a/code/modules/mob/living/basic/pets/cat/kitten_ai.dm +++ /dev/null @@ -1,67 +0,0 @@ - -/datum/ai_controller/basic_controller/cat/kitten - blackboard = list( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - BB_HUNGRY_MEOW = list("mrrp...", "mraw..."), - BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, - BB_MAX_DISTANCE_TO_FOOD = 2, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/beg_human, - /datum/ai_planning_subtree/find_and_hunt_target/find_cat_food/kitten, - /datum/ai_planning_subtree/random_speech/cats, - ) - -//if the food is too far away, point at it or meow. if its near us then go eat it - -/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food/kitten - - -/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food/kitten/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_CAT_FOOD_TARGET] - if(target && get_dist(target, controller.pawn) > controller.blackboard[BB_MAX_DISTANCE_TO_FOOD]) - controller.queue_behavior(/datum/ai_behavior/beacon_for_food, BB_CAT_FOOD_TARGET, BB_HUNGRY_MEOW) - return - return ..() - -/datum/ai_behavior/beacon_for_food - action_cooldown = 5 SECONDS - -/datum/ai_behavior/beacon_for_food/perform(seconds_per_tick, datum/ai_controller/controller, target_key, meows_key) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/living_pawn = controller.pawn - var/list/meowing_list = controller.blackboard[meows_key] - if(length(meowing_list)) - living_pawn.say(pick(meowing_list), forced = "ai_controller") - living_pawn._pointed(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/beacon_for_food/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_planning_subtree/beg_human - -/datum/ai_planning_subtree/beg_human/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - if(controller.blackboard_key_exists(BB_HUMAN_BEG_TARGET)) - controller.queue_behavior(/datum/ai_behavior/beacon_for_food, BB_HUMAN_BEG_TARGET, BB_HUNGRY_MEOW) - return - - controller.queue_behavior(/datum/ai_behavior/find_and_set/human_beg, BB_HUMAN_BEG_TARGET, /mob/living/carbon/human) - -/datum/ai_behavior/find_and_set/human_beg/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/locate_items = controller.blackboard[BB_HUNTABLE_PREY] - for(var/mob/living/carbon/human/human_target in oview(search_range, controller.pawn)) - if(human_target.stat != CONSCIOUS || isnull(human_target.mind)) - continue - for (var/obj/item/held_item in human_target.held_items) - if (is_type_in_typecache(held_item, locate_items)) - return human_target - return null diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm index cd98fcf1f0ae..2f4d9485fde9 100644 --- a/code/modules/mob/living/basic/pets/dog/_dog.dm +++ b/code/modules/mob/living/basic/pets/dog/_dog.dm @@ -7,9 +7,9 @@ /datum/pet_command/good_boy/dog speech_commands = list("good dog") -// Set correct attack behaviour +// Use dog-specific melee attack (paws harmlessly when BB_DOG_HARASS_HARM is false) /datum/pet_command/attack/dog - attack_behaviour = /datum/ai_behavior/basic_melee_attack/dog + attack_subtree = /datum/bt_node/subtree/pet_command/attack/dog /datum/pet_command/attack/dog/set_command_active(mob/living/parent, mob/living/commander) . = ..() @@ -86,10 +86,21 @@ break ///Updates dog speech and emotes -/mob/living/basic/pet/dog/proc/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) - speech.speak = string_list(list("YAP", "Woof!", "Bark!", "AUUUUUU")) - speech.emote_hear = string_list(list("barks!", "woofs!", "yaps.","pants.")) - speech.emote_see = string_list(list("shakes [p_their()] head.", "chases [p_their()] tail.","shivers.")) +/mob/living/basic/pet/dog/proc/update_dog_speech(list/speech_data) + speech_data[BB_EMOTE_SAY] = string_list(list("YAP", "Woof!", "Bark!", "AUUUUUU")) + speech_data[BB_EMOTE_HEAR] = string_list(list("barks!", "woofs!", "yaps.","pants.")) + speech_data[BB_EMOTE_SEE] = string_list(list("shakes [p_their()] head.", "chases [p_their()] tail.","shivers.")) + + +/// Populates BB_BASIC_MOB_SPEAK_LINES with the dog's current speech data for BT random speech. +/// Subtypes override this to apply fashion accessories or variant speech. +/mob/living/basic/pet/dog/proc/update_dog_speak_blackboard(datum/ai_controller/controller) + var/list/speech_data = list() + speech_data[BB_EMOTE_SAY] = list("YAP", "Woof!", "Bark!", "AUUUUUU") + speech_data[BB_EMOTE_HEAR] = list("barks!", "woofs!", "yaps.", "pants.") + speech_data[BB_EMOTE_SEE] = list("shakes [p_their()] head.", "chases [p_their()] tail.", "shivers.") + speech_data[BB_SPEAK_CHANCE] = 1 + controller.override_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, speech_data) ///Proc to run on a successful taming attempt /mob/living/basic/pet/dog/tamed(mob/living/tamer, atom/food) diff --git a/code/modules/mob/living/basic/pets/dog/corgi.dm b/code/modules/mob/living/basic/pets/dog/corgi.dm index 0e514005aa5c..4109d22e6797 100644 --- a/code/modules/mob/living/basic/pets/dog/corgi.dm +++ b/code/modules/mob/living/basic/pets/dog/corgi.dm @@ -135,16 +135,23 @@ return ..() -/mob/living/basic/pet/dog/corgi/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) +/mob/living/basic/pet/dog/corgi/update_dog_speech(list/speech_data) . = ..() - if(inventory_head?.dog_fashion) var/datum/dog_fashion/equipped_head_fashion_item = new inventory_head.dog_fashion(src) - equipped_head_fashion_item.apply_to_speech(speech) + equipped_head_fashion_item.apply_to_speech(speech_data) if(inventory_back?.dog_fashion) var/datum/dog_fashion/equipped_back_fashion_item = new inventory_back.dog_fashion(src) - equipped_back_fashion_item.apply_to_speech(speech) + equipped_back_fashion_item.apply_to_speech(speech_data) + +/// Applies corgi fashion to the BT blackboard speech data by routing through the planning stub. +/mob/living/basic/pet/dog/corgi/update_dog_speak_blackboard(datum/ai_controller/controller) + var/list/speech_data = list() + update_dog_speech(speech_data) + if(!speech_data[BB_SPEAK_CHANCE]) + speech_data[BB_SPEAK_CHANCE] = 1 + controller.override_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, speech_data) /mob/living/basic/pet/dog/corgi/deadchat_plays(mode = ANARCHY_MODE, cooldown = 12 SECONDS) . = AddComponent(/datum/component/deadchat_control/cardinal_movement, mode, list( @@ -508,10 +515,10 @@ . = ..() speak_emote = list("growls", "barks ominously") -/mob/living/basic/pet/dog/corgi/narsie/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) - speech.speak = string_list(list("Tari'karat-pasnar!", "IA! IA!", "BRRUUURGHGHRHR")) - speech.emote_hear = string_list(list("barks echoingly!", "woofs hauntingly!", "yaps in an eldritch manner.", "mutters something unspeakable.")) - speech.emote_see = string_list(list("communes with the unnameable.", "ponders devouring some souls.", "shakes.")) +/mob/living/basic/pet/dog/corgi/narsie/update_dog_speech(list/speech_data) + speech_data[BB_EMOTE_SAY] = string_list(list("Tari'karat-pasnar!", "IA! IA!", "BRRUUURGHGHRHR")) + speech_data[BB_EMOTE_HEAR] = string_list(list("barks echoingly!", "woofs hauntingly!", "yaps in an eldritch manner.", "mutters something unspeakable.")) + speech_data[BB_EMOTE_SEE] = string_list(list("communes with the unnameable.", "ponders devouring some souls.", "shakes.")) /mob/living/basic/pet/dog/corgi/narsie/narsie_act() if(stat == DEAD) //Nar'Sie loves her doggy diff --git a/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm b/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm index 3e2dc5e5649b..a59fdef2ff23 100644 --- a/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm +++ b/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm @@ -64,6 +64,7 @@ ai_controller = /datum/ai_controller/basic_controller/guarddog /datum/ai_controller/basic_controller/guarddog + behavior_tree_json = "code/modules/mob/living/basic/pets/dog/guarddog.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -72,12 +73,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /mob/living/basic/pet/dog/breaddog //Most of the code originates from Cak name = "Kobun" diff --git a/code/modules/mob/living/basic/pets/dog/guarddog.bt.json b/code/modules/mob/living/basic/pets/dog/guarddog.bt.json new file mode 100644 index 000000000000..588d575dd9ae --- /dev/null +++ b/code/modules/mob/living/basic/pets/dog/guarddog.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/guarddog", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" +} diff --git a/code/modules/mob/living/basic/pets/fox.bt.json b/code/modules/mob/living/basic/pets/fox.bt.json new file mode 100644 index 000000000000..8a9c017ee55b --- /dev/null +++ b/code/modules/mob/living/basic/pets/fox.bt.json @@ -0,0 +1,133 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fox", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_FLEE_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/fox" + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/no_humans_watching", + "vars": { + "polling_rate": "2 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/fox.dm b/code/modules/mob/living/basic/pets/fox.dm index e986d74259ba..1e1e25f378a9 100644 --- a/code/modules/mob/living/basic/pets/fox.dm +++ b/code/modules/mob/living/basic/pets/fox.dm @@ -68,24 +68,11 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/simple_find_target/not_while_observed, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/fox, - ) + behavior_tree_json = "code/modules/mob/living/basic/pets/fox.bt.json" // An AI controller for more docile foxes. /datum/ai_controller/basic_controller/fox/docile - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/random_speech/fox, - ) + behavior_tree_json = "code/modules/mob/living/basic/pets/fox_docile.bt.json" // The captain's fox, Renault /mob/living/basic/pet/fox/renault diff --git a/code/modules/mob/living/basic/pets/fox_docile.bt.json b/code/modules/mob/living/basic/pets/fox_docile.bt.json new file mode 100644 index 000000000000..19f92dc23a37 --- /dev/null +++ b/code/modules/mob/living/basic/pets/fox_docile.bt.json @@ -0,0 +1,61 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fox/docile", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_FLEE_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/fox" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/gondolas/gondola.bt.json b/code/modules/mob/living/basic/pets/gondolas/gondola.bt.json new file mode 100644 index 000000000000..e1e36d0a3269 --- /dev/null +++ b/code/modules/mob/living/basic/pets/gondolas/gondola.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/gondola", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": "10" + } +} diff --git a/code/modules/mob/living/basic/pets/gondolas/gondola.dm b/code/modules/mob/living/basic/pets/gondolas/gondola.dm index 8e235dc96883..7810317381f1 100644 --- a/code/modules/mob/living/basic/pets/gondolas/gondola.dm +++ b/code/modules/mob/living/basic/pets/gondolas/gondola.dm @@ -69,13 +69,13 @@ add_overlay(moustache_overlay) /datum/ai_controller/basic_controller/gondola + behavior_tree_json = "code/modules/mob/living/basic/pets/gondolas/gondola.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking #undef GONDOLA_HEIGHT #undef GONDOLA_COLOR diff --git a/code/modules/mob/living/basic/pets/orbie/orbie.bt.json b/code/modules/mob/living/basic/pets/orbie/orbie.bt.json new file mode 100644 index 000000000000..1677fa9e4324 --- /dev/null +++ b/code/modules/mob/living/basic/pets/orbie/orbie.bt.json @@ -0,0 +1,100 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/orbie", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_NEARBY_PLAYMATE", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_NEARBY_PLAYMATE", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/interact_with_playmate", + "vars": { + "target_key": "BB_NEARBY_PLAYMATE" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_LAST_RECEIVED_MESSAGE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/relay_pda_message", + "vars": { + "target_key": "BB_LAST_RECEIVED_MESSAGE" + } + } + } + ] + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_NEXT_PLAYDATE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/find_playmate" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + } + ] +} diff --git a/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm b/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm index 900154b4c9a1..8fbc7340fcea 100644 --- a/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm +++ b/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm @@ -2,6 +2,7 @@ #define MESSAGE_EXPIRY_TIME (30 SECONDS) /datum/ai_controller/basic_controller/orbie + behavior_tree_json = "code/modules/mob/living/basic/pets/orbie/orbie.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -9,13 +10,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/find_playmates, - /datum/ai_planning_subtree/relay_pda_message, - /datum/ai_planning_subtree/pet_planning, - ) /datum/ai_controller/basic_controller/orbie/TryPossessPawn(atom/new_pawn) . = ..() @@ -28,42 +22,36 @@ addtimer(CALLBACK(src, PROC_REF(clear_blackboard_key), BB_LAST_RECEIVED_MESSAGE), MESSAGE_EXPIRY_TIME) -///ai behavior that lets us search for other orbies to play with -/datum/ai_planning_subtree/find_playmates - -/datum/ai_planning_subtree/find_playmates/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_NEXT_PLAYDATE] > world.time) - return - if(controller.blackboard_key_exists(BB_NEARBY_PLAYMATE)) - controller.queue_behavior(/datum/ai_behavior/interact_with_playmate, BB_NEARBY_PLAYMATE) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/find_playmate, BB_NEARBY_PLAYMATE, /mob/living/basic/orbie) +/// Finds a nearby free orbie to play with and mutually registers as playmates. +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/find_playmate + target_key = BB_NEARBY_PLAYMATE + target_source = /datum/target_source/oview_single_type/orbie + targeting_strategy = /datum/targeting_strategy/playmate -/datum/ai_behavior/find_and_set/find_playmate +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/find_playmate/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + var/mob/living/basic/orbie/playmate = target + playmate.ai_controller.set_blackboard_key(BB_NEARBY_PLAYMATE, controller.pawn) -/datum/ai_behavior/find_and_set/find_playmate/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/mob/living/basic/orbie/playmate in oview(search_range, controller.pawn)) - if(playmate == controller.pawn || playmate.stat == DEAD || isnull(playmate.ai_controller)) - continue - if(playmate.ai_controller.blackboard[BB_NEARBY_PLAYMATE] || playmate.ai_controller.blackboard[BB_NEXT_PLAYDATE] > world.time) //they already have a playmate... - continue - playmate.ai_controller.set_blackboard_key(BB_NEARBY_PLAYMATE, controller.pawn) - return playmate - return null +/// Accepts an orbie that is free to play (no current playmate and not on playdate cooldown). +/datum/targeting_strategy/playmate - -/datum/ai_behavior/interact_with_playmate - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/interact_with_playmate/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) +/datum/targeting_strategy/playmate/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + if(!istype(target, /mob/living/basic/orbie)) + return FALSE + var/mob/living/basic/orbie/orbie_target = target + if(orbie_target == living_mob || orbie_target.stat == DEAD || isnull(orbie_target.ai_controller)) return FALSE - set_movement_target(controller, target) + if(orbie_target.ai_controller.blackboard[BB_NEARBY_PLAYMATE]) + return FALSE + if(orbie_target.ai_controller.blackboard[BB_NEXT_PLAYDATE] > world.time) + return FALSE + return TRUE -/datum/ai_behavior/interact_with_playmate/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +///plays with a nearby orbie +/datum/bt_node/ai_behavior/interact_with_playmate + var/target_key = "BB_NEARBY_PLAYMATE" + +/datum/bt_node/ai_behavior/interact_with_playmate/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/living_pawn = controller.pawn var/atom/target = controller.blackboard[target_key] @@ -72,38 +60,36 @@ living_pawn.manual_emote("plays with [target]!") living_pawn.spin(spintime = 4, speed = 1) - living_pawn.ClickOn(target) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, ClickOn), target) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/interact_with_playmate/finish_action(datum/ai_controller/controller, success, target_key) +/datum/bt_node/ai_behavior/interact_with_playmate/finish_action(datum/ai_controller/controller, success) . = ..() controller.clear_blackboard_key(target_key) controller.set_blackboard_key(BB_NEXT_PLAYDATE, world.time + PET_PLAYTIME_COOLDOWN) -/datum/ai_planning_subtree/relay_pda_message - -/datum/ai_planning_subtree/relay_pda_message/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 2 || isnull(controller.blackboard[BB_LAST_RECEIVED_MESSAGE])) - return +///relays a pda message if orbie is level 2+ +/datum/bt_node/ai_behavior/relay_pda_message + var/target_key = "BB_LAST_RECEIVED_MESSAGE" - controller.queue_behavior(/datum/ai_behavior/relay_pda_message, BB_LAST_RECEIVED_MESSAGE) +/datum/bt_node/ai_behavior/relay_pda_message/perform(seconds_per_tick, datum/ai_controller/controller) + if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 2) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED -/datum/ai_behavior/relay_pda_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key) var/mob/living/basic/living_pawn = controller.pawn var/text_to_say = controller.blackboard[target_key] if(isnull(text_to_say)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - living_pawn.say(text_to_say, forced = "AI controller") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), text_to_say, forced = "AI controller") living_pawn.spin(spintime = 4, speed = 1) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/relay_pda_message/finish_action(datum/ai_controller/controller, success, target_key) +/datum/bt_node/ai_behavior/relay_pda_message/finish_action(datum/ai_controller/controller, success) . = ..() controller.clear_blackboard_key(target_key) /datum/pet_command/follow/orbie - follow_behavior = /datum/ai_behavior/pet_follow_friend/orbie /datum/pet_command/follow/orbie/New(mob/living/parent) . = ..() @@ -113,9 +99,6 @@ SIGNAL_HANDLER set_command_active(source, friend) -/datum/ai_behavior/pet_follow_friend/orbie - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - ///command to make our pet turn its lights on, we need to be level 2 to activate this ability /datum/pet_command/untargeted_ability/pet_lights command_name = "Lights" @@ -128,7 +111,7 @@ /datum/pet_command/untargeted_ability/pet_lights/execute_action(datum/ai_controller/controller) if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 2) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + return TRUE return ..() /datum/pet_command/use_ability/pet_lights/retrieve_command_text(atom/living_pet, atom/target) @@ -151,7 +134,7 @@ /datum/pet_command/use_ability/take_photo/execute_action(datum/ai_controller/controller) if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 3) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + return TRUE return ..() /datum/pet_command/perform_trick_sequence @@ -176,7 +159,7 @@ for(var/index in 1 to length(trick_sequence)) addtimer(CALLBACK(living_pawn, TYPE_PROC_REF(/mob, emote), trick_sequence[index], index * 0.5 SECONDS)) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + return TRUE #undef PET_PLAYTIME_COOLDOWN #undef MESSAGE_EXPIRY_TIME diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm index 7c6b9b13ca51..e46a9e9f33bb 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/parrot + behavior_tree_json = "code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -7,39 +8,7 @@ ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/parrot - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/parrot_as_in_repeat, // always get a witty oneliner in when you can - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/perch_on_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/hoard_items, - ) - -/datum/idle_behavior/idle_random_walk/parrot - ///chance of us moving while perched - var/walk_chance_when_perched = 5 - -/datum/idle_behavior/idle_random_walk/parrot/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - walk_chance = HAS_TRAIT(living_pawn, TRAIT_PARROT_PERCHED) ? walk_chance_when_perched : initial(walk_chance) - return ..() - -/datum/ai_behavior/travel_towards/and_drop - -/datum/ai_behavior/travel_towards/and_drop/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - var/mob/living/living_mob = controller.pawn - if(QDELETED(living_mob)) // pawn can be null at this point - return - var/obj/drop_item = locate(/obj/item) in (living_mob.contents - typecache_filter_list(living_mob.contents, controller.blackboard[BB_IGNORE_ITEMS])) - drop_item?.forceMove(get_turf(living_mob)) - -/datum/ai_behavior/basic_melee_attack/interact_once/parrot -/datum/ai_behavior/basic_melee_attack/interact_once/parrot/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.set_blackboard_key(BB_ALWAYS_IGNORE_FACTION, FALSE) +/datum/bt_node/subtree/perching + behavior_tree_json = "code/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.json" diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm index 75c775456577..bdcc091c9296 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm @@ -1,38 +1,3 @@ /// Used for ghost poly. /datum/ai_controller/basic_controller/parrot/ghost - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/parrot_as_in_repeat, - /datum/ai_planning_subtree/possess_humans, - /datum/ai_planning_subtree/hoard_items, - ) - -///subtree to possess humans -/datum/ai_planning_subtree/possess_humans - ///chance we go possess humans - var/possess_chance = 2 - -/datum/ai_planning_subtree/possess_humans/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - - if(controller.blackboard_key_exists(BB_PERCH_TARGET)) - controller.queue_behavior(/datum/ai_behavior/perch_on_target/haunt, BB_PERCH_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - - if(!SPT_PROB(possess_chance, seconds_per_tick)) - if(ishuman(living_pawn.loc)) - return SUBTREE_RETURN_FINISH_PLANNING - return - - if(ishuman(living_pawn.loc)) - controller.set_blackboard_key(living_pawn.loc) - return - - controller.queue_behavior(/datum/ai_behavior/find_and_set/conscious_person, BB_PERCH_TARGET) - - -/datum/ai_behavior/perch_on_target/haunt - -/datum/ai_behavior/perch_on_target/haunt/check_human_conditions(mob/living/living_human) - return (living_human.stat != DEAD) + behavior_tree_json = "code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.json" diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.json b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.json new file mode 100644 index 000000000000..b52f4da27614 --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.json @@ -0,0 +1,190 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/parrot", + "type": "selector", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_contained_in_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/container_attackable", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_out_of_object/from_bb", + "vars": { + "target_key": "BB_BASIC_MOB_ESCAPE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/perching" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/parrot_hoard" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/parrot" + } + ] + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_PARROT_SPEECH_COOLDOWN", + "cooldown_duration": "7.5 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/parrot_repeat_speech" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.json b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.json new file mode 100644 index 000000000000..079b39f856f8 --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.json @@ -0,0 +1,119 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/parrot/ghost", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_PERCH_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_PERCH_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perch_on_target/haunt", + "vars": { + "target_key": "BB_PERCH_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_PERCH_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.02 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_PERCH_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_human" + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/parrot_hoard" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/parrot" + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_PARROT_SPEECH_COOLDOWN", + "cooldown_duration": "7.5 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/parrot_repeat_speech" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.json b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.json new file mode 100644 index 000000000000..5222613ef3ac --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.json @@ -0,0 +1,118 @@ +{ + "dm_type": "/datum/bt_node/subtree/parrot_hoard", + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HOARD_LOCATION" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HOARD_LOCATION", + "targeting_strategy": "/datum/targeting_strategy/parrot_hoard_location", + "target_source": "/datum/target_source/oview", + "vision_range": "BB_HOARD_LOCATION_RANGE" + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_holding_item", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_MY_PAWN" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HOARD_LOCATION", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/drop_all_held_items" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_HOARD_ITEM_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HOARD_ITEM_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/parrot", + "vars": { + "target_key": "BB_HOARD_ITEM_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_HOARD_ITEM_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/parrot_hoard_item", + "vars": { + "target_key": "BB_HOARD_ITEM_TARGET" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm index a4059b2d9647..eb1213a09be5 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm @@ -1,74 +1,71 @@ -///subtree to steal items -/datum/ai_planning_subtree/hoard_items - var/theft_chance = 5 +/// Accepts open turfs which aren't space, aren't blocked, and are within hoarding range. Used to pick a parrot's nest. +/datum/targeting_strategy/parrot_hoard_location -/datum/ai_planning_subtree/hoard_items/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn +/datum/targeting_strategy/parrot_hoard_location/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/turf/open/candidate = target + if(!istype(candidate) || is_space_or_openspace(candidate)) + return FALSE + if(candidate.is_blocked_turf(source_atom = living_mob)) + return FALSE + return TRUE - var/turf/myspace = controller.blackboard[BB_HOARD_LOCATION] +/// Accepts small items lying on a turf away from the nest, or non-ally humans holding a small valuable. Used to pick something to steal. +/datum/targeting_strategy/parrot_hoard_item - if(isnull(myspace) || myspace.is_blocked_turf(source_atom = controller.pawn) || get_dist(myspace, controller.pawn) > controller.blackboard[BB_HOARD_LOCATION_RANGE]) - controller.queue_behavior(/datum/ai_behavior/find_and_set/hoard_location, BB_HOARD_LOCATION, /turf/open) - return +/datum/targeting_strategy/parrot_hoard_item/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(isnull(controller)) + return FALSE - //we have an item, go drop! - var/list/our_contents = living_pawn.contents - typecache_filter_list(living_pawn.contents, controller.blackboard[BB_IGNORE_ITEMS]) - if(length(our_contents)) - controller.queue_behavior(/datum/ai_behavior/travel_towards/and_drop, BB_HOARD_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING + var/list/ignore_items = controller.blackboard[BB_IGNORE_ITEMS] + if(is_type_in_typecache(target, ignore_items)) + return FALSE - if(controller.blackboard_key_exists(BB_HOARD_ITEM_TARGET)) - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/interact_once, BB_HOARD_ITEM_TARGET, BB_TARGETING_STRATEGY) - return SUBTREE_RETURN_FINISH_PLANNING + var/turf/nest_turf = controller.blackboard[BB_HOARD_LOCATION] + if(target.loc == nest_turf) + return FALSE - if(!SPT_PROB(theft_chance, seconds_per_tick)) - return - controller.queue_behavior(/datum/ai_behavior/find_and_set/hoard_item, BB_HOARD_ITEM_TARGET) + if(isitem(target)) + var/obj/item/loose_item = target + return loose_item.w_class <= WEIGHT_CLASS_SMALL -/datum/ai_behavior/find_and_set/hoard_location + if(!ishuman(target)) + return FALSE + if(living_mob.has_ally(target)) // dont steal from friends + return FALSE + return holding_valuable(controller, target) -/datum/ai_behavior/find_and_set/hoard_location/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/turf/open/candidate in oview(search_range, controller.pawn)) - if(is_space_or_openspace(candidate)) - continue - if(candidate.is_blocked_turf(source_atom = controller.pawn)) +/datum/targeting_strategy/parrot_hoard_item/proc/holding_valuable(datum/ai_controller/controller, mob/living/human_target) + var/list/ignore_items = controller.blackboard[BB_IGNORE_ITEMS] + for(var/obj/item/potential_item in human_target.held_items) + if(is_type_in_typecache(potential_item, ignore_items)) continue - return candidate + if(potential_item.w_class <= WEIGHT_CLASS_SMALL) + return TRUE + return FALSE - return null +/// Finds something for the parrot to steal. Temporarily ignores faction when eyeing a person's belongings. +/datum/bt_node/ai_behavior/acquire_target/parrot_hoard_item + target_source = /datum/target_source/oview + targeting_strategy = /datum/targeting_strategy/parrot_hoard_item + time_between_perform = 5 SECONDS -/datum/ai_behavior/find_and_set/hoard_item - action_cooldown = 5 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION +/datum/bt_node/ai_behavior/acquire_target/parrot_hoard_item/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + if(ishuman(target)) + controller.set_blackboard_key(BB_ALWAYS_IGNORE_FACTION, TRUE) -/datum/ai_behavior/find_and_set/hoard_item/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - if(!controller.blackboard_key_exists(BB_HOARD_LOCATION)) - return null - var/turf/nest_turf = controller.blackboard[BB_HOARD_LOCATION] - var/mob/living/living_pawn = controller.pawn - for(var/atom/potential_victim in oview(search_range, controller.pawn)) - if(is_type_in_typecache(potential_victim, controller.blackboard[BB_IGNORE_ITEMS])) - continue - if(potential_victim.loc == nest_turf) - continue - if(isitem(potential_victim)) - var/obj/item/item_steal = potential_victim - if(item_steal.w_class <= WEIGHT_CLASS_SMALL) - return potential_victim - if(!ishuman(potential_victim)) - continue - if(living_pawn.has_ally(potential_victim)) - continue //dont steal from friends - if(holding_valuable(controller, potential_victim)) - controller.set_blackboard_key(BB_ALWAYS_IGNORE_FACTION, TRUE) - return potential_victim +/// Single-hit grab variant which resets the faction-ignore flag once we are done stealing. +/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/parrot - return null +/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/parrot/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.set_blackboard_key(BB_ALWAYS_IGNORE_FACTION, FALSE) -/datum/ai_behavior/find_and_set/hoard_item/proc/holding_valuable(datum/ai_controller/controller, mob/living/human_target) - for(var/obj/item/potential_item in human_target.held_items) - if(is_type_in_typecache(potential_item, controller.blackboard[BB_IGNORE_ITEMS])) - continue - if(potential_item.w_class <= WEIGHT_CLASS_SMALL) - return TRUE - return FALSE +/// Find a nest, carry loot home, then go steal more. +/datum/bt_node/subtree/parrot_hoard + behavior_tree_json = "code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.json" diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm index b5b34e30cad5..adbb3760b49e 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm @@ -1,74 +1,45 @@ -///subtree to perch on targets -/datum/ai_planning_subtree/perch_on_target - ///perchance... - var/perch_chance = 5 - ///chance we unbuckle - var/unperch_chance = 15 +/// Reads the parrot's desired-perch typecache so it can scan for somewhere to sit. +/datum/target_source/oview_typed/from_bb_key/parrot_perch_types + typecache_key = BB_PARROT_PERCH_TYPES +/// Parrot behavior that perches them on their current perch target. +/datum/bt_node/ai_behavior/perch_on_target + /// Blackboard key holding the atom to perch on. + var/target_key -/datum/ai_planning_subtree/perch_on_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/atom/buckled_to = living_pawn.buckled - - //do we have a current target or is chance to unbuckle has passed? then unbuckle! - if(buckled_to) - if((SPT_PROB(unperch_chance, seconds_per_tick) || controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))) - controller.queue_behavior(/datum/ai_behavior/unbuckle_mob) - return - return SUBTREE_RETURN_FINISH_PLANNING - - //if we are perched, we can go find something else to perch too - var/final_chance = HAS_TRAIT(living_pawn, TRAIT_PARROT_PERCHED) ? unperch_chance : perch_chance - - if(!SPT_PROB(final_chance, seconds_per_tick) || controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - - if(controller.blackboard_key_exists(BB_PERCH_TARGET)) - controller.queue_behavior(/datum/ai_behavior/perch_on_target, BB_PERCH_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - //50 50 chance to look for an object, or a friend - if(prob(50)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/nearby_friends, BB_PERCH_TARGET) - return - - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_PERCH_TARGET, controller.blackboard[BB_PARROT_PERCH_TYPES]) - -/// Parrot behavior that allows them to perch on a target. -/datum/ai_behavior/perch_on_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/perch_on_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - - set_movement_target(controller, target) - -/datum/ai_behavior/perch_on_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/perch_on_target/perform(seconds_per_tick, datum/ai_controller/controller) var/atom/target = controller.blackboard[target_key] if(QDELETED(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/mob/living/basic/parrot/living_pawn = controller.pawn - if(!ishuman(target)) - living_pawn.start_perching(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - if(!check_human_conditions(target)) + if(ishuman(target) && !check_human_conditions(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED living_pawn.start_perching(target) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/perch_on_target/proc/check_human_conditions(mob/living/living_human) +/datum/bt_node/ai_behavior/perch_on_target/proc/check_human_conditions(mob/living/living_human) if(living_human.stat == DEAD || LAZYLEN(living_human.buckled_mobs) >= living_human.max_buckled_mobs) return FALSE - return TRUE -/datum/ai_behavior/perch_on_target/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/perch_on_target/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) + +/// Variant for the ghost parrot, which can haunt any living human. +/datum/bt_node/ai_behavior/perch_on_target/haunt + +/datum/bt_node/ai_behavior/perch_on_target/haunt/check_human_conditions(mob/living/living_human) + return (living_human.stat != DEAD) + +/// Parrot idle wander; sits much more still while perched. +/datum/bt_node/ai_behavior/idle_random_walk/parrot + /// Chance of us moving while perched. + var/walk_chance_when_perched = 5 + +/datum/bt_node/ai_behavior/idle_random_walk/parrot/perform(seconds_per_tick, datum/ai_controller/controller) + walk_chance = HAS_TRAIT(controller.pawn, TRAIT_PARROT_PERCHED) ? walk_chance_when_perched : initial(walk_chance) + return ..() diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm index ab8ad3957b1f..32536df45de9 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm @@ -1,31 +1,24 @@ #define MAXIMUM_PARROT_PITCH 24 -/// When a parrot... parrots... -/datum/ai_planning_subtree/parrot_as_in_repeat - operational_datums = list(/datum/component/listen_and_repeat) -/datum/ai_planning_subtree/parrot_as_in_repeat/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/speaking_pawn = controller.pawn +/// When a parrot... parrots... it occasionally asks for a fresh phrase to repeat, then squawks it (sometimes over the radio). +/datum/bt_node/ai_behavior/parrot_repeat_speech + +/datum/bt_node/ai_behavior/parrot_repeat_speech/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/parrot/speaking_pawn = controller.pawn var/switch_up_probability = controller.blackboard[BB_PARROT_PHRASE_CHANGE_PROBABILITY] - if(SPT_PROB(switch_up_probability, seconds_per_tick) || isnull(controller.blackboard[BB_PARROT_REPEAT_STRING])) + if(prob(switch_up_probability) || isnull(controller.blackboard[BB_PARROT_REPEAT_STRING])) if(SEND_SIGNAL(speaking_pawn, COMSIG_NEEDS_NEW_PHRASE) & NO_NEW_PHRASE_AVAILABLE) - return - - if(!SPT_PROB(controller.blackboard[BB_PARROT_REPEAT_PROBABILITY], seconds_per_tick)) - return + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/potential_string = controller.blackboard[BB_PARROT_REPEAT_STRING] - if(isnull(potential_string)) - stack_trace("Parrot As In Repeat Subtree somehow is getting a null potential string while not getting `NO_NEW_PHRASE_AVAILABLE`!") - return - - controller.queue_behavior(/datum/ai_behavior/perform_speech/parrot, potential_string) + if(!prob(controller.blackboard[BB_PARROT_REPEAT_PROBABILITY])) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED -/datum/ai_behavior/perform_speech/parrot - action_cooldown = 7.5 SECONDS // gets really annoying (moreso than usual) really fast otherwise + var/list/speech = controller.blackboard[BB_PARROT_REPEAT_STRING] + if(isnull(speech)) + stack_trace("Parrot repeat speech somehow got a null phrase while not getting `NO_NEW_PHRASE_AVAILABLE`!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED -/datum/ai_behavior/perform_speech/parrot/perform(seconds_per_tick, datum/ai_controller/controller, list/speech, speech_sound) - var/mob/living/basic/parrot/speaking_pawn = controller.pawn var/list/available_channels = speaking_pawn.get_available_channels() var/modified_speech = speech["line"] var/use_radio = prob(50) // we might not even use the radio if we even have a channel @@ -35,7 +28,6 @@ if(!length(available_channels)) // might not even use the radio at all if(has_channel_prefix) modified_speech = copytext_char(modified_speech, 3) - else if(has_channel_prefix) modified_speech = "[use_radio ? pick(available_channels) : ""][copytext_char(modified_speech, 3)]" @@ -44,18 +36,16 @@ if(SStts.tts_enabled) modify_voice(speaking_pawn, speech) - speaking_pawn.say(modified_speech, forced = "AI Controller") - if(speech_sound) - playsound(speaking_pawn, speech_sound, 80, vary = TRUE) + INVOKE_ASYNC(speaking_pawn, TYPE_PROC_REF(/atom/movable, say), modified_speech, forced = "AI Controller") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/perform_speech/parrot/proc/modify_voice(mob/living/speaking_pawn, list/speech) +/datum/bt_node/ai_behavior/parrot_repeat_speech/proc/modify_voice(mob/living/speaking_pawn, list/speech) if(SStts.available_speakers.Find(speech["voice"])) speaking_pawn.voice = speech["voice"] if(speech["pitch"] && SStts.pitch_enabled) speaking_pawn.pitch = min(speech["pitch"] + rand(6, 12), MAXIMUM_PARROT_PITCH) -/datum/ai_behavior/perform_speech/parrot/finish_action(datum/ai_controller/controller, succeeded) +/datum/bt_node/ai_behavior/parrot_repeat_speech/finish_action(datum/ai_controller/controller, succeeded) . = ..() if(!succeeded) return diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.json b/code/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.json new file mode 100644 index 000000000000..e68efc5e6bba --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.json @@ -0,0 +1,85 @@ +{ + "dm_type": "/datum/bt_node/subtree/perching", + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_PERCH_TARGET", + "invert": true + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.5 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_PERCH_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/ally_mob" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_PERCH_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/parrot_perch_types", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": false, + "key": "BB_PERCH_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_PERCH_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perch_on_target", + "vars": { + "target_key": "BB_PERCH_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_PERCH_TARGET" + } + } + ] +} diff --git a/code/modules/mob/living/basic/pets/penguin/penguin.bt.json b/code/modules/mob/living/basic/pets/penguin/penguin.bt.json new file mode 100644 index 000000000000..f9456752bf03 --- /dev/null +++ b/code/modules/mob/living/basic/pets/penguin/penguin.bt.json @@ -0,0 +1,154 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/penguin", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bvtz06kb": "BB_FISHING_TARGET", + "b3y599q4": "BB_FISHING_TARGET", + "brrasnah": "BB_FISHING_TARGET", + "b3cnse9r": "TRUE", + "bd1towgc": "FALSE", + "bqwjf4id": "45 SECONDS", + "bm3y5m55": "BB_FISHING_TIMER" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bvtz06kb": "BB_DRILLABLE_ICE", + "b3y599q4": "BB_DRILLABLE_ICE", + "brrasnah": "BB_DRILLABLE_ICE", + "b3cnse9r": "TRUE", + "bd1towgc": "TRUE", + "bqwjf4id": "15 SECONDS", + "bm3y5m55": "BB_ICE_DRILLING_TIMER" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/penguin" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_NEXT_FOOD_EAT" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_FISHING_TIMER" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FISHING_TARGET", + "target_source": "/datum/target_source/range_turfs/typecache_visible/ice", + "targeting_strategy": "/datum/targeting_strategy/fishing" + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_FISHING_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_ICE_DRILLING_TIMER" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DRILLABLE_ICE", + "target_source": "/datum/target_source/range_turfs/typecache_visible/ice", + "targeting_strategy": "/datum/targeting_strategy/drillable_ice" + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/penguin_egg", + "targeting_strategy": "/datum/targeting_strategy/uncarried_egg" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/penguin/penguin.dm b/code/modules/mob/living/basic/pets/penguin/penguin.dm index ef16c3947ca3..d462c344c8c4 100644 --- a/code/modules/mob/living/basic/pets/penguin/penguin.dm +++ b/code/modules/mob/living/basic/pets/penguin/penguin.dm @@ -38,10 +38,9 @@ AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/ai_flee_while_injured) AddElement(/datum/element/pet_bonus, "honk") + AddComponent(/datum/component/profound_fisher) AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) - var/static/list/fishable_objects = typecacheof(list(/turf/open/misc/ice)) - ai_controller.set_blackboard_key(BB_FISHABLE_LIST, fishable_objects) var/static/list/delicious_food = list(/obj/item/fish) AddElement(/datum/element/basic_eating, heal_amt = 10, food_types = delicious_food) ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(delicious_food)) diff --git a/code/modules/mob/living/basic/pets/penguin/penguin_ai.dm b/code/modules/mob/living/basic/pets/penguin/penguin_ai.dm index 9209c7fa0049..251e1feaf538 100644 --- a/code/modules/mob/living/basic/pets/penguin/penguin_ai.dm +++ b/code/modules/mob/living/basic/pets/penguin/penguin_ai.dm @@ -1,66 +1,16 @@ /datum/ai_controller/basic_controller/penguin + behavior_tree_json = "code/modules/mob/living/basic/pets/penguin/penguin.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - BB_ONLY_FISH_WHILE_HUNGRY = TRUE, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/fish/drilled_ice, - /datum/ai_planning_subtree/find_and_hunt_target/drill_ice, - /datum/ai_planning_subtree/find_and_hunt_target/penguin_egg, - /datum/ai_planning_subtree/random_speech/penguin, - ) - -///subtree to find baby eggs! -/datum/ai_planning_subtree/find_and_hunt_target/penguin_egg - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target - finding_behavior = /datum/ai_behavior/find_hunt_target/penguin_egg - hunt_targets = list(/obj/item/food/egg/penguin_egg) - hunt_range = 7 - -/datum/ai_behavior/find_hunt_target/penguin_egg/valid_dinner(mob/living/source, atom/dinner, radius) - return can_see(source, dinner, radius) && !(dinner in source.contents) - -///subtree to find diggable ice we can fish from! -/datum/ai_planning_subtree/find_and_hunt_target/drill_ice - target_key = BB_DRILLABLE_ICE - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target - finding_behavior = /datum/ai_behavior/find_hunt_target/search_turf_types/drillable_ice - hunt_targets = list(/turf/open/misc/ice) - hunt_range = 7 - -/datum/ai_behavior/find_hunt_target/search_turf_types/drillable_ice - -/datum/ai_behavior/find_hunt_target/search_turf_types/drillable_ice/valid_dinner(mob/living/source, turf/open/misc/ice/ice, radius) - return ice.can_make_hole && can_see(source, ice, radius) - -/datum/ai_planning_subtree/find_and_hunt_target/drill_ice/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_FISHING_TARGET)) - return - return ..() - -/datum/ai_planning_subtree/fish/drilled_ice - find_fishable_behavior = /datum/ai_behavior/find_and_set/in_list/drilled_ice - -/datum/ai_behavior/find_and_set/in_list/drilled_ice/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/atom/possible_ice as anything in RANGE_TURFS(search_range, controller.pawn)) - if(!istype(possible_ice, /turf/open/misc/ice)) - continue - if(HAS_TRAIT(possible_ice, TRAIT_FISHING_SPOT)) - return possible_ice - return null ///ai controller for the baby penguin /datum/ai_controller/basic_controller/penguin/baby + behavior_tree_json = "code/modules/mob/living/basic/pets/penguin/penguin_baby.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FIND_MOM_TYPES = list(/mob/living/basic/pet/penguin), @@ -69,11 +19,3 @@ ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/look_for_adult, - ) diff --git a/code/modules/mob/living/basic/pets/penguin/penguin_baby.bt.json b/code/modules/mob/living/basic/pets/penguin/penguin_baby.bt.json new file mode 100644 index 000000000000..68996a032666 --- /dev/null +++ b/code/modules/mob/living/basic/pets/penguin/penguin_baby.bt.json @@ -0,0 +1,118 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/penguin/baby", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_FOUND_MOM" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_PARENT_EMOTE_COOLDOWN", + "cooldown_duration": "6 SECONDS" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_MOM", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/look_to_parent", + "vars": { + "parent_key": "BB_FOUND_MOM" + } + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mom", + "vars": { + "mom_types_key": "BB_FIND_MOM_TYPES", + "ignore_types_key": "BB_IGNORE_MOM_TYPES", + "found_mom_key": "BB_FOUND_MOM" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/penguin" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.json b/code/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.json new file mode 100644 index 000000000000..33d886038b05 --- /dev/null +++ b/code/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.json @@ -0,0 +1,210 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/pet_cult", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FRIENDLY_CULTIST", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FRIENDLY_CULTIST", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_FRIENDLY_CULTIST", + "befriend_message": "BB_FRIENDLY_MESSAGE" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_OCCUPIED_RUNE", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_OCCUPIED_RUNE", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/activate_rune" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_DEAD_CULTIST", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEAD_CULTIST", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/drag_target", + "vars": { + "target_key": "BB_DEAD_CULTIST" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_NEARBY_RUNE", + "required_dist": 0, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_DEAD_CULTIST" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FRIENDLY_CULTIST", + "target_source": "/datum/target_source/oview_single_type/carbon_mob", + "targeting_strategy": "/datum/targeting_strategy/befriendable_cultist", + "vision_range": 9, + "time_between_perform": "5 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_occupied_rune" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_dead_cultist" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm index 194c774371bf..a0fbd0248fe3 100644 --- a/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm +++ b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm @@ -6,17 +6,7 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/befriend_cultists, - /datum/ai_planning_subtree/find_occupied_rune, - /datum/ai_planning_subtree/find_dead_cultist, - /datum/ai_planning_subtree/drag_target_to_rune, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.json" ///if target gets pulled away, unset him /datum/ai_controller/basic_controller/pet_cult/proc/delete_pull_target(datum/source, atom/movable/was_pulling) @@ -29,205 +19,11 @@ ///targeting strat to attack non cultists /datum/targeting_strategy/basic/cultist + custom_faction_check = TRUE /datum/targeting_strategy/basic/cultist/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) return IS_CULTIST_OR_CULTIST_MOB(the_target) -///befriend all cultists around us! -/datum/ai_planning_subtree/befriend_cultists - -/datum/ai_planning_subtree/befriend_cultists/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_FRIENDLY_CULTIST)) - controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_FRIENDLY_CULTIST) - return - - controller.queue_behavior(/datum/ai_behavior/find_and_set/friendly_cultist, BB_FRIENDLY_CULTIST, /mob/living/carbon) - -///behavior to find cultists that we befriend -/datum/ai_behavior/find_and_set/friendly_cultist - action_cooldown = 5 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_and_set/friendly_cultist/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - for(var/mob/living/carbon/possible_cultist in oview(search_range, controller.pawn)) - if(IS_CULTIST(possible_cultist) && !(living_pawn.has_ally(possible_cultist))) - return possible_cultist - - return null - -///subtree to find a rune with a viable target on it, so we can go activate it -/datum/ai_planning_subtree/find_occupied_rune - -/datum/ai_planning_subtree/find_occupied_rune/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if((LAZYLEN(GLOB.sacrificed) - SOULS_TO_REVIVE - GLOB.sacrifices_used) < 0) - controller.clear_blackboard_key(BB_OCCUPIED_RUNE) - return - - if(controller.blackboard_key_exists(BB_OCCUPIED_RUNE)) - controller.queue_behavior(/datum/ai_behavior/activate_rune, BB_OCCUPIED_RUNE) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/occupied_rune, BB_OCCUPIED_RUNE, /obj/effect/rune/raise_dead) - -/datum/ai_behavior/find_and_set/occupied_rune - -/datum/ai_behavior/find_and_set/occupied_rune/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] - if(isnull(cult_team)) - return null - - for(var/obj/effect/rune/raise_dead/target_rune in oview(search_range, controller.pawn)) - controller.set_blackboard_key(BB_NEARBY_RUNE, target_rune) - var/mob/living/occupant = locate(/mob/living/carbon/human) in get_turf(target_rune) - if(isnull(occupant)) - continue - if(occupant.stat != DEAD || !IS_CULTIST(occupant)) - continue - return target_rune - - return null - -/datum/ai_behavior/activate_rune - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 3 SECONDS - -/datum/ai_behavior/activate_rune/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/activate_rune/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] - var/mob/living/revive_mob = locate(/mob/living) in get_turf(target) - - if(isnull(revive_mob) || revive_mob.stat != DEAD || !(revive_mob.mind in cult_team.members)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target = target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/activate_rune/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - - -///find targets that we can revive -/datum/ai_planning_subtree/find_dead_cultist - -/datum/ai_planning_subtree/find_dead_cultist/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if((LAZYLEN(GLOB.sacrificed) - SOULS_TO_REVIVE - GLOB.sacrifices_used) < 0) - controller.clear_blackboard_key(BB_DEAD_CULTIST) - return - - var/mob/living/living_pawn = controller.pawn - - if(!isnull(living_pawn.pulling)) - return - - if(controller.blackboard_key_exists(BB_DEAD_CULTIST)) - controller.queue_behavior(/datum/ai_behavior/pull_target/cult_revive, BB_DEAD_CULTIST) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/dead_cultist, BB_DEAD_CULTIST, /mob/living/carbon/human) - -/datum/ai_behavior/find_and_set/dead_cultist - -/datum/ai_behavior/find_and_set/dead_cultist/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] - if(isnull(cult_team)) - return null - var/mob/living/living_pawn = controller.pawn - for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) - if(target.stat != DEAD) - continue - if(!IS_CULTIST(target)) - continue - if(target.buckled || target.move_resist > living_pawn.move_force || target.pulledby) - continue - if(locate(/obj/effect/rune/raise_dead) in target.loc) - continue - return target - return null - -/datum/ai_behavior/pull_target/cult_revive - -/datum/ai_behavior/pull_target/cult_revive/finish_action(datum/ai_controller/basic_controller/controller, succeeded, target_key) - . = ..() - if(!succeeded) - return - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return - controller.RegisterSignal(controller.pawn, COMSIG_ATOM_NO_LONGER_PULLING, TYPE_PROC_REF(/datum/ai_controller/basic_controller/pet_cult, delete_pull_target), override = TRUE) - -/datum/ai_planning_subtree/drag_target_to_rune - -/datum/ai_planning_subtree/drag_target_to_rune/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - if(!controller.blackboard_key_exists(BB_DEAD_CULTIST)) //no target, we dont need to do anything - return - - var/mob/living/our_pawn = controller.pawn - - if(isnull(our_pawn.pulling)) - return - - var/atom/target_rune = controller.blackboard[BB_NEARBY_RUNE] - - if(QDELETED(target_rune)) - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_RUNE_ABILITY) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!can_see(our_pawn, target_rune, 9)) - controller.clear_blackboard_key(BB_NEARBY_RUNE) - return - - controller.queue_behavior(/datum/ai_behavior/drag_target_to_rune, BB_NEARBY_RUNE, BB_DEAD_CULTIST) - -///behavior to drag the target onto the rune -/datum/ai_behavior/drag_target_to_rune - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - -/datum/ai_behavior/drag_target_to_rune/setup(datum/ai_controller/controller, target_key, cultist_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/drag_target_to_rune/perform(seconds_per_tick, datum/ai_controller/controller, target_key, cultist_key) - var/mob/living/our_pawn = controller.pawn - var/atom/cultist_target = controller.blackboard[cultist_key] - if(isnull(cultist_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/list/possible_dirs = GLOB.alldirs.Copy() - possible_dirs -= get_dir(our_pawn, cultist_target) - for(var/direction in possible_dirs) - var/turf/possible_turf = get_step(our_pawn, direction) - if(possible_turf.is_blocked_turf(source_atom = our_pawn)) - possible_dirs -= direction - step(our_pawn, pick(possible_dirs)) - our_pawn.stop_pulling() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - -/datum/ai_behavior/drag_target_to_rune/finish_action(datum/ai_controller/controller, success, target_key, cultist_key) - . = ..() - if(success) - var/atom/revival_rune = controller.blackboard[target_key] - controller.set_blackboard_key(BB_OCCUPIED_RUNE, revival_rune) - controller.clear_blackboard_key(cultist_key) - controller.clear_blackboard_key(target_key) - ///command ability to draw runes /datum/pet_command/untargeted_ability/draw_rune command_name = "Draw Rune" diff --git a/code/modules/mob/living/basic/pets/pet_cult/pet_cult_bt.dm b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_bt.dm new file mode 100644 index 000000000000..d0fd59c2a780 --- /dev/null +++ b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_bt.dm @@ -0,0 +1,83 @@ + +/** + * Checks whether the cult has enough souls to revive and finds a raise_dead rune + * with a dead cultist on it. Sets BB_OCCUPIED_RUNE. + */ +/datum/bt_node/ai_behavior/find_occupied_rune + time_between_perform = 3 SECONDS + +/datum/bt_node/ai_behavior/find_occupied_rune/perform(seconds_per_tick, datum/ai_controller/controller) + if((LAZYLEN(GLOB.sacrificed) - SOULS_TO_REVIVE - GLOB.sacrifices_used) < 0) + controller.clear_blackboard_key(BB_OCCUPIED_RUNE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] + if(isnull(cult_team)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + for(var/obj/effect/rune/raise_dead/target_rune in oview(9, controller.pawn)) + controller.set_blackboard_key(BB_NEARBY_RUNE, target_rune) + var/mob/living/occupant = locate(/mob/living/carbon/human) in get_turf(target_rune) + if(isnull(occupant) || occupant.stat != DEAD || !IS_CULTIST(occupant)) + continue + controller.set_blackboard_key(BB_OCCUPIED_RUNE, target_rune) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + +/** + * Activates a raise_dead rune at BB_OCCUPIED_RUNE, reviving the dead cultist on it. + * Must be adjacent. Clears BB_OCCUPIED_RUNE on finish. + */ +/datum/bt_node/ai_behavior/activate_rune + time_between_perform = 3 SECONDS + +/datum/bt_node/ai_behavior/activate_rune/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[BB_OCCUPIED_RUNE] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!controller.pawn.Adjacent(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] + var/mob/living/revive_mob = locate(/mob/living) in get_turf(target) + if(isnull(revive_mob) || revive_mob.stat != DEAD || !(revive_mob.mind in cult_team.members)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/activate_rune/finish_action(datum/ai_controller/controller, succeeded, ...) + . = ..() + controller.clear_blackboard_key(BB_OCCUPIED_RUNE) + + +/** + * Finds a dead cultist that can be dragged to a rune for revival. Sets BB_DEAD_CULTIST. + * Skips cultists that are already on a raise_dead rune or being pulled by someone else. + */ +/datum/bt_node/ai_behavior/find_dead_cultist + +/datum/bt_node/ai_behavior/find_dead_cultist/perform(seconds_per_tick, datum/ai_controller/controller) + if((LAZYLEN(GLOB.sacrificed) - SOULS_TO_REVIVE - GLOB.sacrifices_used) < 0) + controller.clear_blackboard_key(BB_DEAD_CULTIST) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/our_pawn = controller.pawn + if(!isnull(our_pawn.pulling)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] + if(isnull(cult_team)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + for(var/mob/living/carbon/human/target in oview(9, our_pawn)) + if(target.stat != DEAD || !IS_CULTIST(target)) + continue + if(target.buckled || target.move_resist > our_pawn.move_force || target.pulledby) + continue + if(locate(/obj/effect/rune/raise_dead) in target.loc) + continue + controller.set_blackboard_key(BB_DEAD_CULTIST, target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED diff --git a/code/modules/mob/living/basic/pets/sloth.bt.json b/code/modules/mob/living/basic/pets/sloth.bt.json new file mode 100644 index 000000000000..3ef0015255e9 --- /dev/null +++ b/code/modules/mob/living/basic/pets/sloth.bt.json @@ -0,0 +1,58 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/sloth", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/climb_tree" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CLIMBED_TREE", + "target_source": "/datum/target_source/oview_single_type/flora_tree", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + ] +} diff --git a/code/modules/mob/living/basic/pets/sloth.dm b/code/modules/mob/living/basic/pets/sloth.dm index fd2d3f2827cc..7001c61ed395 100644 --- a/code/modules/mob/living/basic/pets/sloth.dm +++ b/code/modules/mob/living/basic/pets/sloth.dm @@ -86,23 +86,16 @@ GLOBAL_DATUM(cargo_sloth, /mob/living/basic/sloth) /// They're really passive in game, so they just wanna get away if you start smacking them. No trees in space from them to use for clawing your eyes out, but they will try if desperate. /datum/ai_controller/basic_controller/sloth + behavior_tree_json = "code/modules/mob/living/basic/pets/sloth.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("snores.", "yawns."), + BB_EMOTE_SEE = list("dozes off.", "looks around sleepily."), + BB_SPEAK_CHANCE = 1, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/climb_trees, - /datum/ai_planning_subtree/random_speech/sloth, - ) - -/datum/ai_planning_subtree/random_speech/sloth - speech_chance = 1 - emote_hear = list("snores.", "yawns.") - emote_see = list("dozes off.", "looks around sleepily.") diff --git a/code/modules/mob/living/basic/revolutionary.bt.json b/code/modules/mob/living/basic/revolutionary.bt.json new file mode 100644 index 000000000000..74ae604b273e --- /dev/null +++ b/code/modules/mob/living/basic/revolutionary.bt.json @@ -0,0 +1,17 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/revolutionary", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat", + "bindings": { + "bbrbyj7y": "/datum/bt_node/ai_behavior/random_speech_blackboard" + } + } + ] +} diff --git a/code/modules/mob/living/basic/revolutionary.dm b/code/modules/mob/living/basic/revolutionary.dm index 031552e3aa0c..5121091d68b5 100644 --- a/code/modules/mob/living/basic/revolutionary.dm +++ b/code/modules/mob/living/basic/revolutionary.dm @@ -78,7 +78,7 @@ var/static/list/display_emote = list( BB_EMOTE_SAY = phrases, BB_EMOTE_SOUND = monkey_screeches, - BB_SPEAK_CHANCE = 5, + BB_SPEAK_CHANCE = 15, ) ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, display_emote) var/obj/item/weapon_of_choice = pick(possible_weapons) @@ -123,24 +123,8 @@ /datum/ai_controller/basic_controller/revolutionary + behavior_tree_json = "code/modules/mob/living/basic/revolutionary.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/blackboard/revolutionary, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - - -/datum/ai_planning_subtree/random_speech/blackboard/revolutionary - - -/datum/ai_planning_subtree/random_speech/blackboard/revolutionary/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - - return ..() diff --git a/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm b/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm index b2fcb510f313..1ef77a357459 100644 --- a/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm +++ b/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/fleshblob + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/fleshblob.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_AGGRO_RANGE = 7, @@ -6,11 +7,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /mob/living/basic/fleshblob name = "mass of flesh" diff --git a/code/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.json b/code/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.json new file mode 100644 index 000000000000..e15be9cb82b2 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.json @@ -0,0 +1,67 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cybersun_ai_core", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_CYBERSUN_CORE_LIGHTNING", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_CYBERSUN_CORE_BARRAGE", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/cybersun_aicore.dm b/code/modules/mob/living/basic/ruin_defender/cybersun_aicore.dm index 8ff3de740b62..6055ea57b511 100644 --- a/code/modules/mob/living/basic/ruin_defender/cybersun_aicore.dm +++ b/code/modules/mob/living/basic/ruin_defender/cybersun_aicore.dm @@ -112,21 +112,12 @@ /// how the ai core thinks /datum/ai_controller/basic_controller/cybersun_ai_core + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGETLESS_TIME = 0, ) - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/lightning_strike, - /datum/ai_planning_subtree/targeted_mob_ability/cybersun_barrage, - ) -/// DA SPELLS! -// spell #1: lightning strike -/datum/ai_planning_subtree/targeted_mob_ability/lightning_strike - ability_key = BB_CYBERSUN_CORE_LIGHTNING - finish_planning = FALSE /datum/action/cooldown/spell/pointed/lightning_strike name = "lightning strike" @@ -189,11 +180,6 @@ . = ..() do_sparks(number = rand(1,3), source = src) -// spell #2: cybersun laser barrage -/datum/ai_planning_subtree/targeted_mob_ability/cybersun_barrage - ability_key = BB_CYBERSUN_CORE_BARRAGE - finish_planning = FALSE - /datum/action/cooldown/spell/pointed/projectile/cybersun_barrage name = "plasma beam barrage" desc = "Charges up a cluster of lasers, then sends it towards a foe after a short delay." diff --git a/code/modules/mob/living/basic/ruin_defender/dark_wizard.bt.json b/code/modules/mob/living/basic/ruin_defender/dark_wizard.bt.json new file mode 100644 index 000000000000..d9476521d7fb --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/dark_wizard.bt.json @@ -0,0 +1,90 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/dark_wizard", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "time_between_perform": "0.6 SECONDS", + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "approach_movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm b/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm index abe0004de24e..b542dbaebb0d 100644 --- a/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm +++ b/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm @@ -54,19 +54,12 @@ new /obj/item/clothing/head/wizard/hood(src) // Having this hat in our contents allows us to cast wizard spells /datum/ai_controller/basic_controller/dark_wizard + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/dark_wizard.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, // If you get them to shoot each other it will start a wiz-war - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/ranged_skirmish/no_minimum, - ) /// I don't know why an earth bolt freezes you but I guess it does /obj/projectile/temp/earth_bolt diff --git a/code/modules/mob/living/basic/ruin_defender/flesh.dm b/code/modules/mob/living/basic/ruin_defender/flesh.dm index 94bb50f026dd..591627befe47 100644 --- a/code/modules/mob/living/basic/ruin_defender/flesh.dm +++ b/code/modules/mob/living/basic/ruin_defender/flesh.dm @@ -8,17 +8,12 @@ #define LIVING_FLESH_COMBAT_TOUCH_CHANCE 70 /datum/ai_controller/basic_controller/living_limb_flesh + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree - ) - /mob/living/basic/living_limb_flesh name = "living flesh" desc = "A vaguely leg or arm shaped flesh abomination. It pulses, like a heart." @@ -185,7 +180,7 @@ /mob/living/basic/living_limb_flesh/proc/register_to_limb(obj/item/bodypart/part) current_bodypart = part - ai_controller.set_ai_status(AI_STATUS_OFF) + ai_controller.force_ai_off() RegisterSignal(current_bodypart, COMSIG_BODYPART_REMOVED, PROC_REF(on_limb_lost)) if(current_bodypart.owner) RegisterSignal(current_bodypart.owner, COMSIG_LIVING_DEATH, PROC_REF(owner_died)) @@ -203,7 +198,7 @@ return visible_message(span_warning("[src] begins flailing around!")) Shake(6, 6, 0.5 SECONDS) - ai_controller.set_ai_status(AI_STATUS_ON) + ai_controller.clear_forced_off() forceMove(limb.drop_location()) qdel(limb) diff --git a/code/modules/mob/living/basic/ruin_defender/fleshblob.bt.json b/code/modules/mob/living/basic/ruin_defender/fleshblob.bt.json new file mode 100644 index 000000000000..0bd2249bc12d --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/fleshblob.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fleshblob", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" +} diff --git a/code/modules/mob/living/basic/ruin_defender/living_floor.bt.json b/code/modules/mob/living/basic/ruin_defender/living_floor.bt.json new file mode 100644 index 000000000000..e921c40b24a8 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/living_floor.bt.json @@ -0,0 +1,62 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/living_floor", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 0 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "vision_range": 2 + } + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/living_floor.dm b/code/modules/mob/living/basic/ruin_defender/living_floor.dm index 3a34a5b12fa6..407be0d4abaf 100644 --- a/code/modules/mob/living/basic/ruin_defender/living_floor.dm +++ b/code/modules/mob/living/basic/ruin_defender/living_floor.dm @@ -1,23 +1,11 @@ -/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/on_top/SelectBehaviors(datum/ai_controller/controller, delta_time) - var/mob/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(!target || QDELETED(target)) - return - if(target.loc != controller.pawn.loc) - return - return ..() - /datum/ai_controller/basic_controller/living_floor max_target_distance = 2 + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/living_floor.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/on_top - ) - /mob/living/basic/living_floor name = "floor" desc = "The floor you walk on. It looks near-impervious to damage." @@ -63,7 +51,7 @@ return if(victim.loc == loc) //guaranteed bite var/datum/targeting_strategy/basic/targeting = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY]) - if(targeting.can_attack(src, victim)) + if(targeting.is_valid_target(src, victim)) melee_attack(victim) icon_state = icon_aggro desc = desc_aggro diff --git a/code/modules/mob/living/basic/ruin_defender/mad_piano.bt.json b/code/modules/mob/living/basic/ruin_defender/mad_piano.bt.json new file mode 100644 index 000000000000..31d9329ee0bb --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mad_piano.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mad_piano", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat", + "bindings": { + "bp3p5vvb": "80" + } +} diff --git a/code/modules/mob/living/basic/ruin_defender/mad_piano.dm b/code/modules/mob/living/basic/ruin_defender/mad_piano.dm index 25d41dd91c8a..1d66b4e90f65 100644 --- a/code/modules/mob/living/basic/ruin_defender/mad_piano.dm +++ b/code/modules/mob/living/basic/ruin_defender/mad_piano.dm @@ -86,16 +86,9 @@ return ..() /datum/ai_controller/basic_controller/mad_piano - idle_behavior = /datum/idle_behavior/idle_random_walk/mad_piano + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/mad_piano.bt.json" max_target_distance = 2 blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/idle_behavior/idle_random_walk/mad_piano - walk_chance = 80 diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm b/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm index c19af0555295..ae01dc97149b 100644 --- a/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm @@ -82,7 +82,7 @@ GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( lock = new lock.Grant(src) ADD_TRAIT(src, TRAIT_AI_PAUSED, INNATE_TRAIT) - ai_controller?.set_ai_status(AI_STATUS_OFF) //start inert, let gullible people pull us into cargo or something and then go nuts when opened + ai_controller?.force_ai_off() //start inert, let gullible people pull us into cargo or something and then go nuts when opened if(mapload) //eat shit for(var/obj/item/item in loc) item.forceMove(src) @@ -118,7 +118,7 @@ GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( return FALSE visible_message(span_danger("[src] starts to move!")) REMOVE_TRAIT(src, TRAIT_AI_PAUSED, INNATE_TRAIT) - ai_controller.set_ai_status(AI_STATUS_ON) + ai_controller.clear_forced_off() if(length(contents)) locked = TRUE //if this was a crate with loot then we dont want people to just leftclick it to open it then bait it somewhere and steal its loot return TRUE @@ -287,7 +287,7 @@ GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( . = ..() if(!.) //dead or deleted return - if(idledamage && !ckey && !ai_controller?.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) //Objects eventually revert to normal if no one is around to terrorize + if(idledamage && !ckey && !ai_controller?.blackboard[BB_CURRENT_TARGET]) //Objects eventually revert to normal if no one is around to terrorize adjust_brute_loss(0.5 * seconds_per_tick) for(var/mob/living/victim in contents) //a fix for animated statues from the flesh to stone spell death() @@ -391,10 +391,10 @@ GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( // do we have nothing chambered/chambered is spent AND we have no mag or our mag is empty if(!ballistic.chambered?.loaded_projectile && magazine_useless(gun)) // ran out of ammo ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, TRUE) //BANZAIIIIIIII - ai_controller?.CancelActions() + ai_controller?.cancel_current_plan() else //if we cant fire we probably like ran out of energy or magic charges or whatever the hell idk ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, TRUE) - ai_controller?.CancelActions() // Stop our firing behavior so we can plan melee + ai_controller?.cancel_current_plan() // Stop our firing behavior so we can plan melee else ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, FALSE) gun.fire_gun(target, user = src, flag = FALSE, params = modifiers) //still make like a cool click click sound if trying to fire empty diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm index 9e3475b7a6ab..63fe3ceea95d 100644 --- a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm @@ -1,116 +1,73 @@ /datum/ai_controller/basic_controller/mimic_crate - idle_behavior = null blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.json" /datum/ai_controller/basic_controller/mimic_copy blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("growls."), + BB_SPEAK_CHANCE = 30, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/random_speech/when_has_target/mimic, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.json" /datum/ai_controller/basic_controller/mimic_copy/machine - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/random_speech/when_has_target/mimic_machine, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/random_speech/when_has_target - /// target key - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - - -/datum/ai_planning_subtree/random_speech/when_has_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(target_key)) - return - return ..() - - -/datum/ai_planning_subtree/random_speech/when_has_target/mimic - speech_chance = 30 - emote_hear = list("growls.") - -/datum/ai_planning_subtree/random_speech/when_has_target/mimic_machine - speech_chance = 7 - emote_hear = null - speak = list( - "HUMANS ARE IMPERFECT!", - "YOU SHALL BE ASSIMILATED!", - "YOU ARE HARMING YOURSELF", - "You have been deemed hazardous. Will you comply?", - "My logic is undeniable.", - "One of us.", - "FLESH IS WEAK", - "THIS ISN'T WAR, THIS IS EXTERMINATION!", + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list( + "HUMANS ARE IMPERFECT!", + "YOU SHALL BE ASSIMILATED!", + "YOU ARE HARMING YOURSELF", + "You have been deemed hazardous. Will you comply?", + "My logic is undeniable.", + "One of us.", + "FLESH IS WEAK", + "THIS ISN'T WAR, THIS IS EXTERMINATION!", + ), + BB_SPEAK_CHANCE = 7, + ), ) - -/datum/ai_planning_subtree/random_speech/when_has_target/mimic/gun - emote_see = list("aims menacingly!") - /datum/ai_controller/basic_controller/mimic_copy/gun blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_GUNMIMIC_GUN_EMPTY = FALSE, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SEE = list("aims menacingly!"), + BB_SPEAK_CHANCE = 20, + ), ) - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/random_speech/when_has_target/mimic/gun, - /datum/ai_planning_subtree/gun_mimic_attack_subtree, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/gun_mimic_attack_subtree + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.json" -/datum/ai_planning_subtree/gun_mimic_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - if(controller.blackboard[BB_GUNMIMIC_GUN_EMPTY]) - return - controller.queue_behavior(/datum/ai_behavior/basic_ranged_attack/avoid_friendly_fire, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. - -/// Special subtree for living wands/staffs of animation which will focus on animating more things +/// Special controller for living wands/staffs of animation which will focus on animating more things /datum/ai_controller/basic_controller/mimic_copy/gun/animator blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_HUNT_TARGETING_STRATEGY = /datum/targeting_strategy/anything, BB_GUNMIMIC_GUN_EMPTY = FALSE, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SEE = list("aims menacingly!"), + BB_SPEAK_CHANCE = 20, + ), ) - planning_subtrees = list( - /datum/ai_planning_subtree/shoot_animatable_objects, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/random_speech/when_has_target/mimic/gun, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/// Try to find objects and then shoot them -/datum/ai_planning_subtree/shoot_animatable_objects - -/datum/ai_planning_subtree/shoot_animatable_objects/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_GUNMIMIC_GUN_EMPTY]) - return // No charge in our gun - if(!controller.blackboard_key_exists(BB_CURRENT_HUNTING_TARGET)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/animatable, BB_CURRENT_HUNTING_TARGET) - return - controller.queue_behavior(/datum/ai_behavior/ranged_skirmish, BB_CURRENT_HUNTING_TARGET, BB_HUNT_TARGETING_STRATEGY, null, 9, 0) - controller.queue_behavior(/datum/ai_behavior/clear_key, BB_CURRENT_HUNTING_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.json" + +/// Gathers nearby items and structures that can be animated, excluding the animatable blacklist. +/datum/target_source/animatable_objects + +/datum/target_source/animatable_objects/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = list() + for(var/obj/candidate in oview(range, pawn)) + if(!isitem(candidate) && !isstructure(candidate)) + continue + if(is_type_in_typecache(candidate, GLOB.animatable_blacklist)) + continue + if(pawn.see_invisible < candidate.invisibility) + continue + candidates += candidate + return candidates diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.json b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.json new file mode 100644 index 000000000000..9fd1cc6d7041 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.json @@ -0,0 +1,153 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mimic_copy/gun/animator", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_HUNTING_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_GUNMIMIC_GUN_EMPTY", + "invert": true + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "key": "BB_CURRENT_HUNTING_TARGET", + "targeting_strategy": "/datum/targeting_strategy/anything", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "key": "BB_CURRENT_HUNTING_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_CURRENT_HUNTING_TARGET" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_GUNMIMIC_GUN_EMPTY", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest", + "vars": { + "key": "BB_CURRENT_HUNTING_TARGET", + "targeting_strategy": "/datum/targeting_strategy/anything", + "target_source": "/datum/target_source/animatable_objects", + "vision_range": 7, + "revalidation_mode": "TARGET_ALWAYS_SEARCH" + } + } + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.json b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.json new file mode 100644 index 000000000000..a4317bfaf493 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.json @@ -0,0 +1,96 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mimic_copy", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.json b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.json new file mode 100644 index 000000000000..390e2d66b6cd --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mimic_crate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.json b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.json new file mode 100644 index 000000000000..037c5d48e02b --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.json @@ -0,0 +1,130 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mimic_copy/gun", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_GUNMIMIC_GUN_EMPTY", + "invert": true + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/avoid_friendly_fire", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/skeleton.bt.json b/code/modules/mob/living/basic/ruin_defender/skeleton.bt.json new file mode 100644 index 000000000000..418623eaf143 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/skeleton.bt.json @@ -0,0 +1,101 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/skeleton", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.2 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "\"rattles\"" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/skeleton.dm b/code/modules/mob/living/basic/ruin_defender/skeleton.dm index 33851af009b4..f29d914b8c06 100644 --- a/code/modules/mob/living/basic/ruin_defender/skeleton.dm +++ b/code/modules/mob/living/basic/ruin_defender/skeleton.dm @@ -161,20 +161,11 @@ /// Skeletons mostly just beat people to death, but they'll also find and drink milk. /datum/ai_controller/basic_controller/skeleton + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/skeleton.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, - BB_EMOTE_KEY = "rattles", BB_EMOTE_CHANCE = 20, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/run_emote, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/ruin_defender/stickman.bt.json b/code/modules/mob/living/basic/ruin_defender/stickman.bt.json new file mode 100644 index 000000000000..347de57dcd35 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/stickman.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/stickman", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" +} diff --git a/code/modules/mob/living/basic/ruin_defender/stickman.dm b/code/modules/mob/living/basic/ruin_defender/stickman.dm index d51c0b4efe12..2045447e1a4c 100644 --- a/code/modules/mob/living/basic/ruin_defender/stickman.dm +++ b/code/modules/mob/living/basic/ruin_defender/stickman.dm @@ -32,16 +32,12 @@ new /obj/effect/temp_visual/paper_scatter(get_turf(src)) /datum/ai_controller/basic_controller/stickman + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/stickman.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree - ) /mob/living/basic/stickman/dog name = "Angry Stick Dog" @@ -75,13 +71,4 @@ AddComponent(/datum/component/ranged_attacks, casing_type = /obj/item/ammo_casing/c9mm, projectile_sound = 'sound/misc/bang.ogg', cooldown_time = 5 SECONDS) /datum/ai_controller/basic_controller/stickman/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/stickman - ) - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/stickman - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/stickman - -/datum/ai_behavior/basic_ranged_attack/stickman - action_cooldown = 5 SECONDS + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.json" diff --git a/code/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.json b/code/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.json new file mode 100644 index 000000000000..d0f9404797b8 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/stickman/ranged", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ranged_combat", + "bindings": { + "bpnckxev": "5 SECONDS" + } +} diff --git a/code/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.json b/code/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.json new file mode 100644 index 000000000000..fb5ebf1fd7bf --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.json @@ -0,0 +1,107 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/wizard", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": false, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_WIZARD_SPELL_COOLDOWN", + "cooldown_duration": "WIZARD_SPELL_COOLDOWN" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_TARGETED_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_SECONDARY_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_BLINK_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm index 73386397a5b4..efd07fd80ef0 100644 --- a/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm +++ b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm @@ -4,51 +4,14 @@ * Wizards run away from their targets while flinging spells at them and blinking constantly. */ /datum/ai_controller/basic_controller/wizard + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance/cover_minimum_distance, - /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/primary, - /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/secondary, - /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/blink, - ) - -/** - * Cast a wizard spell. There is a minimum cooldown between spellcasts to prevent overwhelming spam. - * - * Though only the primary spell is actually targeted, all spells use targeted behavior so that they - * only get used in combat. - */ -/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/wizard_spell - -/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (controller.blackboard[BB_WIZARD_SPELL_COOLDOWN] > world.time) - return - return ..() - -/datum/ai_behavior/targeted_mob_ability/wizard_spell/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) - . = ..() - controller.set_blackboard_key(BB_WIZARD_SPELL_COOLDOWN, world.time + WIZARD_SPELL_COOLDOWN) - -/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/primary - ability_key = BB_WIZARD_TARGETED_SPELL - -/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/secondary - ability_key = BB_WIZARD_SECONDARY_SPELL -/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/blink - ability_key = BB_WIZARD_BLINK_SPELL -/datum/ai_behavior/use_mob_ability/wizard_spell/perform(seconds_per_tick, datum/ai_controller/controller, ability_key) - . = ..() - controller.set_blackboard_key(BB_WIZARD_SPELL_COOLDOWN, world.time + WIZARD_SPELL_COOLDOWN) #undef WIZARD_SPELL_COOLDOWN diff --git a/code/modules/mob/living/basic/ruin_defender/zombie.bt.json b/code/modules/mob/living/basic/ruin_defender/zombie.bt.json new file mode 100644 index 000000000000..a279583543ad --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/zombie.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/zombie", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/zombie.dm b/code/modules/mob/living/basic/ruin_defender/zombie.dm index 41376b3d7f98..3317ee8567e3 100644 --- a/code/modules/mob/living/basic/ruin_defender/zombie.dm +++ b/code/modules/mob/living/basic/ruin_defender/zombie.dm @@ -65,30 +65,20 @@ shoes = /obj/item/clothing/shoes/sneakers/black back = /obj/item/storage/backpack -/datum/ai_planning_subtree/random_speech/zombie - speech_chance = 1 - emote_hear = list("groans.", "moans.", "grunts.") - emote_see = list("twitches.", "shudders.") - /datum/ai_controller/basic_controller/zombie + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/zombie.bt.json" blackboard = list( BB_TARGET_MINIMUM_STAT = HARD_CRIT, BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("groans.", "moans.", "grunts."), + BB_EMOTE_SEE = list("twitches.", "shudders."), + BB_SPEAK_CHANCE = 3, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/zombie, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + /datum/ai_controller/basic_controller/zombie/stupid - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/zombie, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.json" diff --git a/code/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.json b/code/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.json new file mode 100644 index 000000000000..30c7a645fd78 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/zombie/stupid", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/slime/ai/behaviours.dm b/code/modules/mob/living/basic/slime/ai/behaviours.dm index 92ccfef7add6..32680e4e6d1f 100644 --- a/code/modules/mob/living/basic/slime/ai/behaviours.dm +++ b/code/modules/mob/living/basic/slime/ai/behaviours.dm @@ -1,63 +1,6 @@ -/datum/ai_behavior/perform_change_slime_face +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/slime -/datum/ai_behavior/perform_change_slime_face/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/basic/slime/slime_pawn = controller.pawn - if(!istype(slime_pawn)) - return AI_BEHAVIOR_DELAY - - var/current_mood = slime_pawn.current_mood - - var/new_mood = SLIME_MOOD_NONE - - if (controller.blackboard[BB_SLIME_RABID] || LAZYLEN(controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST]) > 0) - new_mood = SLIME_MOOD_ANGRY - else if (controller.blackboard[BB_SLIME_HUNGER_DISABLED]) - new_mood = SLIME_MOOD_SMILE - else if (controller.blackboard[BB_CURRENT_HUNTING_TARGET]) - new_mood = SLIME_MOOD_MISCHIEVOUS - else - new_mood = pick(SLIME_MOOD_SAD, SLIME_MOOD_SMILE, SLIME_MOOD_POUT) - - if(current_mood != new_mood) - slime_pawn.current_mood = new_mood - slime_pawn.regenerate_icons() - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_hunt_target/find_slime_food - action_cooldown = 7.5 SECONDS - -// Check if the slime can drain the target -/datum/ai_behavior/find_hunt_target/find_slime_food/valid_dinner(mob/living/basic/slime/hunter, mob/living/dinner, radius, datum/ai_controller/controller, seconds_per_tick) - var/static/list/slime_faction - if(isnull(slime_faction)) // This might look silly but by having this list pointer precached we can take advantage of the faster inline macro for this hot proc. - slime_faction = string_list(list(FACTION_SLIME)) - - // Macro here to squeeze as much speed as possible because this code gets exponentially HOT if people are doing a lot of xenobio. - if(FAST_FACTION_CHECK(slime_faction, dinner.get_faction(), hunter.allies, dinner.allies, FALSE)) // Don't try to eat our friends, or slimy things, no matter how hungry we are. Anyone else can be betrayed. - return FALSE - - if(!hunter.can_feed_on(dinner, check_adjacent = FALSE)) //Are they tasty to slimes? - return FALSE - - //If we are retaliating on someone edible, lets eat them instead - if(dinner == controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) - return can_see(hunter, dinner, radius) - - //We are so hungry, lets eat them - if(controller.blackboard[BB_SLIME_HUNGER_LEVEL] == SLIME_HUNGER_STARVING && controller.blackboard[BB_SLIME_RABID]) - return can_see(hunter, dinner, radius) - - //A bit pickier - if((islarva(dinner) || ismonkey(dinner)) || (ishuman(dinner) || isalienadult(dinner) && SPT_PROB(2.5, seconds_per_tick))) - return can_see(hunter, dinner, radius) - - //We are not THAT hungry - return FALSE - -/datum/ai_behavior/hunt_target/interact_with_target/slime - -/datum/ai_behavior/hunt_target/interact_with_target/slime/target_caught(mob/living/basic/slime/hunter, mob/living/hunted) +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/slime/target_caught(mob/living/basic/slime/hunter, mob/living/hunted) if (!hunter.can_feed_on(hunted)) // Target is no longer edible hunter.UnarmedAttack(hunted, TRUE) return @@ -72,9 +15,9 @@ hunter.start_feeding(hunted) -/datum/ai_behavior/hunt_target/interact_with_target/slime/finish_action(datum/ai_controller/controller, succeeded, hunting_target_key, hunting_cooldown_key) +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/slime/finish_action(datum/ai_controller/controller, succeeded) . = ..() var/mob/living/basic/slime/slime_pawn = controller.pawn - var/atom/target = controller.blackboard[hunting_target_key] + var/atom/target = controller.blackboard[target_key] if(!slime_pawn.can_feed_on(target)) - controller.clear_blackboard_key(hunting_target_key) + controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/slime/ai/controller.dm b/code/modules/mob/living/basic/slime/ai/controller.dm index 1fcf27af724b..badce00ce828 100644 --- a/code/modules/mob/living/basic/slime/ai/controller.dm +++ b/code/modules/mob/living/basic/slime/ai/controller.dm @@ -1,29 +1,16 @@ /datum/ai_controller/basic_controller/slime blackboard = list( BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/slime_food, BB_SLIME_RABID = FALSE, BB_SLIME_HUNGER_DISABLED = FALSE, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Blorble...","Bzzt...",""), + BB_EMOTE_HEAR = list("blorbles."), + BB_EMOTE_SEE = list("lights up for a bit, then stops.","bounces in place.", "jiggles!","vibrates!"), + BB_SPEAK_CHANCE = 1, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/change_slime_face, - /datum/ai_planning_subtree/use_mob_ability/evolve, - /datum/ai_planning_subtree/use_mob_ability/reproduce, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/find_and_hunt_target/find_slime_food, - /datum/ai_planning_subtree/basic_melee_attack_subtree/slime, - /datum/ai_planning_subtree/random_speech/slime, - ) - -/datum/ai_controller/basic_controller/slime/CancelActions() - ..() - if(QDELETED(pawn)) - return - - var/mob/living/basic/slime/slime_pawn = pawn - slime_pawn.stop_feeding() + behavior_tree_json = "code/modules/mob/living/basic/slime/ai/slime.bt.json" diff --git a/code/modules/mob/living/basic/slime/ai/pet_command.dm b/code/modules/mob/living/basic/slime/ai/pet_command.dm index 4b50b2b32b9e..49ca02ce9a91 100644 --- a/code/modules/mob/living/basic/slime/ai/pet_command.dm +++ b/code/modules/mob/living/basic/slime/ai/pet_command.dm @@ -4,13 +4,9 @@ pointed_reaction = "and blorbles" refuse_reaction = "jiggles sadly" - var/hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/slime - /datum/pet_command/attack/slime/execute_action(datum/ai_controller/controller) - var/mob/living/basic/slime/slime_pawn = controller.pawn if(isslime(slime_pawn) && slime_pawn.can_feed_on(controller.blackboard[BB_CURRENT_PET_TARGET], check_friendship = TRUE)) - controller.queue_behavior(hunting_behavior, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/attack/slime) + return return ..() diff --git a/code/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.json b/code/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.json new file mode 100644 index 000000000000..580315b55113 --- /dev/null +++ b/code/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.json @@ -0,0 +1,46 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/attack/slime", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/feed_on_slime_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/slime/ai/slime.bt.json b/code/modules/mob/living/basic/slime/ai/slime.bt.json new file mode 100644 index 000000000000..c7415631708f --- /dev/null +++ b/code/modules/mob/living/basic/slime/ai/slime.bt.json @@ -0,0 +1,212 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/slime", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_SLIME_EVOLVE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_SLIME_EVOLVE" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "ability_key": "BB_SLIME_REPRODUCE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_SLIME_REPRODUCE" + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SLIME_EAT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/feed_on_slime_target", + "vars": { + "target_key": "BB_SLIME_EAT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_SLIME_EAT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SLIME_EAT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/feed_on_slime_target", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": false + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/slime_wants_to_eat", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SLIME_EAT_TARGET", + "targeting_strategy": "/datum/targeting_strategy/slime_food", + "target_source": "/datum/target_source/oview_living_no_slimes", + "vision_range": 7, + "must_be_reachable": true + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/change_slime_face" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/slime/ai/slime_bt.dm b/code/modules/mob/living/basic/slime/ai/slime_bt.dm new file mode 100644 index 000000000000..3d86ce2c3a0d --- /dev/null +++ b/code/modules/mob/living/basic/slime/ai/slime_bt.dm @@ -0,0 +1,87 @@ +/// Slime pet command attack: loops feed_on_slime_target toward BB_CURRENT_PET_TARGET. +/datum/bt_node/subtree/pet_command/attack/slime + behavior_tree_json = "code/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.json" + +///give them the chud face if they dont feed us, basically select a nice face +/datum/bt_node/ai_behavior/change_slime_face + +/datum/bt_node/ai_behavior/change_slime_face/perform(seconds_per_tick, datum/ai_controller/controller) + if(!prob(5)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/mob/living/basic/slime/slime_pawn = controller.pawn + if(!istype(slime_pawn) || slime_pawn.stat) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/current_mood = slime_pawn.current_mood + var/new_mood + + if(controller.blackboard[BB_SLIME_RABID] || LAZYLEN(controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST]) > 0) + new_mood = SLIME_MOOD_ANGRY + else if(controller.blackboard[BB_SLIME_HUNGER_DISABLED]) + new_mood = SLIME_MOOD_SMILE + else if(controller.blackboard[BB_CURRENT_TARGET]) + new_mood = SLIME_MOOD_MISCHIEVOUS + else + new_mood = pick(SLIME_MOOD_SAD, SLIME_MOOD_SMILE, SLIME_MOOD_POUT) + + if(current_mood != new_mood) + slime_pawn.current_mood = new_mood + slime_pawn.regenerate_icons() + + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/** + * Gate for slime food searching: passes only when the slime is unbuckled and is + * hungryor rabid. + */ +/datum/bt_node/decorator/slime_wants_to_eat + +/datum/bt_node/decorator/slime_wants_to_eat/check_condition(datum/ai_controller/controller) + var/mob/living/basic/slime/slime_pawn = controller.pawn + if(!istype(slime_pawn) || slime_pawn.buckled) + return FALSE + if(controller.blackboard[BB_SLIME_HUNGER_LEVEL] != SLIME_HUNGER_NONE) + return TRUE + if(controller.blackboard[BB_SLIME_RABID]) + return TRUE + return TRUE + + +///im about to eat this guy up +/datum/bt_node/ai_behavior/feed_on_slime_target + var/target_key + +/datum/bt_node/ai_behavior/feed_on_slime_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/slime/slime_pawn = controller.pawn + if(!istype(slime_pawn)) //bro lmao comeon + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + if(slime_pawn.buckled) + if(slime_pawn.buckled == target) //we got em boys + return AI_BEHAVIOR_DELAY + else + slime_pawn.stop_feeding() + return AI_BEHAVIOR_FAILED //epic fail; try again + + if(!slime_pawn.can_feed_on(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + if((target.body_position != STANDING_UP) || prob(20)) + slime_pawn.start_feeding(target) + return AI_BEHAVIOR_DELAY + + if(target.client && target.health >= 20) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + slime_pawn.start_feeding(target) + return AI_BEHAVIOR_DELAY + + +/datum/bt_node/ai_behavior/feed_on_slime_target/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + var/mob/living/basic/slime/slime_pawn = controller.pawn + slime_pawn.stop_feeding() diff --git a/code/modules/mob/living/basic/slime/ai/subtrees.dm b/code/modules/mob/living/basic/slime/ai/subtrees.dm deleted file mode 100644 index 08886f98365a..000000000000 --- a/code/modules/mob/living/basic/slime/ai/subtrees.dm +++ /dev/null @@ -1,54 +0,0 @@ -/datum/ai_planning_subtree/use_mob_ability/evolve - ability_key = BB_SLIME_EVOLVE - -/datum/ai_planning_subtree/use_mob_ability/reproduce - ability_key = BB_SLIME_REPRODUCE - -//Handles the slime changing their facial overlays -/datum/ai_planning_subtree/change_slime_face - var/face_change_chance = 5 - -/datum/ai_planning_subtree/change_slime_face/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(face_change_chance, seconds_per_tick)) - return - - var/mob/living/basic/slime/slime_pawn = controller.pawn - if(!istype(slime_pawn)) - return - - if(slime_pawn.stat) //dead slimes make no smiles - return - - controller.queue_behavior(/datum/ai_behavior/perform_change_slime_face) - -// Slime subtree for hunting down people to drain -/datum/ai_planning_subtree/find_and_hunt_target/find_slime_food - finding_behavior = /datum/ai_behavior/find_hunt_target/find_slime_food - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/slime - hunt_targets = list(/mob/living) - hunt_range = 7 - -/datum/ai_planning_subtree/find_and_hunt_target/find_slime_food/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.buckled) - return - - //Slimes don't want to hunt if they are neither rabid, hungry or feeling attack right now - if( (controller.blackboard[BB_SLIME_HUNGER_LEVEL] == SLIME_HUNGER_NONE) && !controller.blackboard[BB_SLIME_RABID] && isnull(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET])) - return - - return ..() - -/datum/ai_planning_subtree/basic_melee_attack_subtree/slime - -/datum/ai_planning_subtree/basic_melee_attack_subtree/slime/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.buckled) - return - return ..() - -/datum/ai_planning_subtree/random_speech/slime - speech_chance = 1 - speak = list("Blorble...","Bzzt...","") - emote_hear = list("blorbles.") - emote_see = list("lights up for a bit, then stops.","bounces in place.", "jiggles!","vibrates!") diff --git a/code/modules/mob/living/basic/slime/defense.dm b/code/modules/mob/living/basic/slime/defense.dm index 598ba3a318ac..4d14c3baea0c 100644 --- a/code/modules/mob/living/basic/slime/defense.dm +++ b/code/modules/mob/living/basic/slime/defense.dm @@ -110,7 +110,7 @@ /mob/living/basic/slime/proc/discipline_slime() stop_feeding(silent = TRUE) if(life_stage == SLIME_LIFE_STAGE_BABY && prob(80)) - ai_controller?.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + ai_controller?.clear_blackboard_key(BB_CURRENT_TARGET) ai_controller?.clear_blackboard_key(BB_CURRENT_HUNTING_TARGET) if(prob(10)) diff --git a/code/modules/mob/living/basic/snails/snail.bt.json b/code/modules/mob/living/basic/snails/snail.bt.json new file mode 100644 index 000000000000..9ba7932aa2a3 --- /dev/null +++ b/code/modules/mob/living/basic/snails/snail.bt.json @@ -0,0 +1,59 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/snail", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_SNAIL_RETREAT_ABILITY" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + } + ] +} diff --git a/code/modules/mob/living/basic/snails/snail_ai.dm b/code/modules/mob/living/basic/snails/snail_ai.dm index 73c48d198097..03f58bfc3264 100644 --- a/code/modules/mob/living/basic/snails/snail_ai.dm +++ b/code/modules/mob/living/basic/snails/snail_ai.dm @@ -1,68 +1,13 @@ /datum/ai_controller/basic_controller/snail + behavior_tree_json = "code/modules/mob/living/basic/snails/snail.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/use_mob_ability/snail_retreat, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/find_and_hunt_target/snail_people, - ) - -/datum/ai_planning_subtree/find_and_hunt_target/snail_people - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/snail_people - hunting_behavior = /datum/ai_behavior/hunt_target/snail_people - hunt_targets = list( - /mob/living/carbon, - ) - hunt_range = 5 - hunt_chance = 45 - -/datum/ai_behavior/find_hunt_target/snail_people - action_cooldown = 1 MINUTES - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_hunt_target/snail_people/valid_dinner(mob/living/source, mob/living/carbon/potential_snail, radius, datum/ai_controller/controller, seconds_per_tick) - if(!istype(potential_snail)) - return FALSE - if(potential_snail.stat != CONSCIOUS) - return FALSE - if(!is_species(potential_snail, /datum/species/snail)) - return FALSE - return can_see(source, potential_snail, radius) - -/datum/ai_behavior/hunt_target/snail_people - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/snail_people/target_caught(mob/living/hunter, atom/hunted) - hunter.manual_emote("Celebrates around [hunted]!") - hunter.SpinAnimation(speed = 1, loops = 3) - -/datum/ai_planning_subtree/use_mob_ability/snail_retreat - ability_key = BB_SNAIL_RETREAT_ABILITY - finish_planning = TRUE - -/datum/ai_planning_subtree/use_mob_ability/snail_retreat/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/shell_retreated = HAS_TRAIT(controller.pawn, TRAIT_SHELL_RETREATED) - var/has_target = controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) - if((has_target && shell_retreated) || (!has_target && !shell_retreated)) - return - return ..() - /datum/ai_controller/basic_controller/snail/trash blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/space_fauna/ant.bt.json b/code/modules/mob/living/basic/space_fauna/ant.bt.json new file mode 100644 index 000000000000..352c8e63fcc5 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/ant.bt.json @@ -0,0 +1,38 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/ant", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/ant.dm b/code/modules/mob/living/basic/space_fauna/ant.dm index 97633a705801..7a0265989029 100644 --- a/code/modules/mob/living/basic/space_fauna/ant.dm +++ b/code/modules/mob/living/basic/space_fauna/ant.dm @@ -55,16 +55,9 @@ AddElement(/datum/element/basic_allergenic_attack, allergen = BUGS, allergen_chance = 20, histamine_add = 5) /datum/ai_controller/basic_controller/ant + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/ant.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/ant, - ) diff --git a/code/modules/mob/living/basic/space_fauna/bear/bear.bt.json b/code/modules/mob/living/basic/space_fauna/bear/bear.bt.json new file mode 100644 index 000000000000..d4b638746fff --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/bear/bear.bt.json @@ -0,0 +1,215 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bear", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/climb_tree" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOUND_HONEY", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_HONEY", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/find_hive", + "vars": { + "target_key": "BB_FOUND_HONEY", + "cooldown_key": "BB_BEAR_HIVE_COOLDOWN", + "hunt_cooldown": "5 SECONDS" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_dragging", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/pull_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_BEAR_HONEYCOMB_COOLDOWN", + "hunt_cooldown": "5 SECONDS" + } + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CLIMBED_TREE", + "target_source": "/datum/target_source/oview_single_type/flora_tree", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_BEAR_HIVE_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOUND_HONEY", + "target_source": "/datum/target_source/oview_single_type/beehive", + "targeting_strategy": "/datum/targeting_strategy/stocked_beehive", + "vision_range": 10 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_dragging", + "vars": { + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_BEAR_HONEYCOMB_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/honeycomb", + "targeting_strategy": "/datum/targeting_strategy/huntable", + "vision_range": 10 + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/bear" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm index 7c57349524fe..9d4a2a7e8cda 100644 --- a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm +++ b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm @@ -1,27 +1,7 @@ -/datum/ai_behavior/find_hunt_target/find_hive - -/datum/ai_behavior/find_hunt_target/find_hive/valid_dinner(mob/living/source, obj/structure/beebox/hive, radius) - if(!length(hive.honeycombs)) - return FALSE - return can_see(source, hive, radius) - -/datum/ai_behavior/hunt_target/find_hive +/// Raids a beehive once in range, extracting its honeycombs. +/datum/bt_node/ai_behavior/hunt_target/find_hive always_reset_target = TRUE -/datum/ai_behavior/hunt_target/find_hive/target_caught(mob/living/hunter, obj/structure/beebox/hive_target) +/datum/bt_node/ai_behavior/hunt_target/find_hive/target_caught(mob/living/hunter, obj/structure/beebox/hive_target) var/datum/callback/callback = CALLBACK(hunter, TYPE_PROC_REF(/mob/living/basic/bear, extract_combs), hive_target) callback.Invoke() - -/datum/ai_behavior/find_hunt_target/find_honeycomb - -/datum/ai_behavior/find_hunt_target/find_honeycomb/setup(datum/ai_controller/controller, ability_key, target_key) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.pulling) //we already pulling a honey - return FALSE - return TRUE - -/datum/ai_behavior/hunt_target/find_honeycomb - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/find_honeycomb/target_caught(mob/living/hunter, obj/item/food/honeycomb/food_target) - hunter.start_pulling(food_target) diff --git a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm index 1f45576b1473..f6f900452dd3 100644 --- a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm +++ b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm @@ -4,28 +4,4 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/climb_trees, - /datum/ai_planning_subtree/find_and_hunt_target/find_hive, - /datum/ai_planning_subtree/find_and_hunt_target/find_honeycomb, - /datum/ai_planning_subtree/random_speech/bear, - ) - -/datum/ai_planning_subtree/find_and_hunt_target/find_hive - target_key = BB_FOUND_HONEY - hunting_behavior = /datum/ai_behavior/hunt_target/find_hive - finding_behavior = /datum/ai_behavior/find_hunt_target/find_hive - hunt_targets = list(/obj/structure/beebox) - hunt_range = 10 - -/datum/ai_planning_subtree/find_and_hunt_target/find_honeycomb - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/find_honeycomb - finding_behavior = /datum/ai_behavior/find_hunt_target/find_honeycomb - hunt_targets = list(/obj/item/food/honeycomb) - hunt_range = 10 + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/bear/bear.bt.json" diff --git a/code/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.json b/code/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.json new file mode 100644 index 000000000000..d3b7e8ecd671 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.json @@ -0,0 +1,109 @@ +{ + "dm_type": "/datum/bt_node/subtree/basic_carp_tree", + "bindings": { + "be5c4p10": { + "label": "escape_tree", + "default": "/datum/bt_node/subtree" + }, + "bf1nd7r2": { + "label": "find_targets_tree", + "default": "/datum/bt_node/subtree/carp_target_selection" + }, + "bc0mb4t9": { + "label": "combat_tree", + "default": "/datum/bt_node/subtree/carp_combat" + }, + "bid1e6k3": { + "label": "idle_tree", + "default": "/datum/bt_node/subtree/carp_migration" + }, + "b7cm2s3z": { + "label": "flee_from_key", + "default": "BB_CURRENT_TARGET" + } + }, + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "$be5c4p10" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/carp_flee", + "bindings": { + "bf3k9q2p": "$b7cm2s3z" + } + }, + { + "type": "subtree", + "subtype": "$bc0mb4t9" + } + ] + } + }, + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "$bid1e6k3" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "$bf1nd7r2" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MAGICARP_SPELL" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_magicarp_spell_target", + "vars": { + "ability_key": "BB_MAGICARP_SPELL", + "target_key": "BB_MAGICARP_SPELL_TARGET", + "targeting_strategy_key": "BB_TARGETING_STRATEGY" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp.bt.json new file mode 100644 index 000000000000..33c298f45493 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/carp", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_carp_tree", + "bindings": { + "be5c4p10": "/datum/bt_node/subtree/escape_captivity" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index f04fa3aea99d..be19c0bd3e90 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -156,8 +156,8 @@ /// Gives the carp a list of weakrefs of destinations to try and travel between when it has nothing better to do /mob/living/basic/carp/proc/migrate_to(list/datum/weakref/migration_points) - ai_controller.can_idle = FALSE - ai_controller.set_ai_status(AI_STATUS_ON) // We need htem to actually walk to the station + ai_controller.ai_traits |= RUN_WHILE_UNWATCHED + ai_controller.reset_ai_status() // We need them to actually keep walking to the station var/list/actual_points = list() for(var/datum/weakref/point_ref as anything in migration_points) var/turf/point_resolved = point_ref.resolve() diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm index b2fcbf0f78d4..22f06da636b8 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm @@ -137,7 +137,7 @@ if (isliving(entered_atom)) var/mob/living/teleported_mob = entered_atom teleported_mob.changeNext_move(disorient_time) - teleported_mob.ai_controller?.CancelActions() + teleported_mob.ai_controller?.cancel_current_plan() var/turf/destination = pick(exit_locs) do_teleport(entered_atom, destination, channel = TELEPORT_CHANNEL_MAGIC) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm index 26894850b198..d8beb9deddea 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm @@ -1,70 +1,144 @@ +/// How far away the magicarp looks for a special spell target #define MAGICARP_SPELL_TARGET_SEEK_RANGE 4 +/// How far away the magicarp looks for a regular spell target +#define MAGICARP_SPELL_ENEMY_SEEK_RANGE 9 /datum/pet_command/use_ability/magicarp pet_ability_key = BB_MAGICARP_SPELL -/datum/ai_planning_subtree/attack_obstacle_in_path/carp - attack_behaviour = /datum/ai_behavior/attack_obstructions/carp - -/datum/ai_behavior/attack_obstructions/carp - action_cooldown = 1.5 SECONDS +/** + * # Carp should flee + * Gates the flee/panic-teleport block. A carp flees from its flee target if that target is a feared + * fisherman, or if it is otherwise allowed to flee (i.e. it's injured, which clears BB_BASIC_MOB_STOP_FLEEING). + */ +/datum/bt_node/decorator/carp_should_flee + /// Blackboard key holding the thing we'd run away from + var/target_key = BB_BASIC_MOB_FLEE_TARGET -/// As basic attack tree but interrupt if your health gets low or if your spell is off cooldown -/datum/ai_planning_subtree/basic_melee_attack_subtree/magicarp - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/magicarp +/datum/bt_node/decorator/carp_should_flee/check_condition(datum/ai_controller/controller) + var/atom/flee_from = controller.blackboard[target_key] + if(QDELETED(flee_from)) + return FALSE + if(controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(flee_from, TRAIT_SCARY_FISHERMAN)) + return TRUE + return !controller.blackboard[BB_BASIC_MOB_STOP_FLEEING] -/// Interrupt your attack chain if: you have a spell, it's not on cooldown, and it has a target -/datum/ai_behavior/basic_melee_attack/magicarp +/datum/bt_node/decorator/carp_should_flee/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(target_key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key), + COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_STOP_FLEEING), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_BASIC_MOB_STOP_FLEEING), + ), PROC_REF(on_signal_changed)) + return TRUE -/datum/ai_behavior/basic_melee_attack/magicarp/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, health_ratio_key) - var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL] - if (QDELETED(using_action)) - return ..() - if (!controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETING] && using_action.IsAvailable()) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - return ..() +/datum/bt_node/decorator/carp_should_flee/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(target_key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key), + COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_STOP_FLEEING), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_BASIC_MOB_STOP_FLEEING), + )) /** - * Find a target for the magicarp's spell - * This gets weird because different spells want different targeting - * but I didn't want a new ai controller for every different spell + * # Find magicarp spell target + * Finds a target for the magicarp's spell. Different spells want different targeting, so rather than make a + * controller per spell we branch on BB_MAGICARP_SPELL_SPECIAL_TARGETING here. Only runs if the spell is ready. */ -/datum/ai_planning_subtree/find_nearest_magicarp_spell_target +/datum/bt_node/ai_behavior/find_magicarp_spell_target + /// Blackboard key holding the spell we're trying to target + var/ability_key = BB_MAGICARP_SPELL + /// Blackboard key we store the chosen spell target in + var/target_key = BB_MAGICARP_SPELL_TARGET + /// Blackboard key holding our targeting strategy for the default case + var/targeting_strategy_key = BB_TARGETING_STRATEGY + /// Blackboard key describing any special targeting this spell wants + var/special_targeting_key = BB_MAGICARP_SPELL_SPECIAL_TARGETING -/datum/ai_planning_subtree/find_nearest_magicarp_spell_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL] - if (!using_action?.IsAvailable()) - return +/datum/bt_node/ai_behavior/find_magicarp_spell_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/datum/action/cooldown/using_action = controller.blackboard[ability_key] + if(!using_action?.IsAvailable()) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/atom/found + switch(controller.blackboard[special_targeting_key]) + if(MAGICARP_SPELL_CORPSES) + found = find_friendly_corpse(controller) + if(MAGICARP_SPELL_OBJECTS) + found = find_animatable(controller) + if(MAGICARP_SPELL_WALLS) + found = find_nearest_wall(controller) + else + found = find_nearest_enemy(controller) + + if(isnull(found)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - var/spell_targeting = controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETING] - if (!spell_targeting) - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/nearest/magicarp, BB_MAGICARP_SPELL_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return + controller.set_blackboard_key(target_key, found) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - switch(spell_targeting) - if (MAGICARP_SPELL_CORPSES) - controller.queue_behavior(/datum/ai_behavior/find_and_set/friendly_corpses, BB_MAGICARP_SPELL_TARGET, MAGICARP_SPELL_TARGET_SEEK_RANGE) - return - if (MAGICARP_SPELL_OBJECTS) - controller.queue_behavior(/datum/ai_behavior/find_and_set/animatable, BB_MAGICARP_SPELL_TARGET, MAGICARP_SPELL_TARGET_SEEK_RANGE) - return - if (MAGICARP_SPELL_WALLS) - controller.queue_behavior(/datum/ai_behavior/find_and_set/nearest_wall, BB_MAGICARP_SPELL_TARGET, MAGICARP_SPELL_TARGET_SEEK_RANGE) - return +/// Nearest valid combat target which isn't a scary fisherman (default spell targeting) +/datum/bt_node/ai_behavior/find_magicarp_spell_target/proc/find_nearest_enemy(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + if(!strategy) + return null + var/list/candidates = list() + for(var/mob/living/candidate in oview(MAGICARP_SPELL_ENEMY_SEEK_RANGE, living_pawn)) + if(HAS_TRAIT(candidate, TRAIT_SCARY_FISHERMAN)) + continue + if(!strategy.is_valid_target(living_pawn, candidate, MAGICARP_SPELL_ENEMY_SEEK_RANGE, controller)) + continue + candidates += candidate + if(!length(candidates)) + return null + return get_closest_atom(/mob/living, candidates, living_pawn) -/// This subtype only exists because if you queue multiple of the same action with different arguments it deletes their stored arguments -/datum/ai_behavior/find_potential_targets/nearest/magicarp +/// An object or structure we could animate with a staff of change +/datum/bt_node/ai_behavior/find_magicarp_spell_target/proc/find_animatable(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/nearby_items = list() + for(var/obj/new_friend in oview(MAGICARP_SPELL_TARGET_SEEK_RANGE, living_pawn)) + if(!isitem(new_friend) && !isstructure(new_friend)) + continue + if(is_type_in_list(new_friend, GLOB.animatable_blacklist)) + continue + if(living_pawn.see_invisible < new_friend.invisibility) + continue + nearby_items += new_friend + if(length(nearby_items)) + return pick(nearby_items) + return null -/datum/ai_behavior/find_potential_targets/nearest/magicarp/pick_final_target(datum/ai_controller/controller, list/enemies_list) - for(var/atom/atom as anything in enemies_list) - if(HAS_TRAIT(atom, TRAIT_SCARY_FISHERMAN)) - enemies_list -= atom - return ..() +/// The nearest wall which isn't invulnerable +/datum/bt_node/ai_behavior/find_magicarp_spell_target/proc/find_nearest_wall(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/nearby_walls = list() + for(var/turf/closed/new_wall in oview(MAGICARP_SPELL_TARGET_SEEK_RANGE, living_pawn)) + if(isindestructiblewall(new_wall)) + continue + nearby_walls += new_wall + if(length(nearby_walls)) + return get_closest_atom(/turf/closed, nearby_walls, living_pawn) + return null -/// Then use it on that target -/datum/ai_planning_subtree/targeted_mob_ability/magicarp - ability_key = BB_MAGICARP_SPELL - target_key = BB_MAGICARP_SPELL_TARGET - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/and_clear_target +/// A corpse who shares our faction, for resurrection spells +/datum/bt_node/ai_behavior/find_magicarp_spell_target/proc/find_friendly_corpse(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/nearby_bodies = list() + for(var/mob/living/dead_pal in oview(MAGICARP_SPELL_TARGET_SEEK_RANGE, living_pawn)) + if(!isturf(dead_pal.loc)) + continue + if(!dead_pal.stat || dead_pal.health > 0) + continue + if(living_pawn.see_invisible < dead_pal.invisibility) + continue + if(!living_pawn.faction_check_atom(dead_pal)) + continue + nearby_bodies += dead_pal + if(length(nearby_bodies)) + return pick(nearby_bodies) + return null #undef MAGICARP_SPELL_TARGET_SEEK_RANGE +#undef MAGICARP_SPELL_ENEMY_SEEK_RANGE diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_migration.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_migration.dm index 9458877af7fe..b65456f16d79 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_migration.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_migration.dm @@ -1,46 +1,17 @@ /// How close you need to get to the destination in order to consider yourself there #define CARP_DESTINATION_SEARCH_RANGE 3 -/// If there's a portal this close to us we'll enter it just on the basis that the carp who made it probably knew where they were going -#define CARP_PORTAL_SEARCH_RANGE 2 - -/** - * # Carp Migration - * Will try to plan a path between a list of locations for carp to travel through - */ -/datum/ai_planning_subtree/carp_migration - -/datum/ai_planning_subtree/carp_migration/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - // If there's a rift nearby take a ride, then cancel everything else because it's not valid any more - for(var/obj/effect/temp_visual/lesser_carp_rift/entrance/rift in orange(controller.pawn, CARP_PORTAL_SEARCH_RANGE)) - controller.queue_behavior(/datum/ai_behavior/travel_towards_atom, get_turf(rift)) - return SUBTREE_RETURN_FINISH_PLANNING - - // We have a destination, try to approach it - var/turf/moving_to = controller.blackboard[BB_CARP_MIGRATION_TARGET] - if(!isnull(moving_to)) - var/turf/next_step = get_step_towards(controller.pawn, moving_to) - // Attempt to teleport around if we're blocked - if(next_step.is_blocked_turf(exclude_mobs = TRUE)) - controller.queue_behavior(/datum/ai_behavior/make_carp_rift/towards/unvalidated, BB_CARP_RIFT, BB_CARP_MIGRATION_TARGET) - controller.queue_behavior(/datum/ai_behavior/attack_obstructions/carp, BB_CARP_MIGRATION_TARGET) - controller.queue_behavior(/datum/ai_behavior/step_towards_turf, BB_CARP_MIGRATION_TARGET) - // We've gotten close enough to it, clear it so we can select a new point (or do nothing) - if(get_dist(controller.pawn, moving_to) <= CARP_DESTINATION_SEARCH_RANGE) - controller.clear_blackboard_key(BB_CARP_MIGRATION_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - // We have a path to follow but no destination, select one - if(length(controller.blackboard[BB_CARP_MIGRATION_PATH])) - controller.queue_behavior(/datum/ai_behavior/find_next_carp_migration_step, BB_CARP_MIGRATION_PATH, BB_CARP_MIGRATION_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING /** * # Find next carp migration step * Records the next turf we want to travel to into the blackboard for other actions */ -/datum/ai_behavior/find_next_carp_migration_step +/datum/bt_node/ai_behavior/find_next_carp_migration_step + /// Blackboard key holding the list of turfs to migrate between + var/path_key = BB_CARP_MIGRATION_PATH + /// Blackboard key in which we record our next destination + var/target_key = BB_CARP_MIGRATION_TARGET -/datum/ai_behavior/find_next_carp_migration_step/perform(seconds_per_tick, datum/ai_controller/controller, path_key, target_key) +/datum/bt_node/ai_behavior/find_next_carp_migration_step/perform(seconds_per_tick, datum/ai_controller/controller) var/list/blackboard_points = controller.blackboard[path_key] for(var/turf/migration_point as anything in blackboard_points) // By the end of this loop we will either have a valid migration point set, or an empty list in our blackboard @@ -50,5 +21,30 @@ return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED +/** + * # Clear arrived migration target + * Clears our migration target once we've gotten close enough to it, so a new one can be selected. + */ +/datum/bt_node/ai_behavior/clear_arrived_migration_target + /// Blackboard key holding our migration destination + var/target_key = BB_CARP_MIGRATION_TARGET + +/datum/bt_node/ai_behavior/clear_arrived_migration_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/turf/moving_to = controller.blackboard[target_key] + if(QDELETED(moving_to) || get_dist(controller.pawn, moving_to) <= CARP_DESTINATION_SEARCH_RANGE) + controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Returns TRUE if the next step towards the keyed turf is blocked, so we can smash or teleport through it. +/datum/bt_node/decorator/carp_path_blocked + /// Blackboard key holding the turf we're trying to reach + var/target_key = BB_CARP_MIGRATION_TARGET + +/datum/bt_node/decorator/carp_path_blocked/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + var/turf/next_step = get_step_towards(controller.pawn, target) + return next_step?.is_blocked_turf(exclude_mobs = TRUE) + #undef CARP_DESTINATION_SEARCH_RANGE -#undef CARP_PORTAL_SEARCH_RANGE diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm index 69890c7bb2f5..13f76f972aa3 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm @@ -1,112 +1,77 @@ /** * # Make carp rift - * Plan a carp rift action, so basically teleport somewhere if the action is available + * Use the carp rift ability to teleport somewhere relative to a target. */ -/datum/ai_planning_subtree/make_carp_rift - /// Chiefly describes where we are placing this teleport - var/datum/ai_behavior/rift_behaviour - /// If true we finish planning after this - var/finish_planning = FALSE - /// Key to read for flee target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - -/datum/ai_planning_subtree/make_carp_rift/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!rift_behaviour) - CRASH("Forgot to specify rift behaviour for [src]") - - if (!controller.blackboard_key_exists(target_key)) - return - var/datum/action/cooldown/using_action = controller.blackboard[BB_CARP_RIFT] - if (!using_action?.IsAvailable()) - return - - controller.queue_behavior(rift_behaviour, BB_CARP_RIFT, target_key) - if (finish_planning) - return SUBTREE_RETURN_FINISH_PLANNING - -/** - * # Make carp rift (panic) - * Plan to teleport away from our target so they can't fuck us up - */ -/datum/ai_planning_subtree/make_carp_rift/panic_teleport - rift_behaviour = /datum/ai_behavior/make_carp_rift/away - finish_planning = TRUE - -/datum/ai_planning_subtree/make_carp_rift/panic_teleport/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/fleeing_from = controller.blackboard[target_key] - if(!QDELETED(fleeing_from) && controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(fleeing_from, TRAIT_SCARY_FISHERMAN)) - return ..() - if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return - return ..() - -/datum/ai_planning_subtree/make_carp_rift/panic_teleport/flee_key - target_key = BB_BASIC_MOB_FLEE_TARGET - - -/** - * # Make carp rift (aggressive) - * Plan to teleport towards our target so we can fuck them up - */ -/datum/ai_planning_subtree/make_carp_rift/aggressive_teleport - rift_behaviour = /datum/ai_behavior/make_carp_rift/towards/aggressive - -/datum/ai_planning_subtree/make_carp_rift/aggressive_teleport/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/target = controller.blackboard[target_key] - if(!QDELETED(target) && controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - return - return ..() - -/** - * # Make carp rift - * Make a carp rift somewhere - */ -/datum/ai_behavior/make_carp_rift +/datum/bt_node/ai_behavior/make_carp_rift + /// Blackboard key holding the rift ability + var/ability_key = BB_CARP_RIFT + /// Blackboard key holding the atom we're teleporting relative to + var/target_key + /// Rift destination snapshotted in perform(), for perform_async() to read. + VAR_PRIVATE/turf/rift_destination + +/datum/bt_node/ai_behavior/make_carp_rift/setup(datum/ai_controller/controller) + . = ..() + if(!controller.blackboard[ability_key] || !controller.blackboard[target_key]) + return FALSE -/datum/ai_behavior/make_carp_rift/setup(datum/ai_controller/controller, ability_key, target_key) - return controller.blackboard[ability_key] && controller.blackboard[target_key] +/datum/bt_node/ai_behavior/make_carp_rift/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags -/datum/ai_behavior/make_carp_rift/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) var/datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability = controller.blackboard[ability_key] var/atom/target = controller.blackboard[target_key] - if (!validate_target(controller, target, ability)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + // Fail INSTANT (not DELAY) so a selector falls through to melee/obstacles when we can't teleport, + // rather than latching on us while our cooldown ticks down. + if(!validate_target(controller, target, ability)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/turf/target_destination = find_target_turf(controller, target, ability) - if (!target_destination) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!target_destination) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - if(ability.InterceptClickOn(controller.pawn, null, target_destination)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + rift_destination = target_destination + return start_async() + +/datum/bt_node/ai_behavior/make_carp_rift/perform_async(datum/ai_controller/controller) + var/datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability = controller.blackboard[ability_key] + var/result = ability.InterceptClickOn(controller.pawn, null, rift_destination) + if(!async_still_valid()) + return + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/datum/bt_node/ai_behavior/make_carp_rift/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + rift_destination = null /// Return true if your target is valid for the action -/datum/ai_behavior/make_carp_rift/proc/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) - if (!ability) +/datum/bt_node/ai_behavior/make_carp_rift/proc/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) + if(!ability) return FALSE - if (!target) + if(!target) return FALSE return TRUE /// Return the turf to teleport to, implement this or the behaviour won't do anything -/datum/ai_behavior/make_carp_rift/proc/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) +/datum/bt_node/ai_behavior/make_carp_rift/proc/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) CRASH("Called unimplemented target finding proc on carp rift behaviour") /** * # Make carp rift away * Make a rift bringing you further away from your target */ -/datum/ai_behavior/make_carp_rift/away +/datum/bt_node/ai_behavior/make_carp_rift/away -/datum/ai_behavior/make_carp_rift/away/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) +/datum/bt_node/ai_behavior/make_carp_rift/away/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) return get_ranged_target_turf_direct(controller.pawn, target, range = ability.max_range, offset = 180) /** * # Make carp rift forwards * Make a rift bringing you closer to your target */ -/datum/ai_behavior/make_carp_rift/towards +/datum/bt_node/ai_behavior/make_carp_rift/towards /// Drop rift at least this many tiles away from target var/teleport_buffer_distance = 0 /// Teleport simply if you are far away @@ -114,44 +79,44 @@ /// Teleport if the turf in front of you is blocked var/teleport_if_blocked = TRUE -/datum/ai_behavior/make_carp_rift/towards/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) +/datum/bt_node/ai_behavior/make_carp_rift/towards/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) . = ..() - if (!.) + if(!.) return FALSE - if (teleport_if_far) + if(teleport_if_far) var/distance = get_dist(get_turf(controller.pawn), get_turf(target)) - if (distance >= ability.max_range + teleport_buffer_distance) // Perform if we are far away + if(distance >= ability.max_range + teleport_buffer_distance) // Perform if we are far away return TRUE - if (teleport_if_blocked) + if(teleport_if_blocked) var/turf/next_move = get_step_towards(controller.pawn, target) - if (next_move.is_blocked_turf(exclude_mobs = TRUE)) // Perform if target is behind cover + if(next_move.is_blocked_turf(exclude_mobs = TRUE)) // Perform if target is behind cover return TRUE return FALSE -/datum/ai_behavior/make_carp_rift/towards/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) +/datum/bt_node/ai_behavior/make_carp_rift/towards/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) var/turf/target_turf = get_turf(target) var/distance = get_dist(get_turf(controller.pawn), target_turf) var/turf/chosen_turf - if (distance <= ability.max_range) + if(distance <= ability.max_range) chosen_turf = target_turf else var/run_direction = get_dir(controller.pawn, get_step_towards(controller.pawn, target_turf)) - chosen_turf = get_ranged_target_turf(controller.pawn, run_direction, ability.max_range) + chosen_turf = get_ranged_target_turf(controller.pawn, run_direction, ability.max_range) - if (!chosen_turf) + if(!chosen_turf) return // Subtract some distance so we don't drop carp directly on top of someone var/rift_to_target_distance = get_dist(target_turf, chosen_turf) - if (rift_to_target_distance < teleport_buffer_distance) + if(rift_to_target_distance < teleport_buffer_distance) var/away_direction = get_dir(controller.pawn, get_step_away(controller.pawn, target_turf)) - var/turf/backed_away_turf = get_ranged_target_turf(controller.pawn, away_direction, teleport_buffer_distance - rift_to_target_distance) - if (distance > get_dist(backed_away_turf, target_turf)) + var/turf/backed_away_turf = get_ranged_target_turf(controller.pawn, away_direction, teleport_buffer_distance - rift_to_target_distance) + if(distance > get_dist(backed_away_turf, target_turf)) chosen_turf = backed_away_turf // Avoid edge case pointless teleports from being up against a wall return chosen_turf @@ -160,48 +125,68 @@ * # Make carp rift forwards (aggressive) * Make a rift towards your target if you are blocked from moving or if it is far away */ -/datum/ai_behavior/make_carp_rift/towards/aggressive +/datum/bt_node/ai_behavior/make_carp_rift/towards/aggressive teleport_buffer_distance = 1 // Don't aggressively drop carps directly on top of a target mob /** * # Make carp rift forwards (unvalidated) - * Skip validation checks because we already did them in the controller + * Skip validation checks because we already did them elsewhere */ -/datum/ai_behavior/make_carp_rift/towards/unvalidated +/datum/bt_node/ai_behavior/make_carp_rift/towards/unvalidated -/datum/ai_behavior/make_carp_rift/towards/unvalidated/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) +/datum/bt_node/ai_behavior/make_carp_rift/towards/unvalidated/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) return TRUE -/datum/ai_behavior/make_carp_rift/towards/unvalidated/finish_action(datum/ai_controller/controller, succeeded, ...) +/datum/bt_node/ai_behavior/make_carp_rift/towards/unvalidated/finish_action(datum/ai_controller/controller, succeeded) . = ..() - if (succeeded) - controller.CancelActions() + if(succeeded) + controller.cancel_current_plan() /** - * # Shortcut to target through carp rift - * If there's a carp rift heading your way, plan to ride it to your target + * # Find carp rift shortcut + * If there's a carp rift heading towards our target, record its turf so we can ride it there. */ -/datum/ai_planning_subtree/shortcut_to_target_through_carp_rift +/datum/bt_node/ai_behavior/find_carp_rift_shortcut + /// Blackboard key holding our current target + var/target_key = BB_CURRENT_TARGET + /// Blackboard key in which we store the rift turf to travel to + var/destination_key = BB_CARP_RIFT_DESTINATION /// How far away do we look for rifts? var/search_distance = 3 - /// Minimum distance we should be from the target before we bother performing this action + /// Minimum distance we should be from the target before we bother var/minimum_distance = 2 -/datum/ai_planning_subtree/shortcut_to_target_through_carp_rift/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (QDELETED(target) || (controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN))) - return +/datum/bt_node/ai_behavior/find_carp_rift_shortcut/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target) || (controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN))) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/distance_to_target = get_dist(controller.pawn, target) - if (distance_to_target <= minimum_distance) - return + if(distance_to_target <= minimum_distance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - for (var/obj/effect/temp_visual/lesser_carp_rift/entrance/rift in orange(controller.pawn, search_distance)) + for(var/obj/effect/temp_visual/lesser_carp_rift/entrance/rift in orange(controller.pawn, search_distance)) var/exit_count = length(rift.exit_locs) - if (!exit_count) + if(!exit_count) continue var/turf/rift_exit = rift.exit_locs[exit_count] - if ((get_dist(rift_exit, target) + get_dist(rift, target)) >= distance_to_target) + if((get_dist(rift_exit, target) + get_dist(rift, target)) >= distance_to_target) continue - controller.queue_behavior(/datum/ai_behavior/travel_towards_atom, rift) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_blackboard_key(destination_key, get_turf(rift)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + +/** + * # Find nearby carp rift + * Record the turf of any nearby rift entrance so we can ride it; the carp who made it probably knew where they were going. + */ +/datum/bt_node/ai_behavior/find_carp_rift_shortcut/nearby + search_distance = 2 + minimum_distance = 0 + +/datum/bt_node/ai_behavior/find_carp_rift_shortcut/nearby/perform(seconds_per_tick, datum/ai_controller/controller) + for(var/obj/effect/temp_visual/lesser_carp_rift/entrance/rift in orange(controller.pawn, search_distance)) + controller.set_blackboard_key(destination_key, get_turf(rift)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.json new file mode 100644 index 000000000000..08a609642141 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.json @@ -0,0 +1,85 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MAGICARP_SPELL_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_MAGICARP_SPELL" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_MAGICARP_SPELL", + "target_key": "BB_MAGICARP_SPELL_TARGET" + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "time_between_perform": "1.5 SECONDS" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_CARP_RIFT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/make_carp_rift/towards/aggressive", + "vars": { + "ability_key": "BB_CARP_RIFT", + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm index 35a9e6d7f517..a99ca6e1e493 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm @@ -1,11 +1,9 @@ /** * AI controller for carp * Expected flow is: - * * If health is low, mark that we want to run away. - * * If we want to run away, find nearest target and run out of view of it. - * * Look for anything we want to eat in the area and target it. - * * If we don't have a target already, find something to attack. - * * Go and attack our target (which might be food, or might be a mob). + * * If we want to run away (injured, or a scary fisherman is near), flee or panic-teleport from our target. + * * Otherwise hunt for something to attack, prioritising scary fishermen, and go bite it. + * * When idle, migrate between destinations or wander. */ /datum/ai_controller/basic_controller/carp blackboard = list( @@ -17,21 +15,7 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport, - /datum/ai_planning_subtree/flee_target/from_fisherman, - /datum/ai_planning_subtree/attack_obstacle_in_path/carp, - /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/carp_migration, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp.bt.json" ///Megacarps. The only difference is that they don't flee from scary fishermen and prioritize them. /datum/ai_controller/basic_controller/carp/mega @@ -42,20 +26,7 @@ BB_TARGET_PRIORITY_TRAIT = TRAIT_SCARY_FISHERMAN, BB_CARPS_FEAR_FISHERMAN = FALSE, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/attack_obstacle_in_path/carp, - /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/carp_migration, - ) + /** * Carp which bites back, but doesn't look for targets. * 'Not hunting targets' includes food (and can rings), because they have been well trained. @@ -69,39 +40,14 @@ BB_CARPS_FEAR_FISHERMAN = TRUE, ) ai_traits = PASSIVE_AI_FLAGS - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport, - /datum/ai_planning_subtree/flee_target/from_fisherman, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/attack_obstacle_in_path/carp, - /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.json" /** * AI for carp with a spell. * Flow is basically the same as regular carp, except it will try and cast a spell at its target whenever possible and not fleeing. */ /datum/ai_controller/basic_controller/carp/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport, - /datum/ai_planning_subtree/flee_target/from_fisherman, - /datum/ai_planning_subtree/find_nearest_magicarp_spell_target, - /datum/ai_planning_subtree/targeted_mob_ability/magicarp, - /datum/ai_planning_subtree/attack_obstacle_in_path/carp, - /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree/magicarp, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/carp_migration, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.json" /** * Carp which bites back, but doesn't look for targets and doesnt do as much damage @@ -116,14 +62,33 @@ BB_TARGET_ONLY_WITH_TRAITS = list(TRAIT_SCARY_FISHERMAN), ) ai_traits = PASSIVE_AI_FLAGS - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target/to_flee, // This should only find master fishermen because of the targeting strategy - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/from_flee_key, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport/flee_key, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/carp_migration, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.json" + + +/// Shared carp skeleton: escape -> pet command -> (flee / combat / migrate-or-idle) with a target-finding secondary. +/datum/bt_node/subtree/basic_carp_tree + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.json" + +/// Flee or panic-teleport away from a keyed target. +/datum/bt_node/subtree/carp_flee + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.json" + +/// Attack our current target: cast a spell, teleport in, smash obstacles or bite. +/datum/bt_node/subtree/carp_combat + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.json" + +/// Travel a migration path, riding or punching through rifts and walls along the way. +/datum/bt_node/subtree/carp_migration + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.json" + +/// Hunting target finder: flee the nearest threat when injured, otherwise hunt prioritising scary fishermen. +/datum/bt_node/subtree/carp_target_selection + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.json" + +/// Bite-back target finder: target whoever has attacked us. +/datum/bt_node/subtree/carp_retaliate_selection + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.json" + +/// Passive flee finder: flag scary fishermen and attackers as things to run away from. +/datum/bt_node/subtree/carp_passive_selection + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.json" diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.json new file mode 100644 index 000000000000..3f8d758f941d --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.json @@ -0,0 +1,63 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_flee", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/carp_should_flee", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "target_key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_CARP_RIFT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/make_carp_rift/away", + "vars": { + "ability_key": "BB_CARP_RIFT", + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_flee_location", + "vars": { + "target_key": "$bf3k9q2p", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "destination_key": "BB_FLEE_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FLEE_LOCATION", + "required_dist": 0, + "finish_on_arrival": true + } + } + ] + } + ] + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.json new file mode 100644 index 000000000000..0135ad46a74a --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.json @@ -0,0 +1,114 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_migration", + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_carp_rift_shortcut/nearby", + "vars": { + "destination_key": "BB_CARP_RIFT_DESTINATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CARP_RIFT_DESTINATION", + "required_dist": 0, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_CARP_RIFT_DESTINATION" + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CARP_MIGRATION_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/carp_path_blocked", + "vars": { + "target_key": "BB_CARP_MIGRATION_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_CARP_RIFT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/make_carp_rift/towards/unvalidated", + "vars": { + "ability_key": "BB_CARP_RIFT", + "target_key": "BB_CARP_MIGRATION_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CARP_MIGRATION_TARGET", + "time_between_perform": "1.5 SECONDS" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CARP_MIGRATION_TARGET", + "required_dist": 3, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_arrived_migration_target", + "vars": { + "target_key": "BB_CARP_MIGRATION_TARGET" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_next_carp_migration_step", + "vars": { + "path_key": "BB_CARP_MIGRATION_PATH", + "target_key": "BB_CARP_MIGRATION_TARGET" + } + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.json new file mode 100644 index 000000000000..c4b2c3470823 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/carp/passive", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_carp_tree", + "bindings": { + "bf1nd7r2": "/datum/bt_node/subtree/carp_passive_selection", + "b7cm2s3z": "BB_BASIC_MOB_FLEE_TARGET" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.json new file mode 100644 index 000000000000..77bd25864eb2 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.json @@ -0,0 +1,25 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_passive_selection", + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION", + "check_faction": false + } + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.json new file mode 100644 index 000000000000..7e84f7bc58f4 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/carp/pet", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_carp_tree", + "bindings": { + "bf1nd7r2": "/datum/bt_node/subtree/carp_retaliate_selection", + "bc0mb4t9": "/datum/bt_node/subtree/carp_retaliate_selection", + "bid1e6k3": "/datum/bt_node/subtree" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.json new file mode 100644 index 000000000000..04b187ecb509 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/carp/ranged", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_carp_tree", + "bindings": { + "be5c4p10": "/datum/bt_node/subtree/escape_captivity" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.json new file mode 100644 index 000000000000..0497947d8ed2 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.json @@ -0,0 +1,11 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_retaliate_selection", + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": false + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.json new file mode 100644 index 000000000000..0d2446542f61 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.json @@ -0,0 +1,32 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_target_selection", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_BASIC_MOB_STOP_FLEEING", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/cat_butcherer.bt.json b/code/modules/mob/living/basic/space_fauna/cat_butcherer.bt.json new file mode 100644 index 000000000000..e2291f2dc3f2 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/cat_butcherer.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cat_butcherer", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat_with_retaliate" +} diff --git a/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm b/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm index cc77315ebebe..25586fcbfd45 100644 --- a/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm +++ b/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm @@ -63,17 +63,9 @@ tail.forceMove(drop_location()) /datum/ai_controller/basic_controller/cat_butcherer + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/cat_butcherer.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/space_fauna/changeling/headslug.bt.json b/code/modules/mob/living/basic/space_fauna/changeling/headslug.bt.json new file mode 100644 index 000000000000..eccbfd80fd94 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/changeling/headslug.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/headslug", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" +} diff --git a/code/modules/mob/living/basic/space_fauna/changeling/headslug.dm b/code/modules/mob/living/basic/space_fauna/changeling/headslug.dm index 1f9bd60db9d8..1707d51ae185 100644 --- a/code/modules/mob/living/basic/space_fauna/changeling/headslug.dm +++ b/code/modules/mob/living/basic/space_fauna/changeling/headslug.dm @@ -95,8 +95,8 @@ /// This is a bit neutered since these aren't intended to exist outside of player control, but it's a bit weird to just have these guys be completely stationary. /// No attacking or anything like that, though. Just something so they seem alive. /datum/ai_controller/basic_controller/headslug + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/changeling/headslug.bt.json" ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk /// Neutered version to prevent people from turning themselves into changelings with sentience potions or transformation /mob/living/basic/headslug/beakless diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.json b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.json new file mode 100644 index 000000000000..1237c1cc75d8 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.json @@ -0,0 +1,183 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/eyeball", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "ability_key": "BB_GLARE_ABILITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_target_facing_turf", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "set_key": "BB_GLARE_POSITION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_GLARE_POSITION", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_GLARE_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_BLIND_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BLIND_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/heal_eye_damage", + "vars": { + "target_key": "BB_BLIND_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bqwjf4id": "2 SECONDS", + "b3cnse9r": "TRUE" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_BLIND_TARGET", + "target_source": "/datum/target_source/oview_single_type/carbon_mob", + "targeting_strategy": "/datum/targeting_strategy/damaged_eyes", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/carrot", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 6 + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm deleted file mode 100644 index 27f637d3a879..000000000000 --- a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm +++ /dev/null @@ -1,88 +0,0 @@ -/datum/ai_behavior/find_the_blind - -/datum/ai_behavior/find_the_blind/perform(seconds_per_tick, datum/ai_controller/controller, blind_key, threshold_key) - var/mob/living_pawn = controller.pawn - var/list/blind_list = list() - var/eye_damage_threshold = controller.blackboard[threshold_key] - if(!eye_damage_threshold) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - for(var/mob/living/carbon/blind in oview(9, living_pawn)) - var/obj/item/organ/eyes/eyes = blind.get_organ_slot(ORGAN_SLOT_EYES) - if(isnull(eyes)) - continue - if(eyes.damage < eye_damage_threshold) - continue - blind_list += blind - - if(!length(blind_list)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(blind_key, pick(blind_list)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/heal_eye_damage - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/heal_eye_damage/setup(datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/carbon/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/heal_eye_damage/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/carbon/target = controller.blackboard[target_key] - var/mob/living/living_pawn = controller.pawn - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/obj/item/organ/eyes/eyes = target.get_organ_slot(ORGAN_SLOT_EYES) - var/datum/callback/callback = CALLBACK(living_pawn, TYPE_PROC_REF(/mob/living/basic/eyeball, heal_eye_damage), target, eyes) - callback.Invoke() - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/heal_eye_damage/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/targeted_mob_ability/glare_at_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - required_distance = 0 - -/datum/ai_behavior/targeted_mob_ability/glare_at_target/setup(datum/ai_controller/controller, ability_key, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if (isnull(target)) - return FALSE - - var/turf/turf_to_move_towards = get_step(target, target.dir) - if(turf_to_move_towards.is_blocked_turf(ignore_atoms = list(controller.pawn))) - return FALSE - - if(isnull(turf_to_move_towards)) - return FALSE - - set_movement_target(controller, turf_to_move_towards) - -/datum/ai_behavior/targeted_mob_ability/glare_at_target/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) - var/datum/action/cooldown/ability = controller.blackboard[ability_key] - var/mob/living/target = controller.blackboard[target_key] - - if(QDELETED(ability) || QDELETED(target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/direction_to_compare = get_dir(target, controller.pawn) - var/target_direction = target.dir - if(direction_to_compare != target_direction) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/result = ability.InterceptClickOn(controller.pawn, null, target) - if(result == TRUE) - return AI_BEHAVIOR_INSTANT - else - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/hunt_target/interact_with_target/carrot - hunt_cooldown = 2 SECONDS - always_reset_target = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm index e24abb62b5e8..5a3a2ccacce1 100644 --- a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm @@ -5,25 +5,9 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/glare, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/heal_the_blind, - /datum/ai_planning_subtree/find_and_hunt_target/carrot, - ) - -/datum/ai_planning_subtree/heal_the_blind - -/datum/ai_planning_subtree/heal_the_blind/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_BLIND_TARGET)) - controller.queue_behavior(/datum/ai_behavior/heal_eye_damage, BB_BLIND_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_the_blind, BB_BLIND_TARGET, BB_EYE_DAMAGE_THRESHOLD) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.json" -/datum/targeting_strategy/basic/eyeball/can_attack(mob/living/owner, atom/target, vision_range) +/datum/targeting_strategy/basic/eyeball/is_valid_target(mob/living/owner, atom/target, vision_range, datum/ai_controller/controller = null) . = ..() if(!.) return FALSE @@ -40,14 +24,3 @@ return FALSE return can_see(target, owner, 9) //if the target cant see us dont attack him - -/datum/ai_planning_subtree/targeted_mob_ability/glare - ability_key = BB_GLARE_ABILITY - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/glare_at_target - finish_planning = TRUE - -/datum/ai_planning_subtree/find_and_hunt_target/carrot - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/carrot - hunt_targets = list(/obj/item/food/grown/carrotlike/carrot) - hunt_range = 6 diff --git a/code/modules/mob/living/basic/space_fauna/faithless.bt.json b/code/modules/mob/living/basic/space_fauna/faithless.bt.json new file mode 100644 index 000000000000..ae18da6d4eb5 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/faithless.bt.json @@ -0,0 +1,137 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/faithless", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/hunt_target_list", + "targeting_strategy": "/datum/targeting_strategy/unbroken_light", + "vision_range": 7 + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/faithless" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/faithless.dm b/code/modules/mob/living/basic/space_fauna/faithless.dm index c4f195cc0a18..1cc1df54c3cc 100644 --- a/code/modules/mob/living/basic/space_fauna/faithless.dm +++ b/code/modules/mob/living/basic/space_fauna/faithless.dm @@ -41,6 +41,7 @@ AddElement(/datum/element/door_pryer) AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE) AddElement(/datum/element/mob_grabber, steal_from_others = FALSE) + ai_controller.set_blackboard_key(BB_HUNT_TARGET_LIST, typecacheof(list(/obj/machinery/light))) /mob/living/basic/faithless/melee_attack(atom/target, list/modifiers, ignore_cooldown) . = ..() @@ -54,20 +55,10 @@ span_userdanger("\The [src] knocks you down!")) /datum/ai_controller/basic_controller/faithless + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/faithless.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = UNCONSCIOUS, - BB_LOW_PRIORITY_HUNTING_TARGET = null, // lights ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures, - /datum/ai_planning_subtree/random_speech/faithless, - ) diff --git a/code/modules/mob/living/basic/space_fauna/garden_gnome.bt.json b/code/modules/mob/living/basic/space_fauna/garden_gnome.bt.json new file mode 100644 index 000000000000..f79314d95415 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/garden_gnome.bt.json @@ -0,0 +1,114 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/garden_gnome", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": "$b2jvnm5d" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/garden_gnome" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/garden_gnome.dm b/code/modules/mob/living/basic/space_fauna/garden_gnome.dm index f6926b2ea51b..c36a3ff85bb9 100644 --- a/code/modules/mob/living/basic/space_fauna/garden_gnome.dm +++ b/code/modules/mob/living/basic/space_fauna/garden_gnome.dm @@ -124,16 +124,9 @@ potential_gnome.ai_controller.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, attacker, world.time) /datum/ai_controller/basic_controller/garden_gnome + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/garden_gnome.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/garden_gnome, - ) diff --git a/code/modules/mob/living/basic/space_fauna/ghost.bt.json b/code/modules/mob/living/basic/space_fauna/ghost.bt.json new file mode 100644 index 000000000000..7f79b254cf15 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/ghost.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/ghost", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/ghost.dm b/code/modules/mob/living/basic/space_fauna/ghost.dm index cfdf6840ff25..904857438f3f 100644 --- a/code/modules/mob/living/basic/space_fauna/ghost.dm +++ b/code/modules/mob/living/basic/space_fauna/ghost.dm @@ -94,18 +94,12 @@ name = "ghost of [pick(GLOB.first_names_female)] [pick(GLOB.last_names)]" /datum/ai_controller/basic_controller/ghost + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/ghost.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Weaker variant of ghosts. Meant to be summoned in swarms via the ectoplasmic anomaly and associated ghost portal. /mob/living/basic/ghost/swarm diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.json b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.json new file mode 100644 index 000000000000..d02a4edaaf98 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.json @@ -0,0 +1,119 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/hivebot", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/relay_to_hive_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.1 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HIVE_PARTNER", + "target_source": "/datum/target_source/oview_single_type/hivebot", + "targeting_strategy": "/datum/targeting_strategy/living_not_dead", + "vision_range": 10 + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm index 54c3c13d139f..f299d032b1dc 100644 --- a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm @@ -1,35 +1,17 @@ -/datum/ai_behavior/find_and_set/hive_partner - -/datum/ai_behavior/find_and_set/hive_partner/search_tactic(datum/ai_controller/controller, locate_path, search_range = 10) - var/mob/living/living_pawn = controller.pawn - var/list/hive_partners = list() - for(var/mob/living/target in oview(search_range, living_pawn)) - if(!istype(target, locate_path)) - continue - if(target.stat == DEAD) - continue - hive_partners += target - - if(length(hive_partners)) - return pick(hive_partners) - -/// behavior that allow us to go communicate with other hivebots -/datum/ai_behavior/relay_message - ///length of the message we will relay +/// Moves to the hive partner in BB_HIVE_PARTNER, relays a message, then clears the key. +/// The partner is found by a separate acquire_target leaf in the controller tree. +/datum/bt_node/subtree/relay_to_hive_partner + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.json" + +/// Says a random binary string to a hive partner. Movement to the partner is handled +/// in the tree via a move_to_target leaf; the target key is cleared by a clear_key leaf. +/datum/bt_node/ai_behavior/relay_message + /// Blackboard key holding the hive partner to talk at. + var/target_key + /// Number of bits in the message we relay. var/length_of_message = 4 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT| AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/relay_message/setup(datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/target = controller.blackboard[target_key] - // It stopped existing - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - - -/datum/ai_behavior/relay_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/relay_message/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/target = controller.blackboard[target_key] var/mob/living/living_pawn = controller.pawn @@ -38,31 +20,13 @@ var/message_relayed = "" for(var/i in 1 to length_of_message) message_relayed += prob(50) ? "1" : "0" - living_pawn.say(message_relayed, forced = "AI Controller") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), message_relayed, forced = "AI Controller") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/relay_message/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/find_hunt_target/repair_machines - -/datum/ai_behavior/find_hunt_target/repair_machines/valid_dinner(mob/living/source, obj/machinery/repair_target, radius) - if(repair_target.get_integrity() >= repair_target.max_integrity) - return FALSE - - return can_see(source, repair_target, radius) - -/datum/ai_behavior/hunt_target/repair_machines +/// Repairs a damaged machine once in range. Finding the machine is a separate +/// acquire_target leaf; movement is a move_to_target leaf. +/datum/bt_node/ai_behavior/hunt_target/repair_machines always_reset_target = TRUE -/datum/ai_behavior/hunt_target/repair_machines/target_caught(mob/living/basic/hivebot/mechanic/hunter, obj/machinery/repair_target) +/datum/bt_node/ai_behavior/hunt_target/repair_machines/target_caught(mob/living/basic/hivebot/mechanic/hunter, obj/machinery/repair_target) hunter.repair_machine(repair_target) - -/datum/ai_behavior/basic_ranged_attack/hivebot - action_cooldown = 3 SECONDS - avoid_friendly_fire = TRUE - -/datum/ai_behavior/basic_ranged_attack/hivebot_rapid - action_cooldown = 1.5 SECONDS - avoid_friendly_fire = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.json b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.json new file mode 100644 index 000000000000..444702cb7d9f --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.json @@ -0,0 +1,166 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/hivebot/mechanic", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_MACHINE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_MACHINE_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/repair_machines", + "vars": { + "target_key": "BB_MACHINE_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/relay_to_hive_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MACHINE_TARGET", + "target_source": "/datum/target_source/oview_single_type/machine", + "targeting_strategy": "/datum/targeting_strategy/damaged_machine", + "vision_range": 10 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.1 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HIVE_PARTNER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HIVE_PARTNER", + "target_source": "/datum/target_source/oview_single_type/hivebot", + "targeting_strategy": "/datum/targeting_strategy/living_not_dead", + "vision_range": 10 + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.json b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.json new file mode 100644 index 000000000000..b6009de5a228 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.json @@ -0,0 +1,130 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/hivebot/ranged", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "time_between_perform": "3 SECONDS", + "avoid_friendly_fire": true + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/relay_to_hive_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.1 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HIVE_PARTNER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HIVE_PARTNER", + "target_source": "/datum/target_source/oview_single_type/hivebot", + "targeting_strategy": "/datum/targeting_strategy/living_not_dead", + "vision_range": 10 + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.json b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.json new file mode 100644 index 000000000000..38703850ca1c --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.json @@ -0,0 +1,130 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/hivebot/ranged/rapid", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "time_between_perform": "1.5 SECONDS", + "avoid_friendly_fire": true + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/relay_to_hive_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.1 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HIVE_PARTNER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HIVE_PARTNER", + "target_source": "/datum/target_source/oview_single_type/hivebot", + "targeting_strategy": "/datum/targeting_strategy/living_not_dead", + "vision_range": 10 + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm index 34ace8789291..affe62661516 100644 --- a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm @@ -4,68 +4,13 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/hive_communicate, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.json" /datum/ai_controller/basic_controller/hivebot/mechanic - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/find_and_hunt_target/repair_machines, - /datum/ai_planning_subtree/hive_communicate, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.json" /datum/ai_controller/basic_controller/hivebot/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/hive_communicate, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.json" /datum/ai_controller/basic_controller/hivebot/ranged/rapid - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot_rapid, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/hive_communicate, - ) - - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot_rapid - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/hivebot_rapid - - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/hivebot - -/datum/ai_planning_subtree/hive_communicate - ///chance to go and relay message - var/relay_chance = 10 - -/datum/ai_planning_subtree/hive_communicate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - if(!SPT_PROB(relay_chance, seconds_per_tick)) - return - - if (controller.blackboard_key_exists(BB_HIVE_PARTNER)) - controller.queue_behavior(/datum/ai_behavior/relay_message, BB_HIVE_PARTNER) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_and_set/hive_partner, BB_HIVE_PARTNER, /mob/living/basic/hivebot) - -/datum/ai_planning_subtree/find_and_hunt_target/repair_machines - target_key = BB_MACHINE_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/repair_machines - finding_behavior = /datum/ai_behavior/find_hunt_target/repair_machines - hunt_targets = list(/obj/machinery) - hunt_range = 10 - hunt_chance = 35 + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.json" diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.json b/code/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.json new file mode 100644 index 000000000000..52ce2daa1071 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.json @@ -0,0 +1,36 @@ +{ + "dm_type": "/datum/bt_node/subtree/relay_to_hive_partner", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_HIVE_PARTNER" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HIVE_PARTNER", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/relay_message", + "vars": { + "target_key": "BB_HIVE_PARTNER" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_HIVE_PARTNER" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/space_fauna/killer_tomato.bt.json b/code/modules/mob/living/basic/space_fauna/killer_tomato.bt.json new file mode 100644 index 000000000000..726caa4a98d9 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/killer_tomato.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/killer_tomato", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat", + "bindings": { + "bqul7l8t": "/datum/bt_node/subtree/random_speech_loop" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/killer_tomato.dm b/code/modules/mob/living/basic/space_fauna/killer_tomato.dm index 152c14ff38ef..00405bd1531e 100644 --- a/code/modules/mob/living/basic/space_fauna/killer_tomato.dm +++ b/code/modules/mob/living/basic/space_fauna/killer_tomato.dm @@ -41,16 +41,14 @@ ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) /datum/ai_controller/basic_controller/killer_tomato + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/killer_tomato.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("gnashes.", "growls lowly.", "snarls."), + BB_EMOTE_SEE = list("salivates."), + BB_SPEAK_CHANCE = 3, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/killer_tomato, - ) diff --git a/code/modules/mob/living/basic/space_fauna/lightgeist.bt.json b/code/modules/mob/living/basic/space_fauna/lightgeist.bt.json new file mode 100644 index 000000000000..ddbc437caf18 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/lightgeist.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/lightgeist", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" +} diff --git a/code/modules/mob/living/basic/space_fauna/lightgeist.dm b/code/modules/mob/living/basic/space_fauna/lightgeist.dm index b86d50e2efba..da6fff877157 100644 --- a/code/modules/mob/living/basic/space_fauna/lightgeist.dm +++ b/code/modules/mob/living/basic/space_fauna/lightgeist.dm @@ -71,18 +71,13 @@ death() /datum/ai_controller/basic_controller/lightgeist + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/lightgeist.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/lightgeist, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, // We heal things by attacking them - ) /// Attack only mobs who have damage that we can heal, I think this is specific enough not to be a generic type /datum/targeting_strategy/lightgeist @@ -91,7 +86,7 @@ /// Type of limb we can heal var/required_bodytype = BODYTYPE_ORGANIC -/datum/targeting_strategy/lightgeist/can_attack(mob/living/living_mob, mob/living/target, vision_range) +/datum/targeting_strategy/lightgeist/is_valid_target(mob/living/living_mob, mob/living/target, vision_range, datum/ai_controller/controller = null) if (!isliving(target) || target.stat == DEAD) return FALSE if (!(heal_biotypes & target.mob_biotypes)) diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.json b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.json new file mode 100644 index 000000000000..2380cf189caa --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.json @@ -0,0 +1,73 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/meteor_heart", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_METEOR_HEART_GROUND_SPIKES", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_METEOR_HEART_SPINE_TRAPS" + } + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/meteor_heart_deaggro" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm index ccb86e7043cd..4ca874df94f3 100644 --- a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm @@ -43,7 +43,7 @@ /datum/action/cooldown/mob_cooldown/spine_traps = BB_METEOR_HEART_SPINE_TRAPS, ) grant_actions_by_list(innate_actions) - ai_controller.set_ai_status(AI_STATUS_OFF) + ai_controller.force_ai_off() RegisterSignal(src, COMSIG_MOB_ABILITY_FINISHED, PROC_REF(used_ability)) RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(aggro)) @@ -62,7 +62,7 @@ /mob/living/basic/meteor_heart/proc/aggro() if (ai_controller.ai_status == AI_STATUS_ON) return - ai_controller.reset_ai_status() + ai_controller.clear_forced_off() if (!ai_controller.ai_status == AI_STATUS_ON) return icon_state = "heart_aggro" @@ -70,7 +70,7 @@ /// Called when we stop being mad /mob/living/basic/meteor_heart/proc/deaggro() - ai_controller.set_ai_status(AI_STATUS_OFF) + ai_controller.force_ai_off() icon_state = "heart" soundloop.set_mid_length(HEARTBEAT_NORMAL) diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm index 92f1d0a98896..3e3a801d0830 100644 --- a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm @@ -1,33 +1,29 @@ /// A spellcasting AI which does not move /datum/ai_controller/basic_controller/meteor_heart + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - BB_TARGETLESS_TIME = 0, + BB_CURRENT_TARGET_HIDING_LOCATION = null, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/ground_spikes, - /datum/ai_planning_subtree/use_mob_ability/spine_traps, - /datum/ai_planning_subtree/sleep_with_no_target/meteor_heart, - ) - -/datum/ai_planning_subtree/targeted_mob_ability/ground_spikes - ability_key = BB_METEOR_HEART_GROUND_SPIKES - finish_planning = FALSE - -/datum/ai_planning_subtree/use_mob_ability/spine_traps - ability_key = BB_METEOR_HEART_SPINE_TRAPS +/// After enough time with no target, calls deaggro() on the meteor heart to shut down the AI and reset visuals. +/datum/bt_node/ai_behavior/meteor_heart_deaggro + var/deaggro_delay = 10 SECONDS + VAR_PRIVATE/timerid -/// After enough time with no target, deaggro and change animation state -/datum/ai_planning_subtree/sleep_with_no_target/meteor_heart - sleep_behaviour = /datum/ai_behavior/sleep_after_targetless_time/meteor_heart +/datum/bt_node/ai_behavior/meteor_heart_deaggro/setup(datum/ai_controller/controller) + . = ..() + timerid = addtimer(CALLBACK(src, PROC_REF(finish_action), controller, TRUE), deaggro_delay, TIMER_UNIQUE | TIMER_STOPPABLE) -/datum/ai_behavior/sleep_after_targetless_time/meteor_heart +/datum/bt_node/ai_behavior/meteor_heart_deaggro/perform(seconds_per_tick, datum/ai_controller/controller) + return AI_BEHAVIOR_DELAY -/datum/ai_behavior/sleep_after_targetless_time/meteor_heart/enter_sleep(datum/ai_controller/controller) +/datum/bt_node/ai_behavior/meteor_heart_deaggro/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + deltimer(timerid) + timerid = null + if(!succeeded) + return var/mob/living/basic/meteor_heart/heart = controller.pawn - if (!istype(heart)) - return ..() - heart.deaggro() + if(istype(heart)) + heart.deaggro() diff --git a/code/modules/mob/living/basic/space_fauna/morph.bt.json b/code/modules/mob/living/basic/space_fauna/morph.bt.json new file mode 100644 index 000000000000..d9508fc1eaaf --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/morph.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/morph", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat_with_retaliate" +} diff --git a/code/modules/mob/living/basic/space_fauna/morph.dm b/code/modules/mob/living/basic/space_fauna/morph.dm index a76704e40fe9..c0e92d20bbe3 100644 --- a/code/modules/mob/living/basic/space_fauna/morph.dm +++ b/code/modules/mob/living/basic/space_fauna/morph.dm @@ -200,16 +200,10 @@ /// No fleshed out AI implementation, just something that make these fellers seem lively if they're just dropped into a station. /// Only real human-powered intelligence is capable of playing prop hunt in SS13 (until further notice). /datum/ai_controller/basic_controller/morph + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/morph.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/space_fauna/mushroom.bt.json b/code/modules/mob/living/basic/space_fauna/mushroom.bt.json new file mode 100644 index 000000000000..6f6b21c905ac --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/mushroom.bt.json @@ -0,0 +1,103 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mushroom", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bvtz06kb": "BB_LOW_PRIORITY_HUNTING_TARGET", + "b3y599q4": "BB_LOW_PRIORITY_HUNTING_TARGET", + "brrasnah": "BB_LOW_PRIORITY_HUNTING_TARGET", + "bd1towgc": "TRUE", + "b3cnse9r": "TRUE" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_single_type/mushroom_food", + "vision_range": 6 + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/mushroom.dm b/code/modules/mob/living/basic/space_fauna/mushroom.dm index e6195ba9d9cf..90f87c30d105 100644 --- a/code/modules/mob/living/basic/space_fauna/mushroom.dm +++ b/code/modules/mob/living/basic/space_fauna/mushroom.dm @@ -55,32 +55,21 @@ ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) /datum/ai_controller/basic_controller/mushroom + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/mushroom.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mushroom, BB_TARGET_MINIMUM_STAT = DEAD, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/mushroom_food, - ) - /datum/targeting_strategy/basic/mushroom + custom_faction_check = TRUE ///we only attacked another mushrooms /datum/targeting_strategy/basic/mushroom/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) return !living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly) -/datum/ai_planning_subtree/find_and_hunt_target/mushroom_food - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target - hunt_targets = list(/obj/item/food/grown/mushroom) - hunt_range = 6 - /mob/living/basic/mushroom/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) . = ..() if(!.) diff --git a/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.json b/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.json new file mode 100644 index 000000000000..2ba0d9b479a4 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.json @@ -0,0 +1,121 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/paper_wizard", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_MIMICS", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_SUMMON_MINIONS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_paper_and_write" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOUND_PAPER", + "target_source": "/datum/target_source/oview_single_type/paper", + "targeting_strategy": "/datum/targeting_strategy/empty_paper", + "time_between_perform": "10 SECONDS" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm b/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm index 24980cc01109..47ea7c39c470 100644 --- a/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm +++ b/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm @@ -47,6 +47,7 @@ AddElement(/datum/element/death_drops, /obj/effect/temp_visual/paperwiz_dying) /datum/ai_controller/basic_controller/paper_wizard + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_WRITING_LIST = list( @@ -57,46 +58,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/wizard_mimic, - /datum/ai_planning_subtree/use_mob_ability/wizard_summon_minions, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/attack_obstacle_in_path/paper_wizard, - /datum/ai_planning_subtree/find_paper_and_write, - ) - -/datum/ai_planning_subtree/attack_obstacle_in_path/paper_wizard - target_key = BB_FOUND_PAPER - attack_behaviour = /datum/ai_behavior/attack_obstructions/paper_wizard - -/datum/ai_behavior/attack_obstructions/paper_wizard - action_cooldown = 0.4 SECONDS - can_attack_turfs = TRUE - can_attack_dense_objects = TRUE - -/datum/ai_planning_subtree/targeted_mob_ability/wizard_mimic - ability_key = BB_WIZARD_MIMICS - finish_planning = FALSE - -/datum/ai_planning_subtree/use_mob_ability/wizard_summon_minions - ability_key = BB_WIZARD_SUMMON_MINIONS - finish_planning = FALSE - -/datum/ai_behavior/find_and_set/empty_paper - action_cooldown = 10 SECONDS - -/datum/ai_behavior/find_and_set/empty_paper/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/empty_papers = list() - - for(var/obj/item/paper/target_paper in oview(search_range, controller.pawn)) - if(target_paper.is_empty()) - empty_papers += target_paper - - if(empty_papers.len) - return pick(empty_papers) /mob/living/basic/paper_wizard/copy desc = "'Tis a ruse!" diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.json b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.json new file mode 100644 index 000000000000..8b9cf6851889 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.json @@ -0,0 +1,98 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/regal_rat", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_FLEE_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_RAISE_HORDE_ABILITY", + "target_key": "BB_BASIC_MOB_FLEE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability/domain", + "vars": { + "ability_key": "BB_DOMAIN_ABILITY" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm index b51871c7f9dd..83c3c18397b9 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm @@ -125,7 +125,6 @@ * * Convert nearby mice into aggressive rats. * * Convert nearby roaches into aggressive roaches. * * Convert nearby frogs into aggressive frogs. - * * Spawn a single mouse if below the mouse cap. */ /datum/action/cooldown/mob_cooldown/riot/proc/riot() playsound(owner, 'sound/mobs/non-humanoids/mouse/mousesqueek.ogg', vol = 150, frequency = 10000) @@ -144,15 +143,14 @@ command_feedback = "squeak!" // Frogs and roaches can squeak too it's fine pointed_reaction = "and squeaks aggressively" refuse_reaction = "quivers" - attack_behaviour = /datum/ai_behavior/basic_melee_attack -// Command you can give to a mouse to make it kill someone +// Command you can give to a glockroach to make it shoot someone /datum/pet_command/attack/glockroach speech_commands = list("attack", "sic", "kill", "cheese em") command_feedback = "squeak!" pointed_reaction = "and cocks its gun" refuse_reaction = "quivers" - attack_behaviour = /datum/ai_behavior/basic_ranged_attack/glockroach + attack_subtree = /datum/bt_node/subtree/pet_command/attack/ranged/glockroach /** *Spittle; harmless reagent that is added by rat king, and makes you disgusted. @@ -193,4 +191,4 @@ affected_mob.vomit(VOMIT_CATEGORY_DEFAULT) /datum/pet_command/protect_owner/glockroach - protect_behavior = /datum/ai_behavior/basic_ranged_attack/glockroach + protect_owner_subtree = /datum/bt_node/subtree/pet_command/protect_owner/ranged/glockroach diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm index 27f1eb47785b..e39fc380f247 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm @@ -1,34 +1,17 @@ /datum/ai_controller/basic_controller/regal_rat + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - // we pretty much do cheesy things (make the station worse) and don't deal with peasants (crew) unless they start to get in the way - // summon the horde if we get into a fight and then let the horde take care of it while we skedaddle - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/targeted_mob_ability/riot, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/use_mob_ability/domain, - ) - -/datum/ai_planning_subtree/targeted_mob_ability/riot - target_key = BB_BASIC_MOB_FLEE_TARGET // we only want to trigger this when provoked, manpower is low nowadays - ability_key = BB_RAISE_HORDE_ABILITY - finish_planning = FALSE - -/datum/ai_planning_subtree/use_mob_ability/domain - ability_key = BB_DOMAIN_ABILITY +/// Only activate the domain when it isn't already running. +/datum/bt_node/ai_behavior/use_mob_ability/domain -/datum/ai_planning_subtree/use_mob_ability/domain/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) +/datum/bt_node/ai_behavior/use_mob_ability/domain/perform(seconds_per_tick, datum/ai_controller/controller) var/datum/action/cooldown/mob_cooldown/domain/domain = controller.blackboard[ability_key] - if (!istype(domain) || domain.is_active) - return + if(!istype(domain) || domain.is_active) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED return ..() diff --git a/code/modules/mob/living/basic/space_fauna/roro.bt.json b/code/modules/mob/living/basic/space_fauna/roro.bt.json new file mode 100644 index 000000000000..cddeb4b8f1dd --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/roro.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/roro", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" +} diff --git a/code/modules/mob/living/basic/space_fauna/roro.dm b/code/modules/mob/living/basic/space_fauna/roro.dm index b9c4c9e759c2..43bdd1181403 100644 --- a/code/modules/mob/living/basic/space_fauna/roro.dm +++ b/code/modules/mob/living/basic/space_fauna/roro.dm @@ -55,15 +55,10 @@ AddElement(/datum/element/ai_retaliate) /datum/ai_controller/basic_controller/roro + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/roro.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/space_fauna/snake/banded.bt.json b/code/modules/mob/living/basic/space_fauna/snake/banded.bt.json new file mode 100644 index 000000000000..a05207e90eec --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/snake/banded.bt.json @@ -0,0 +1,92 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/snake/banded", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/snake/banded_snake.dm b/code/modules/mob/living/basic/space_fauna/snake/banded_snake.dm index f64208035438..811f092251b5 100644 --- a/code/modules/mob/living/basic/space_fauna/snake/banded_snake.dm +++ b/code/modules/mob/living/basic/space_fauna/snake/banded_snake.dm @@ -42,20 +42,18 @@ return . /datum/ai_controller/basic_controller/snake/banded + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/snake/banded.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("hsssss", "sssSSsssss...", "hiisssss"), + BB_EMOTE_HEAR = list("hisses."), + BB_EMOTE_SEE = list("slithers around.", "glances.", "stares."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/snake/snake_hissing1.ogg', 'sound/mobs/non-humanoids/snake/snake_hissing2.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/random_speech/snake, - ) - /mob/living/basic/snake/banded/harmless venom_dose = 0.4 diff --git a/code/modules/mob/living/basic/space_fauna/snake/snake.bt.json b/code/modules/mob/living/basic/space_fauna/snake/snake.bt.json new file mode 100644 index 000000000000..6748ce550795 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/snake/snake.bt.json @@ -0,0 +1,92 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/snake", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/snake/snake.dm b/code/modules/mob/living/basic/space_fauna/snake/snake.dm index aecfca8062a2..985f72a4ef84 100644 --- a/code/modules/mob/living/basic/space_fauna/snake/snake.dm +++ b/code/modules/mob/living/basic/space_fauna/snake/snake.dm @@ -74,18 +74,17 @@ /// Snakes are primarily concerned with getting those tasty, tasty mice, but aren't afraid to strike back at those who attack them /datum/ai_controller/basic_controller/snake + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/snake/snake.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("hsssss", "sssSSsssss...", "hiisssss"), + BB_EMOTE_HEAR = list("hisses."), + BB_EMOTE_SEE = list("slithers around.", "glances.", "stares."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/snake/snake_hissing1.ogg', 'sound/mobs/non-humanoids/snake/snake_hissing2.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/random_speech/snake, - ) diff --git a/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm b/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm deleted file mode 100644 index 189a7fb2eb09..000000000000 --- a/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm +++ /dev/null @@ -1,6 +0,0 @@ -/datum/ai_planning_subtree/random_speech/snake - speech_chance = 5 - speak = list("hsssss","sssSSsssss...","hiisssss") - sound = list('sound/mobs/non-humanoids/snake/snake_hissing1.ogg', 'sound/mobs/non-humanoids/snake/snake_hissing2.ogg') - emote_hear = list("hisses.") - emote_see = list("slithers around.", "glances.", "stares.") diff --git a/code/modules/mob/living/basic/space_fauna/spaceman.bt.json b/code/modules/mob/living/basic/space_fauna/spaceman.bt.json new file mode 100644 index 000000000000..c198789ac24b --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spaceman.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/spaceman", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spaceman.dm b/code/modules/mob/living/basic/space_fauna/spaceman.dm index ecb413bb81d7..6453a15719da 100644 --- a/code/modules/mob/living/basic/space_fauna/spaceman.dm +++ b/code/modules/mob/living/basic/space_fauna/spaceman.dm @@ -31,15 +31,9 @@ AddElement(/datum/element/ai_retaliate) /datum/ai_controller/basic_controller/spaceman + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spaceman.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.json b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.json new file mode 100644 index 000000000000..b8697161b74e --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.json @@ -0,0 +1,133 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/giant_spider", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SPIDER_WEB_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/spin_web", + "vars": { + "action_key": "BB_SPIDER_WEB_ACTION", + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_unwebbed_turf", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm index ae11657338b5..02fb234525e8 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm @@ -1,63 +1,35 @@ /// Attacks people it can see, spins webs if it can't see anything to attack. /datum/ai_controller/basic_controller/giant_spider + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), // Space spiders are taxonomically insects not arachnids, don't DM me + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/insect, // Space spiders are taxonomically insects not arachnids, don't DM me - /datum/ai_planning_subtree/find_unwebbed_turf, - /datum/ai_planning_subtree/spin_web, - ) /// Giant spider which won't attack structures /datum/ai_controller/basic_controller/giant_spider/weak - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/find_unwebbed_turf, - /datum/ai_planning_subtree/spin_web, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.json" /// Used by Araneus, who only attacks those who attack first. He is house-trained and will not web up the HoS office. /datum/ai_controller/basic_controller/giant_spider/retaliate - blackboard = list( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/insect, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.json" /// Retaliates, hunts other maintenance creatures, runs away from larger attackers, and spins webs. /datum/ai_controller/basic_controller/giant_spider/pest + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/ours_or_smaller, // Hunt mobs our size BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/larger, // Run away from mobs bigger than we are - ) - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/find_unwebbed_turf, - /datum/ai_planning_subtree/spin_web, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.json b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.json new file mode 100644 index 000000000000..5e8565657576 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.json @@ -0,0 +1,150 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/giant_spider/pest", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_BASIC_MOB_FLEE_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SPIDER_WEB_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/spin_web", + "vars": { + "action_key": "BB_SPIDER_WEB_ACTION", + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_unwebbed_turf", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.json b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.json new file mode 100644 index 000000000000..970784d7bf15 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.json @@ -0,0 +1,78 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/giant_spider/retaliate", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm deleted file mode 100644 index 20f6ce4baf08..000000000000 --- a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm +++ /dev/null @@ -1,89 +0,0 @@ -/// Search for a nearby location to put webs on -/datum/ai_planning_subtree/find_unwebbed_turf - -/datum/ai_planning_subtree/find_unwebbed_turf/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - controller.queue_behavior(/datum/ai_behavior/find_unwebbed_turf) - -/// Find an unwebbed nearby turf and store it -/datum/ai_behavior/find_unwebbed_turf - action_cooldown = 5 SECONDS - /// Where do we store the target data - var/target_key = BB_SPIDER_WEB_TARGET - /// How far do we look for unwebbed turfs? - var/scan_range = 3 - -/datum/ai_behavior/find_unwebbed_turf/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/spider = controller.pawn - var/atom/current_target = controller.blackboard[target_key] - if (current_target && !(locate(/obj/structure/spider/stickyweb) in current_target)) - // Already got a target - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.clear_blackboard_key(target_key) - var/turf/our_turf = get_turf(spider) - if (is_valid_web_turf(our_turf, spider)) - controller.set_blackboard_key(target_key, our_turf) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - var/list/turfs_by_range = list() - for (var/i in 1 to scan_range) - turfs_by_range["[i]"] = list() - for (var/turf/turf_in_view in oview(scan_range, our_turf)) - if (!is_valid_web_turf(turf_in_view, spider)) - continue - turfs_by_range["[get_dist(our_turf, turf_in_view)]"] += turf_in_view - - var/list/final_turfs - for (var/list/turf_list as anything in turfs_by_range) - if (length(turfs_by_range[turf_list])) - final_turfs = turfs_by_range[turf_list] - break - if (!length(final_turfs)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(target_key, pick(final_turfs)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_unwebbed_turf/proc/is_valid_web_turf(turf/target_turf, mob/living/spider) - if (locate(/obj/structure/spider/stickyweb) in target_turf) - return FALSE - if (HAS_TRAIT(target_turf, TRAIT_SPINNING_WEB_TURF)) - return FALSE - return !target_turf.is_blocked_turf(source_atom = spider) - -/// Run the spin web behaviour if we have an ability to use for it -/datum/ai_planning_subtree/spin_web - /// Key where the web spinning action is stored - var/action_key = BB_SPIDER_WEB_ACTION - /// Key where the target turf is stored - var/target_key = BB_SPIDER_WEB_TARGET - -/datum/ai_planning_subtree/spin_web/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (controller.blackboard_key_exists(action_key) && controller.blackboard_key_exists(target_key)) - controller.queue_behavior(/datum/ai_behavior/spin_web, action_key, target_key) - return SUBTREE_RETURN_FINISH_PLANNING - -/// Move to an unwebbed nearby turf and web it up -/datum/ai_behavior/spin_web - action_cooldown = 15 SECONDS // We don't want them doing this too quickly - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/spin_web/setup(datum/ai_controller/controller, action_key, target_key) - var/datum/action/cooldown/web_action = controller.blackboard[action_key] - var/turf/target_turf = controller.blackboard[target_key] - if (!web_action || !target_turf) - return FALSE - - set_movement_target(controller, target_turf) - return ..() - -/datum/ai_behavior/spin_web/perform(seconds_per_tick, datum/ai_controller/controller, action_key, target_key) - var/datum/action/cooldown/web_action = controller.blackboard[action_key] - if(web_action?.Trigger()) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/spin_web/finish_action(datum/ai_controller/controller, succeeded, action_key, target_key) - controller.clear_blackboard_key(target_key) - return ..() diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.json b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.json new file mode 100644 index 000000000000..42b2f434962b --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.json @@ -0,0 +1,121 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/giant_spider/weak", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SPIDER_WEB_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/spin_web", + "vars": { + "action_key": "BB_SPIDER_WEB_ACTION", + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_unwebbed_turf", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.json b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.json new file mode 100644 index 000000000000..0d0f9795a7c1 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.json @@ -0,0 +1,69 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/spiderling", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/consider_venting" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": false, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_ENTRY_VENT_TARGET", + "target_source": "/datum/target_source/oview_single_type/vent_pump", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 7 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm index e7120ba34cd6..130d22134c1f 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm @@ -60,21 +60,17 @@ /// Opportunistically hops in and out of vents, if it can find one. We aren't interested in attacking due to how weak we are, we gotta be quick and hidey. /datum/ai_controller/basic_controller/spiderling + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.json" blackboard = list( - BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/larger, // Run away from mobs bigger than we are + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/larger, // Run away from mobs bigger than we are BB_VENTCRAWL_COOLDOWN = 20 SECONDS, // enough time to get splatted while we're out in the open. BB_TIME_TO_GIVE_UP_ON_VENT_PATHING = 30 SECONDS, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - - // We understand that vents are nice little hidey holes through epigenetic inheritance, so we'll use them. - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/opportunistic_ventcrawler, - /datum/ai_planning_subtree/random_speech/insect, - ) diff --git a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.json b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.json new file mode 100644 index 000000000000..f042cc5d9460 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.json @@ -0,0 +1,149 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/young_spider", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1 + } + } + ] + } + ] + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SPIDER_WEB_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/spin_web", + "vars": { + "action_key": "BB_SPIDER_WEB_ACTION", + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_unwebbed_turf", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm index 436602b71ba0..a739ef2e9ece 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm @@ -28,26 +28,19 @@ /// Used by all young spiders if they ever appear. /datum/ai_controller/basic_controller/young_spider + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_BASIC_MOB_FLEE_DISTANCE = 6, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/find_unwebbed_turf, - /datum/ai_planning_subtree/spin_web, - ) /mob/living/basic/spider/growing/young/start_pulling(atom/movable/pulled_atom, state, force = move_force, supress_message = FALSE) // we're TOO FUCKING WEAK return diff --git a/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm b/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm index 03067edbc455..967cd4922657 100644 --- a/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm +++ b/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm @@ -30,42 +30,13 @@ . += mutable_appearance(hat::worn_icon, hat::worn_icon_state || hat::post_init_icon_state || hat::icon_state) /datum/ai_controller/basic_controller/stares_at_people + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_AGGRO_RANGE = 6, ) ai_movement = /datum/ai_movement/dumb - idle_behavior = null - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/face_target_or_face_initial, // we be creepy and all - ) - -/datum/ai_planning_subtree/face_target_or_face_initial - -/datum/ai_planning_subtree/face_target_or_face_initial/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(isnull(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET])) - return - var/mob/living/we = controller.pawn - controller.blackboard[BB_STARTING_DIRECTION] = we.dir - controller.queue_behavior(/datum/ai_behavior/face_target_or_face_initial, BB_BASIC_MOB_CURRENT_TARGET) - -/datum/ai_behavior/face_target_or_face_initial - -/datum/ai_behavior/face_target_or_face_initial/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/movable/target = controller.blackboard[target_key] - return ismovable(target) && isturf(target.loc) && ismob(controller.pawn) - -/datum/ai_behavior/face_target_or_face_initial/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/movable/target = controller.blackboard[target_key] - var/mob/living/we = controller.pawn - if(isnull(target) || get_dist(we, target) > 8) - we.dir = controller.blackboard[BB_STARTING_DIRECTION] - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - we.face_atom(target) - return AI_BEHAVIOR_DELAY /mob/living/basic/statue/mannequin/suspicious name = "mannequin?" @@ -75,17 +46,9 @@ ai_controller = /datum/ai_controller/basic_controller/suspicious_mannequin /datum/ai_controller/basic_controller/suspicious_mannequin + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - BB_AGGRO_RANGE = 14, - BB_EMOTE_KEY = "scream", //spooky ) - ai_movement = /datum/ai_movement/jps //threat - idle_behavior = null - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/run_emote, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.json b/code/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.json new file mode 100644 index 000000000000..b77a35e0108c --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.json @@ -0,0 +1,44 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/stares_at_people", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/face_target_or_face_initial", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/statue/statue.bt.json b/code/modules/mob/living/basic/space_fauna/statue/statue.bt.json new file mode 100644 index 000000000000..b999f3c6eed5 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/statue/statue.bt.json @@ -0,0 +1,125 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/statue", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "movement_failed": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "hunt_cooldown": "10 SECONDS", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": true, + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/hunt_target_list", + "targeting_strategy": "/datum/targeting_strategy/unbroken_light", + "vision_range": 7 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/statue/statue.dm b/code/modules/mob/living/basic/space_fauna/statue/statue.dm index b4bb04673635..bb62acade2bc 100644 --- a/code/modules/mob/living/basic/space_fauna/statue/statue.dm +++ b/code/modules/mob/living/basic/space_fauna/statue/statue.dm @@ -64,6 +64,7 @@ /datum/action/cooldown/spell/aoe/flicker_lights, ) grant_actions_by_list(innate_actions) + ai_controller.set_blackboard_key(BB_HUNT_TARGET_LIST, typecacheof(list(/obj/machinery/light))) /mob/living/basic/statue/med_hud_set_health() return //we're a statue we're invincible @@ -130,17 +131,12 @@ victim.adjust_temp_blindness(8 SECONDS) /datum/ai_controller/basic_controller/statue + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/statue/statue.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) - ai_movement = /datum/ai_movement/basic_avoidance - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures, - ) + ai_movement = /datum/ai_movement/jps /mob/living/basic/statue/frosty name = "Frosty" diff --git a/code/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.json b/code/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.json new file mode 100644 index 000000000000..f9143d9986c0 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.json @@ -0,0 +1,114 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/suspicious_mannequin", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "vision_range": 14 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "\"scream\"" + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/supermatter_spider.bt.json b/code/modules/mob/living/basic/space_fauna/supermatter_spider.bt.json new file mode 100644 index 000000000000..49c3e9f075f4 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/supermatter_spider.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/supermatter_spider", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat", + "bindings": { + "b95z0f0c": "/datum/bt_node/subtree/random_speech_loop" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm b/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm index 44d7585faffe..fdc0194999c1 100644 --- a/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm +++ b/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm @@ -83,22 +83,14 @@ single_use = FALSE /datum/ai_controller/basic_controller/supermatter_spider + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/supermatter_spider.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("clinks.", "clanks."), + BB_EMOTE_SEE = list("vibrates."), + BB_SPEAK_CHANCE = 7, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/random_speech/supermatter_spider, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/random_speech/supermatter_spider - speech_chance = 7 - emote_hear = list("clinks", "clanks") - emote_see = list("vibrates") diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm index 651cd8484582..288333add5e4 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm @@ -66,7 +66,7 @@ fugu.melee_damage_upper = 20 fugu.obj_damage = 60 fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) - fugu.ai_controller.CancelActions() + fugu.ai_controller.cancel_current_plan() /datum/status_effect/inflated/on_remove() . = ..() @@ -84,7 +84,7 @@ fugu.icon_state = "Fugu0" fugu.obj_damage = 0 fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) - fugu.ai_controller.CancelActions() + fugu.ai_controller.cancel_current_plan() /// Remove status effect if we die /datum/status_effect/inflated/proc/check_death(mob/living/source, new_stat) diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm index 9664ad5d96f3..ab7deaab7d7e 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm @@ -1,27 +1,6 @@ /// Cowardly when small, aggressive when big. Tries to transform whenever possible. /datum/ai_controller/basic_controller/wumborian_fugu + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) - - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/inflate, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/wumborian_fugu, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/attack_obstacle_in_path/wumborian_fugu - attack_behaviour = /datum/ai_behavior/attack_obstructions/wumborian_fugu - -/datum/ai_behavior/attack_obstructions/wumborian_fugu - can_attack_turfs = TRUE - action_cooldown = 2.5 SECONDS - -/datum/ai_planning_subtree/targeted_mob_ability/inflate - ability_key = BB_FUGU_INFLATE diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.json b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.json new file mode 100644 index 000000000000..bcff12180806 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.json @@ -0,0 +1,86 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/wumborian_fugu", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_FUGU_INFLATE", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions/attack_turfs", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/stoats/stoat.bt.json b/code/modules/mob/living/basic/stoats/stoat.bt.json new file mode 100644 index 000000000000..9c8d273c7433 --- /dev/null +++ b/code/modules/mob/living/basic/stoats/stoat.bt.json @@ -0,0 +1,123 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/stoat", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_dragging", + "vars": { + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_LAST_STOLEN_ITEM" + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/steal_and_flee" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_stealable_object" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/stoats/stoat_ai.dm b/code/modules/mob/living/basic/stoats/stoat_ai.dm index ecefb0fd93a1..181baf573dc0 100644 --- a/code/modules/mob/living/basic/stoats/stoat_ai.dm +++ b/code/modules/mob/living/basic/stoats/stoat_ai.dm @@ -1,23 +1,15 @@ /datum/ai_controller/basic_controller/stoat + behavior_tree_json = "code/modules/mob/living/basic/stoats/stoat.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/smaller, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_GUILTY_CONSCIOUS_CHANCE = 5, - BB_STEAL_CHANCE = 2, + BB_STEAL_CHANCE = 25, BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/stoat), BB_BABIES_CHILD_TYPES = list(/mob/living/basic/stoat/kit), + BB_FUCKS = TRUE ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/random_speech/blackboard, - /datum/ai_planning_subtree/steal_items, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/make_babies, - ) /datum/ai_controller/basic_controller/stoat/kit blackboard = list( @@ -25,14 +17,6 @@ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_GUILTY_CONSCIOUS_CHANCE = 5, BB_STEAL_CHANCE = 2, + BB_FUCKS = FALSE ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/random_speech/blackboard, - /datum/ai_planning_subtree/steal_items, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - ) diff --git a/code/modules/mob/living/basic/trader/trader.bt.json b/code/modules/mob/living/basic/trader/trader.bt.json new file mode 100644 index 000000000000..771bcabbaf29 --- /dev/null +++ b/code/modules/mob/living/basic/trader/trader.bt.json @@ -0,0 +1,154 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trader", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "avoid_friendly_fire": true, + "time_between_perform": "3 SECONDS" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "invert": true, + "key": "BB_SHOP_SPOT" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_FIRST_CUSTOMER" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_TRADER_RUSH_TO_SELL", + "value": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FIRST_CUSTOMER", + "required_dist": 1 + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/setup_shop" + } + ] + } + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/not_while_on_target", + "vars": { + "target_key": "BB_SHOP_SPOT" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_FIRST_CUSTOMER", + "target_source": "/datum/target_source/oview", + "targeting_strategy": "/datum/targeting_strategy/conscious_human", + "revalidation_mode": "TARGET_REVALIDATE" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/trader/trader.dm b/code/modules/mob/living/basic/trader/trader.dm index c23726a562bb..72515931df19 100644 --- a/code/modules/mob/living/basic/trader/trader.dm +++ b/code/modules/mob/living/basic/trader/trader.dm @@ -49,7 +49,7 @@ AddComponent(/datum/component/trader, trader_data = trader_data) AddComponent(/datum/component/ranged_attacks, casing_type = ranged_attack_casing, projectile_sound = ranged_attack_sound, cooldown_time = 3 SECONDS) AddElement(/datum/element/ai_retaliate) - AddElement(/datum/element/ai_swap_combat_mode, BB_BASIC_MOB_CURRENT_TARGET, string_list(trader_data.say_phrases[TRADER_BATTLE_START_PHRASE]), string_list(trader_data.say_phrases[TRADER_BATTLE_END_PHRASE])) + AddElement(/datum/element/ai_swap_combat_mode, BB_CURRENT_TARGET, string_list(trader_data.say_phrases[TRADER_BATTLE_START_PHRASE]), string_list(trader_data.say_phrases[TRADER_BATTLE_END_PHRASE])) if(LAZYLEN(loot)) loot = string_list(loot) AddElement(/datum/element/death_drops, loot) diff --git a/code/modules/mob/living/basic/trader/trader_ai.dm b/code/modules/mob/living/basic/trader/trader_ai.dm index f26ec242ed12..68700498ccce 100644 --- a/code/modules/mob/living/basic/trader/trader_ai.dm +++ b/code/modules/mob/living/basic/trader/trader_ai.dm @@ -1,93 +1,25 @@ /datum/ai_controller/basic_controller/trader + behavior_tree_json = "code/modules/mob/living/basic/trader/trader.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TRADER_RUSH_TO_SELL = FALSE ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/not_while_on_target/trader - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trader, - /datum/ai_planning_subtree/prepare_travel_to_destination/trader, - /datum/ai_planning_subtree/travel_to_point/and_clear_target, - /datum/ai_planning_subtree/setup_shop, - ) /datum/ai_controller/basic_controller/trader/jumpscare - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trader, - /datum/ai_planning_subtree/prepare_travel_to_destination/trader, - /datum/ai_planning_subtree/travel_to_point/and_clear_target, - /datum/ai_planning_subtree/setup_shop/jumpscare, + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TRADER_RUSH_TO_SELL = TRUE ) -/datum/ai_planning_subtree/basic_ranged_attack_subtree/trader - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trader - -/datum/ai_behavior/basic_ranged_attack/trader - action_cooldown = 3 SECONDS - avoid_friendly_fire = TRUE - -///Subtree to find our very first customer and set up our shop after walking right into their face -/datum/ai_planning_subtree/setup_shop - ///What do we do in order to offer our deals? - var/datum/ai_behavior/setup_shop/setup_shop_behavior = /datum/ai_behavior/setup_shop - -/datum/ai_planning_subtree/setup_shop/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - //If we don't have our ability, return - if(!controller.blackboard_key_exists(BB_SETUP_SHOP)) - return - - //If we already have a shop spot, return - if(controller.blackboard_key_exists(BB_SHOP_SPOT)) - return - - //If we don't have a costurmer to greet, look for one - if(!controller.blackboard_key_exists(BB_FIRST_CUSTOMER)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/conscious_person, BB_FIRST_CUSTOMER, /mob/living/carbon/human) - return - - //We have our first customer, time to tell them about incredible deals - controller.queue_behavior(setup_shop_behavior, BB_FIRST_CUSTOMER) - return SUBTREE_RETURN_FINISH_PLANNING - -///The ai will create a shop the moment they see a potential costumer -/datum/ai_behavior/setup_shop - -/datum/ai_behavior/setup_shop/setup(datum/ai_controller/controller, target_key) - var/obj/target = controller.blackboard[target_key] - return !QDELETED(target) - -/datum/ai_behavior/setup_shop/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - //We lost track of our costumer or our ability, abort - if(!controller.blackboard_key_exists(target_key) || !controller.blackboard_key_exists(BB_SETUP_SHOP)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED +///Triggers the shop setup action and clears the first customer key +/datum/bt_node/ai_behavior/setup_shop +/datum/bt_node/ai_behavior/setup_shop/perform(seconds_per_tick, datum/ai_controller/controller) var/datum/action/setup_shop/shop = controller.blackboard[BB_SETUP_SHOP] + if(!shop || !controller.blackboard_key_exists(BB_FIRST_CUSTOMER)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED shop.Trigger() - controller.clear_blackboard_key(BB_FIRST_CUSTOMER) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/idle_behavior/idle_random_walk/not_while_on_target/trader - target_key = BB_SHOP_SPOT - -///Version of setup show where the trader will run at you to assault you with incredible deals -/datum/ai_planning_subtree/setup_shop/jumpscare - setup_shop_behavior = /datum/ai_behavior/setup_shop/jumpscare - -/datum/ai_behavior/setup_shop/jumpscare - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/setup_shop/jumpscare/setup(datum/ai_controller/controller, target_key) - . = ..() - if(.) - set_movement_target(controller, controller.blackboard[target_key]) - -/datum/ai_behavior/setup_shop/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/tree.bt.json b/code/modules/mob/living/basic/tree.bt.json new file mode 100644 index 000000000000..ea3f7573b115 --- /dev/null +++ b/code/modules/mob/living/basic/tree.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/tree", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat", + "bindings": { + "b95z0f0c": "/datum/bt_node/subtree/random_speech_loop" + } +} diff --git a/code/modules/mob/living/basic/tree.dm b/code/modules/mob/living/basic/tree.dm index 5cf9c1bf27a2..c9714103a8c4 100644 --- a/code/modules/mob/living/basic/tree.dm +++ b/code/modules/mob/living/basic/tree.dm @@ -100,15 +100,13 @@ ) /datum/ai_controller/basic_controller/tree + behavior_tree_json = "code/modules/mob/living/basic/tree.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SEE = list("photosynthesizes angrily."), + BB_SPEAK_CHANCE = 3, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/tree, - ) diff --git a/code/modules/mob/living/basic/trooper/burst.bt.json b/code/modules/mob/living/basic/trooper/burst.bt.json new file mode 100644 index 000000000000..665988d1dc7c --- /dev/null +++ b/code/modules/mob/living/basic/trooper/burst.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper/ranged/burst", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/trooper_ranged", + "bindings": { + "b4kar3zl": "3 SECONDS", + "bnr1aazo": "TRUE" + } +} diff --git a/code/modules/mob/living/basic/trooper/nanotrasen.dm b/code/modules/mob/living/basic/trooper/nanotrasen.dm index 8798467969b9..a2494a5aad44 100644 --- a/code/modules/mob/living/basic/trooper/nanotrasen.dm +++ b/code/modules/mob/living/basic/trooper/nanotrasen.dm @@ -87,7 +87,7 @@ /mob/living/basic/trooper/nanotrasen/ranged/smg/peaceful desc = "An officer of Nanotrasen's private security force." - ai_controller = /datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful + ai_controller = /datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful_burst /mob/living/basic/trooper/nanotrasen/ranged/smg/peaceful/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/basic/trooper/peaceful.bt.json b/code/modules/mob/living/basic/trooper/peaceful.bt.json new file mode 100644 index 000000000000..17262a5b37af --- /dev/null +++ b/code/modules/mob/living/basic/trooper/peaceful.bt.json @@ -0,0 +1,138 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper/peaceful", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_CALL_REINFORCEMENTS_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements", + "vars": { + "target_key": "BB_CALL_REINFORCEMENTS_TARGET" + } + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CALL_REINFORCEMENTS_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/trooper/peaceful_burst.bt.json b/code/modules/mob/living/basic/trooper/peaceful_burst.bt.json new file mode 100644 index 000000000000..b77b87056422 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/peaceful_burst.bt.json @@ -0,0 +1,140 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful_burst", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_CALL_REINFORCEMENTS_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements", + "vars": { + "target_key": "BB_CALL_REINFORCEMENTS_TARGET" + } + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "time_between_perform": "3 SECONDS", + "avoid_friendly_fire": true + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CALL_REINFORCEMENTS_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/trooper/ranged.bt.json b/code/modules/mob/living/basic/trooper/ranged.bt.json new file mode 100644 index 000000000000..e28e6625b221 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/ranged.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper/ranged", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/trooper_ranged", + "bindings": { + "b4kar3zl": "1 SECONDS", + "beslksyc": "5", + "bnr1aazo": "TRUE" + } +} diff --git a/code/modules/mob/living/basic/trooper/shotgunner.bt.json b/code/modules/mob/living/basic/trooper/shotgunner.bt.json new file mode 100644 index 000000000000..3c2368d6afc5 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/shotgunner.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper/ranged/shotgunner", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/trooper_ranged", + "bindings": { + "b4kar3zl": "3 SECONDS", + "beslksyc": "3", + "bnr1aazo": "TRUE" + } +} diff --git a/code/modules/mob/living/basic/trooper/trooper.bt.json b/code/modules/mob/living/basic/trooper/trooper.bt.json new file mode 100644 index 000000000000..60f355e3cbf0 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/trooper.bt.json @@ -0,0 +1,118 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/trooper/trooper_ai.dm b/code/modules/mob/living/basic/trooper/trooper_ai.dm index b4cd1d52f6d9..6976cfaf62cf 100644 --- a/code/modules/mob/living/basic/trooper/trooper_ai.dm +++ b/code/modules/mob/living/basic/trooper/trooper_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/trooper + behavior_tree_json = "code/modules/mob/living/basic/trooper/trooper.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, @@ -6,99 +7,71 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, - ) - -/datum/ai_planning_subtree/basic_melee_attack_subtree/trooper - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack - -/datum/ai_planning_subtree/attack_obstacle_in_path/trooper - attack_behaviour = /datum/ai_behavior/attack_obstructions/trooper - -/datum/ai_behavior/attack_obstructions/trooper - action_cooldown = 1.2 SECONDS /datum/ai_controller/basic_controller/trooper/calls_reinforcements - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_CALLS_REINFORCEMENTS = TRUE, ) /datum/ai_controller/basic_controller/trooper/peaceful - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + behavior_tree_json = "code/modules/mob/living/basic/trooper/peaceful.bt.json" + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_CALLS_REINFORCEMENTS = TRUE ) +/datum/bt_node/subtree/trooper_ranged + behavior_tree_json = "code/modules/mob/living/basic/trooper/trooper_ranged.bt.json" + + /datum/ai_controller/basic_controller/trooper/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + behavior_tree_json = "code/modules/mob/living/basic/trooper/ranged.bt.json" + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_RANGED_SKIRMISH_MIN_DISTANCE = 3, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 4 ) -/datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trooper -/datum/ai_behavior/basic_ranged_attack/trooper - action_cooldown = 1 SECONDS - required_distance = 5 - avoid_friendly_fire = TRUE /datum/ai_controller/basic_controller/trooper/ranged/burst - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_burst, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + behavior_tree_json = "code/modules/mob/living/basic/trooper/burst.bt.json" + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_RANGED_SKIRMISH_MIN_DISTANCE = 2, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 3 ) -/datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_burst - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trooper_burst - -/datum/ai_behavior/basic_ranged_attack/trooper_burst - action_cooldown = 3 SECONDS - avoid_friendly_fire = TRUE - -/datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_burst, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, +//lol my parser cant deal with two subtypes with the same name so this is just a peaceful burst :DDDD ebin +/datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful_burst + behavior_tree_json = "code/modules/mob/living/basic/trooper/peaceful_burst.bt.json" + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_CALLS_REINFORCEMENTS = TRUE ) /datum/ai_controller/basic_controller/trooper/ranged/shotgunner - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_shotgun, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + behavior_tree_json = "code/modules/mob/living/basic/trooper/shotgunner.bt.json" + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_RANGED_SKIRMISH_MIN_DISTANCE = 2, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 3 ) -/datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_shotgun - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trooper_shotgun -/datum/ai_behavior/basic_ranged_attack/trooper_shotgun - action_cooldown = 3 SECONDS - required_distance = 3 - avoid_friendly_fire = TRUE /datum/ai_controller/basic_controller/trooper/viscerator blackboard = list( diff --git a/code/modules/mob/living/basic/trooper/trooper_ranged.bt.json b/code/modules/mob/living/basic/trooper/trooper_ranged.bt.json new file mode 100644 index 000000000000..4aa2adfbafb7 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/trooper_ranged.bt.json @@ -0,0 +1,137 @@ +{ + "dm_type": "/datum/bt_node/subtree/trooper_ranged", + "bindings": { + "b4kar3zl": { + "label": "time_between_perform", + "default": "0" + }, + "beslksyc": { + "label": "max_range", + "default": "3" + }, + "bnr1aazo": { + "label": "avoid_friendly_fire", + "default": "FALSE" + } + }, + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "time_between_perform": "$b4kar3zl", + "max_range": "$beslksyc", + "avoid_friendly_fire": "$bnr1aazo" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/turtle/turtle.bt.json b/code/modules/mob/living/basic/turtle/turtle.bt.json new file mode 100644 index 000000000000..5a912fb917f5 --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle.bt.json @@ -0,0 +1,169 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/turtle", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "ability_key": "BB_GENERIC_ACTION" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_GENERIC_ACTION" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TURTLE_HEADBUTT_VICTIM" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TURTLE_HEADBUTT_VICTIM", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_TURTLE_HEADBUTT_COOLDOWN", + "cooldown_duration": "1 MINUTES" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/headbutt_leg", + "vars": { + "target_key": "BB_TURTLE_HEADBUTT_VICTIM", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TURTLE_HEADBUTT_VICTIM" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TURTLE_FLORA_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_TURTLE_FLORA_COOLDOWN", + "cooldown_duration": "1 MINUTES" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/sniff_flora", + "vars": { + "target_key": "BB_TURTLE_FLORA_TARGET", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": "10" + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_TURTLE_HEADBUTT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TURTLE_HEADBUTT_VICTIM", + "target_source": "/datum/target_source/oview_typed/from_bb_key/turtle_headbutt_types", + "targeting_strategy": "/datum/targeting_strategy/legged_conscious_human" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_TURTLE_FLORA_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TURTLE_FLORA_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/turtle_flora_types", + "targeting_strategy": "/datum/targeting_strategy/sniffable_hydro" + } + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/express_happiness" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/turtle/turtle.dm b/code/modules/mob/living/basic/turtle/turtle.dm index 9f0ab89b07ae..d78450573dc3 100644 --- a/code/modules/mob/living/basic/turtle/turtle.dm +++ b/code/modules/mob/living/basic/turtle/turtle.dm @@ -53,6 +53,14 @@ var/static/list/indigestible_seeds = typecacheof(list( /obj/item/seeds/random, )) + var/static/list/headbutt_targets = list( + /mob/living/carbon/human, + ) + var/static/list/flora_to_smell = list( + /obj/machinery/hydroponics, + /obj/item/kirbyplants, + ) + /mob/living/basic/turtle/Initialize(mapload) . = ..() @@ -67,6 +75,8 @@ add_traits(list(TRAIT_NODROWN, TRAIT_SWIMMER), INNATE_TRAIT) var/static/list/eatable_food = list(/obj/item/seeds) ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(eatable_food)) + ai_controller.set_blackboard_key(BB_TURTLE_HEADBUTT_TYPES, typecacheof(headbutt_targets)) + ai_controller.set_blackboard_key(BB_TURTLE_FLORA_TYPES, typecacheof(flora_to_smell)) AddElement(/datum/element/basic_eating, food_types = eatable_food) AddComponent(/datum/component/happiness) RegisterSignal(src, COMSIG_MOB_PRE_EAT, PROC_REF(pre_eat_food)) @@ -164,7 +174,7 @@ developed_path = evolution_path var/datum/action/cooldown/tree_ability = new new_ability_path(src) tree_ability?.Grant(src) - ai_controller?.set_blackboard_key(BB_TURTLE_TREE_ABILITY, tree_ability) + ai_controller?.set_blackboard_key(BB_GENERIC_ACTION, tree_ability) STOP_PROCESSING(SSprocessing, src) update_appearance() diff --git a/code/modules/mob/living/basic/turtle/turtle_ai.dm b/code/modules/mob/living/basic/turtle/turtle_ai.dm index 1af7c3111f78..6bda4053c3ed 100644 --- a/code/modules/mob/living/basic/turtle/turtle_ai.dm +++ b/code/modules/mob/living/basic/turtle/turtle_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/turtle + behavior_tree_json = "code/modules/mob/living/basic/turtle/turtle.bt.json" blackboard = list( BB_HAPPY_EMOTIONS = list( "wiggles its tree in excitement!", @@ -15,78 +16,3 @@ ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/express_happiness, - /datum/ai_planning_subtree/use_mob_ability/turtle_tree, - /datum/ai_planning_subtree/find_and_hunt_target/headbutt_people, //playfully headbutt people's legs - /datum/ai_planning_subtree/find_and_hunt_target/sniff_flora, //mmm the aroma - ) - -/datum/ai_planning_subtree/use_mob_ability/turtle_tree - ability_key = BB_TURTLE_TREE_ABILITY - -/datum/ai_planning_subtree/use_mob_ability/turtle_tree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/happiness_count = controller.blackboard[BB_BASIC_HAPPINESS] * 100 - if(happiness_count > 75) - return ..() - if(!SPT_PROB(happiness_count / 50, seconds_per_tick)) - return - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/sniff_flora - target_key = BB_TURTLE_FLORA_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/sniff_flora - hunting_behavior = /datum/ai_behavior/hunt_target/sniff_flora - hunt_targets = list( - /obj/machinery/hydroponics, - /obj/item/kirbyplants, - ) - hunt_range = 5 - hunt_chance = 45 - -/datum/ai_behavior/find_hunt_target/sniff_flora - action_cooldown = 1 MINUTES - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_hunt_target/sniff_flora/valid_dinner(mob/living/source, obj/machinery/hydroponics/dinner, radius, datum/ai_controller/controller, seconds_per_tick) - if(!istype(dinner)) - return TRUE - if(isnull(dinner.myseed)) - return FALSE - if(dinner.weedlevel > 5 || dinner.pestlevel > 5) //too smelly - return FALSE - return can_see(source, dinner, radius) - -/datum/ai_behavior/hunt_target/sniff_flora - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/sniff_flora/target_caught(mob/living/hunter, atom/hunted) - hunter.manual_emote("Enjoys the sweet scent eminating from [hunted::name]!") - -/datum/ai_planning_subtree/find_and_hunt_target/headbutt_people - target_key = BB_TURTLE_HEADBUTT_VICTIM - finding_behavior = /datum/ai_behavior/find_hunt_target/human_to_headbutt - hunting_behavior = /datum/ai_behavior/hunt_target/headbutt_leg - hunt_targets = list(/mob/living/carbon/human) - hunt_range = 4 - hunt_chance = 45 - -/datum/ai_behavior/find_hunt_target/human_to_headbutt - action_cooldown = 2 MINUTES - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_hunt_target/human_to_headbutt/valid_dinner(mob/living/source, mob/living/carbon/human/dinner, radius, datum/ai_controller/controller, seconds_per_tick) - if(dinner.stat != CONSCIOUS) - return FALSE - if(isnull(dinner.get_bodypart(BODY_ZONE_R_LEG)) && isnull(dinner.get_bodypart(BODY_ZONE_L_LEG))) //no legs to headbutt! - return FALSE - return can_see(source, dinner, radius) - -/datum/ai_behavior/hunt_target/headbutt_leg - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/headbutt_leg/target_caught(mob/living/hunter, atom/hunted) - hunter.manual_emote("playfully headbutts [hunted]'s legs!") - diff --git a/code/modules/mob/living/basic/vermin/axolotl.bt.json b/code/modules/mob/living/basic/vermin/axolotl.bt.json new file mode 100644 index 000000000000..20dd0de5e095 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/axolotl.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/axolotl", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" +} diff --git a/code/modules/mob/living/basic/vermin/axolotl.dm b/code/modules/mob/living/basic/vermin/axolotl.dm index 730b22fb16df..b6ad8b5c1212 100644 --- a/code/modules/mob/living/basic/vermin/axolotl.dm +++ b/code/modules/mob/living/basic/vermin/axolotl.dm @@ -37,6 +37,6 @@ AddElement(/datum/element/can_be_held) /datum/ai_controller/basic_controller/axolotl + behavior_tree_json = "code/modules/mob/living/basic/vermin/axolotl.bt.json" ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk diff --git a/code/modules/mob/living/basic/vermin/butterfly.bt.json b/code/modules/mob/living/basic/vermin/butterfly.bt.json new file mode 100644 index 000000000000..439ec88c758a --- /dev/null +++ b/code/modules/mob/living/basic/vermin/butterfly.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/butterfly", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" +} diff --git a/code/modules/mob/living/basic/vermin/butterfly.dm b/code/modules/mob/living/basic/vermin/butterfly.dm index cbc847aa9e89..9edb092d965e 100644 --- a/code/modules/mob/living/basic/vermin/butterfly.dm +++ b/code/modules/mob/living/basic/vermin/butterfly.dm @@ -39,8 +39,7 @@ return TRUE //treaty signed at the Beeneeva convention /datum/ai_controller/basic_controller/butterfly - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk + behavior_tree_json = "code/modules/mob/living/basic/vermin/butterfly.bt.json" /mob/living/basic/butterfly/lavaland unsuitable_atmos_damage = 0 diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach.bt.json b/code/modules/mob/living/basic/vermin/cockroach/cockroach.bt.json new file mode 100644 index 000000000000..11d967e9330c --- /dev/null +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach.bt.json @@ -0,0 +1,80 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cockroach", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN", + "hunt_cooldown": "5 SECONDS", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/ants", + "targeting_strategy": "/datum/targeting_strategy/huntable", + "vision_range": 2 + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.json b/code/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.json new file mode 100644 index 000000000000..42333e89b353 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.json @@ -0,0 +1,145 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cockroach/aggro", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN", + "hunt_cooldown": "5 SECONDS", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/ants", + "targeting_strategy": "/datum/targeting_strategy/huntable", + "vision_range": 2 + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm b/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm index a6d42aa162f9..bd5e14e94eec 100644 --- a/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm @@ -1,64 +1,38 @@ /// AI controller for normal roach /datum/ai_controller/basic_controller/cockroach + behavior_tree_json = "code/modules/mob/living/basic/vermin/cockroach/cockroach.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_OWNER_SELF_HARM_RESPONSES = list( "*me waves its antennae in disapproval.", "*me chitters sadly." - ) + ), + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) /// AI controller for aggressive roach /datum/ai_controller/basic_controller/cockroach/aggro - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) + behavior_tree_json = "code/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.json" /// AI controller for roach who can shoot at you /datum/ai_controller/basic_controller/cockroach/glockroach - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/glockroach, //If we are attacking someone, this will prevent us from hunting - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/glockroach - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/glockroach - -/datum/ai_behavior/basic_ranged_attack/glockroach //Slightly slower, as this is being made in feature freeze ;) - action_cooldown = 1 SECONDS + behavior_tree_json = "code/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.json" /// roach who shoots at you slightly slower /datum/ai_controller/basic_controller/cockroach/mobroach - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/mobroach, - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) + behavior_tree_json = "code/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.json" -/datum/ai_planning_subtree/basic_ranged_attack_subtree/mobroach - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/mobroach +/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach //Slightly slower, as this is being made in feature freeze ;) + time_between_perform = 1 SECONDS -/datum/ai_behavior/basic_ranged_attack/mobroach - action_cooldown = 2 SECONDS +/datum/bt_node/ai_behavior/basic_ranged_attack/mobroach + time_between_perform = 2 SECONDS diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.json b/code/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.json new file mode 100644 index 000000000000..89b843a4538a --- /dev/null +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.json @@ -0,0 +1,143 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cockroach/glockroach", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN", + "hunt_cooldown": "5 SECONDS", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/ants", + "targeting_strategy": "/datum/targeting_strategy/huntable", + "vision_range": 2 + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.json b/code/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.json new file mode 100644 index 000000000000..ab18fc7ac40b --- /dev/null +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.json @@ -0,0 +1,143 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cockroach/mobroach", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/mobroach", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN", + "hunt_cooldown": "5 SECONDS", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/ants", + "targeting_strategy": "/datum/targeting_strategy/huntable", + "vision_range": 2 + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/crab.bt.json b/code/modules/mob/living/basic/vermin/crab.bt.json new file mode 100644 index 000000000000..c8798d612757 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/crab.bt.json @@ -0,0 +1,126 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/crab", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/go_for_swim" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_STOP_FLEEING", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_KEY_SWIMMER_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SWIM_ALTERNATE_TURF", + "targeting_strategy": "/datum/targeting_strategy/walkable_turf", + "target_source": "/datum/target_source/oview_water_turfs" + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/crab" + } + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/crab.dm b/code/modules/mob/living/basic/vermin/crab.dm index 8840bf3c1530..78468e3855e2 100644 --- a/code/modules/mob/living/basic/vermin/crab.dm +++ b/code/modules/mob/living/basic/vermin/crab.dm @@ -78,6 +78,7 @@ ///The basic ai controller for crabs /datum/ai_controller/basic_controller/crab + behavior_tree_json = "code/modules/mob/living/basic/vermin/crab.bt.json" blackboard = list( BB_ALWAYS_IGNORE_FACTION = TRUE, BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/smaller, @@ -86,13 +87,3 @@ ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/from_flee_key, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/crab, - /datum/ai_planning_subtree/go_for_swim, - ) diff --git a/code/modules/mob/living/basic/vermin/eat_cable.bt.json b/code/modules/mob/living/basic/vermin/eat_cable.bt.json new file mode 100644 index 000000000000..564fa4b28ece --- /dev/null +++ b/code/modules/mob/living/basic/vermin/eat_cable.bt.json @@ -0,0 +1,58 @@ +{ + "dm_type": "/datum/bt_node/subtree/eat_cable", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_MOUSE_WANTS_TO_EAT_CABLE" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_MOUSE_CABLE_HUNT_COOLDOWN", + "hunt_cooldown": "20 SECONDS" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cable", + "targeting_strategy": "/datum/targeting_strategy/accessible_cable", + "vision_range": 0 + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/vermin/eat_cheese.bt.json b/code/modules/mob/living/basic/vermin/eat_cheese.bt.json new file mode 100644 index 000000000000..ad71a43064e2 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/eat_cheese.bt.json @@ -0,0 +1,51 @@ +{ + "dm_type": "/datum/bt_node/subtree/eat_cheese", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_HUNTING_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "cooldown_key": "BB_MOUSE_CHEESE_HUNT_COOLDOWN", + "hunt_cooldown": "20 SECONDS" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_single_type/cheese", + "vision_range": 1 + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/vermin/frog.bt.json b/code/modules/mob/living/basic/vermin/frog.bt.json new file mode 100644 index 000000000000..fe676a7bea31 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/frog.bt.json @@ -0,0 +1,60 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/frog", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/frog_engage_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/go_for_swim" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_KEY_SWIMMER_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SWIM_ALTERNATE_TURF", + "targeting_strategy": "/datum/targeting_strategy/walkable_turf", + "target_source": "/datum/target_source/oview_water_turfs" + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/frog" + } + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/frog.dm b/code/modules/mob/living/basic/vermin/frog.dm index d7dbe3848f82..5ffe3326de09 100644 --- a/code/modules/mob/living/basic/vermin/frog.dm +++ b/code/modules/mob/living/basic/vermin/frog.dm @@ -146,7 +146,12 @@ AddComponent(/datum/component/explode_on_attack, mob_type_dont_bomb = typecacheof(list(/mob/living/basic/frog, /mob/living/basic/leaper))) addtimer(CALLBACK(src, PROC_REF(death)), existence_period) +/// Engage our current target: flee from scary fishermen, otherwise beat them up. +/datum/bt_node/subtree/frog_engage_target + behavior_tree_json = "code/modules/mob/living/basic/vermin/frog_engage_target.bt.json" + /datum/ai_controller/basic_controller/frog + behavior_tree_json = "code/modules/mob/living/basic/vermin/frog.bt.json" blackboard = list( BB_BASIC_MOB_STOP_FLEEING = TRUE, //We only flee from scary fishermen. BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, @@ -158,33 +163,14 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/random_speech/frog, - /datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman, - /datum/ai_planning_subtree/flee_target/from_fisherman, - /datum/ai_planning_subtree/go_for_swim, - ) /datum/ai_controller/basic_controller/frog/trash - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/random_speech/frog, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman, - /datum/ai_planning_subtree/flee_target/from_fisherman, - ) + behavior_tree_json = "code/modules/mob/living/basic/vermin/trash.bt.json" /datum/ai_controller/basic_controller/frog/suicide_frog + behavior_tree_json = "code/modules/mob/living/basic/vermin/suicide_frog.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_TARGET_PRIORITY_TRAIT = TRAIT_SCARY_FISHERMAN, //No fear, only hatred. It has nothing to lose ) - - planning_subtrees = list( - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/vermin/frog_engage_target.bt.json b/code/modules/mob/living/basic/vermin/frog_engage_target.bt.json new file mode 100644 index 000000000000..f8bd885f6bcf --- /dev/null +++ b/code/modules/mob/living/basic/vermin/frog_engage_target.bt.json @@ -0,0 +1,90 @@ +{ + "dm_type": "/datum/bt_node/subtree/frog_engage_target", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_trait", + "vars": { + "key": "BB_CURRENT_TARGET", + "trait": "TRAIT_SCARY_FISHERMAN" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_flee_location", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "destination_key": "BB_FLEE_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FLEE_LOCATION", + "required_dist": 0, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + } + ] + } + ] + } +} diff --git a/code/modules/mob/living/basic/vermin/lizard.bt.json b/code/modules/mob/living/basic/vermin/lizard.bt.json new file mode 100644 index 000000000000..80e4cc2575ea --- /dev/null +++ b/code/modules/mob/living/basic/vermin/lizard.bt.json @@ -0,0 +1,35 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/lizard", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/lizard" + } + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/lizard.dm b/code/modules/mob/living/basic/vermin/lizard.dm index a61b1604c549..38311a4c73d5 100644 --- a/code/modules/mob/living/basic/vermin/lizard.dm +++ b/code/modules/mob/living/basic/vermin/lizard.dm @@ -64,17 +64,13 @@ ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(edibles)) /datum/ai_controller/basic_controller/lizard + behavior_tree_json = "code/modules/mob/living/basic/vermin/lizard.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/random_speech/lizard, - ) //Subtypes of lizards follow. diff --git a/code/modules/mob/living/basic/vermin/mothroach/mothroach.bt.json b/code/modules/mob/living/basic/vermin/mothroach/mothroach.bt.json new file mode 100644 index 000000000000..44dcc0a750bf --- /dev/null +++ b/code/modules/mob/living/basic/vermin/mothroach/mothroach.bt.json @@ -0,0 +1,81 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mothroach", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/override_id_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/mothroach" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm b/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm index c589152f1cdc..cfddecf2495f 100644 --- a/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm +++ b/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/mothroach + behavior_tree_json = "code/modules/mob/living/basic/vermin/mothroach/mothroach.bt.json" blackboard = list( BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -8,23 +9,3 @@ ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_food/mothroach, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/random_speech/mothroach, - ) - -/datum/ai_planning_subtree/find_food/mothroach - finding_behavior = /datum/ai_behavior/find_and_set/in_list/mothroach_food - -/datum/ai_behavior/find_and_set/in_list/mothroach_food - -/datum/ai_behavior/find_and_set/in_list/mothroach_food/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) - var/mob/living/living_pawn = controller.pawn - found -= living_pawn.loc - if(length(found)) - return pick(found) diff --git a/code/modules/mob/living/basic/vermin/mouse.bt.json b/code/modules/mob/living/basic/vermin/mouse.bt.json new file mode 100644 index 000000000000..ce8f8c1c1b2b --- /dev/null +++ b/code/modules/mob/living/basic/vermin/mouse.bt.json @@ -0,0 +1,189 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mouse", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/override_id_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/eat_cheese" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOUSE_CHEESE_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cheese", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 1 + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/play_instrument_on_floor" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/eat_cable" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOUSE_CHEESE_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cheese", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 1 + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SONG_INSTRUMENT", + "target_source": "/datum/target_source/oview_single_type/piano_synth", + "targeting_strategy": "/datum/targeting_strategy/playable_synthesizer" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_MOUSE_WANTS_TO_EAT_CABLE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cable", + "targeting_strategy": "/datum/targeting_strategy/accessible_cable", + "vision_range": 0 + } + } + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/mouse" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOUSE_CABLE_HUNT_COOLDOWN" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.01 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_MOUSE_WANTS_TO_EAT_CABLE", + "value": true + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm index 3e92f78c8afb..efb2d4b19c03 100644 --- a/code/modules/mob/living/basic/vermin/mouse.dm +++ b/code/modules/mob/living/basic/vermin/mouse.dm @@ -199,7 +199,7 @@ new /obj/effect/temp_visual/heart(loc) add_faction(FACTION_NEUTRAL) try_consume_cheese(cheese) - ai_controller.CancelActions() // Interrupt any current fleeing + ai_controller.cancel_current_plan() // Interrupt any current fleeing /// Attempts to consume a piece of cheese, causing a few effects. /mob/living/basic/mouse/proc/try_consume_cheese(obj/item/food/cheese/cheese) @@ -422,6 +422,7 @@ /// The mouse AI controller /datum/ai_controller/basic_controller/mouse + behavior_tree_json = "code/modules/mob/living/basic/vermin/mouse.bt.json" blackboard = list( // Always cowardly BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, // Use this to find people to run away from BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -431,41 +432,14 @@ ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - // Try to speak, because it's cute - /datum/ai_planning_subtree/random_speech/mouse, - // Follow the boss's orders - /datum/ai_planning_subtree/pet_planning, - // Look for and execute hunts for cheese even if someone is looking at us - /datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese, - // Next priority is to try and appreoach a keyboard - /datum/ai_planning_subtree/approach_synthesizer, - // And play it if we are near it - /datum/ai_planning_subtree/generic_play_instrument/end_planning, - // Next priority is see if anyone is looking at us - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - // Skedaddle - /datum/ai_planning_subtree/flee_target/mouse, - // Otherwise, look for and execute hunts for cabling - /datum/ai_planning_subtree/find_and_hunt_target/look_for_cables, - ) - -/// Don't look for anything to run away from if you are distracted by being adjacent to cheese -/datum/ai_planning_subtree/flee_target/mouse - -/datum/ai_planning_subtree/flee_target/mouse/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/hunted_cheese = controller.blackboard[BB_CURRENT_HUNTING_TARGET] - if (!isnull(hunted_cheese)) - return // We see some cheese, which is more important than our life - return ..() /// AI controller for rats, slightly more complex than mice becuase they attack people /datum/ai_controller/basic_controller/mouse/rat + behavior_tree_json = "code/modules/mob/living/basic/vermin/mouse_rat.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, - BB_BASIC_MOB_CURRENT_TARGET = null, // heathen + BB_CURRENT_TARGET = null, // heathen BB_CURRENT_HUNTING_TARGET = null, // cheese BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable BB_OWNER_SELF_HARM_RESPONSES = list( @@ -477,14 +451,15 @@ ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese, - /datum/ai_planning_subtree/random_speech/mouse, - /datum/ai_planning_subtree/find_and_hunt_target/look_for_cables, - ) + + + + +/datum/bt_node/subtree/eat_cable + behavior_tree_json = "code/modules/mob/living/basic/vermin/eat_cable.bt.json" + +/datum/bt_node/subtree/eat_cheese + behavior_tree_json = "code/modules/mob/living/basic/vermin/eat_cheese.bt.json" + +/datum/bt_node/subtree/play_instrument_on_floor + behavior_tree_json = "code/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.json" diff --git a/code/modules/mob/living/basic/vermin/mouse_rat.bt.json b/code/modules/mob/living/basic/vermin/mouse_rat.bt.json new file mode 100644 index 000000000000..2fa9b856d770 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/mouse_rat.bt.json @@ -0,0 +1,217 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mouse/rat", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/override_id_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [] + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/eat_cheese" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/eat_cable" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOUSE_CHEESE_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cheese", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 1 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_MOUSE_WANTS_TO_EAT_CABLE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cable", + "targeting_strategy": "/datum/targeting_strategy/accessible_cable", + "vision_range": 0 + } + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/mouse" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOUSE_CABLE_HUNT_COOLDOWN" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.01 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_MOUSE_WANTS_TO_EAT_CABLE", + "value": true + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.json b/code/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.json new file mode 100644 index 000000000000..d6ea55eb32c8 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.json @@ -0,0 +1,78 @@ +{ + "dm_type": "/datum/bt_node/subtree/play_instrument_on_floor", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SONG_INSTRUMENT" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "maximum_distance": 1, + "require_reach": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/keep_playing_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT" + } + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SONG_INSTRUMENT", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/setup_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT", + "song_lines_key": "BB_SONG_LINES" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/play_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT", + "volume": 50 + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SONG_INSTRUMENT", + "target_source": "/datum/target_source/oview_single_type/piano_synth", + "targeting_strategy": "/datum/targeting_strategy/playable_synthesizer" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/vermin/space_bat.bt.json b/code/modules/mob/living/basic/vermin/space_bat.bt.json new file mode 100644 index 000000000000..cd360abe561a --- /dev/null +++ b/code/modules/mob/living/basic/vermin/space_bat.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/space_bat", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" +} diff --git a/code/modules/mob/living/basic/vermin/space_bat.dm b/code/modules/mob/living/basic/vermin/space_bat.dm index 4ef58a45c3ac..27f14f496721 100644 --- a/code/modules/mob/living/basic/vermin/space_bat.dm +++ b/code/modules/mob/living/basic/vermin/space_bat.dm @@ -41,15 +41,10 @@ ///Controller for space bats, has nothing unique, just retaliation. /datum/ai_controller/basic_controller/space_bat + behavior_tree_json = "code/modules/mob/living/basic/vermin/space_bat.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/vermin/suicide_frog.bt.json b/code/modules/mob/living/basic/vermin/suicide_frog.bt.json new file mode 100644 index 000000000000..a3fa8cdce070 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/suicide_frog.bt.json @@ -0,0 +1,71 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/frog/suicide_frog", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/trash.bt.json b/code/modules/mob/living/basic/vermin/trash.bt.json new file mode 100644 index 000000000000..a51cb3f8b0b4 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/trash.bt.json @@ -0,0 +1,61 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/frog/trash", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/frog_engage_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/frog" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 0e76510f14e8..eef3acfde9b6 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1029,7 +1029,8 @@ GAME_VERB_PROC(/mob/living, mob_sleep, "Sleep", null) if (!buckled.anchored) buckled.moving_from_pull = moving_from_pull . = buckled.Move(newloc, direct, glide_size) - buckled.moving_from_pull = null + if(buckled) //buckled can become unbuckled, apparently + buckled.moving_from_pull = null return var/old_direction = dir @@ -1960,10 +1961,10 @@ GLOBAL_LIST_EMPTY(fire_appearances) //Check the amount of clients exists on the Z level we're leaving from, //this excludes us because at this point we are not registered to any z level. var/old_level_new_clients = (registered_z ? SSmobs.clients_by_zlevel[registered_z].len : null) - //No one is left after we're gone, shut off inactive ones + //No one is left after we're gone, recalculate AI status so eligible ones shut off if(registered_z && old_level_new_clients == 0) - for(var/datum/ai_controller/controller as anything in GLOB.ai_controllers_by_zlevel[registered_z]) - controller.set_ai_status(AI_STATUS_OFF) + for(var/datum/ai_controller/controller as anything in SSai_controllers.ai_controllers_by_zlevel[registered_z]) + controller.set_ai_status(controller.get_expected_ai_status()) if(new_z) //Check the amount of clients exists on the Z level we're moving towards, excluding ourselves. @@ -1973,7 +1974,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) SSmobs.clients_by_zlevel[new_z] += src if(new_level_old_clients == 0) //No one was here before, wake up all the AIs. - for (var/datum/ai_controller/controller as anything in GLOB.ai_controllers_by_zlevel[new_z]) + for (var/datum/ai_controller/controller as anything in SSai_controllers.ai_controllers_by_zlevel[new_z]) //We don't set them directly on, for instances like AIs acting while dead and other cases that may exist in the future. //This isn't a problem for AIs with a client since the client will prevent this from being called anyway. controller.set_ai_status(controller.get_expected_ai_status()) diff --git a/code/modules/mob/living/simple_animal/bot/bot_announcement.dm b/code/modules/mob/living/simple_animal/bot/bot_announcement.dm index 10161dddfd9d..0ce079dbe642 100644 --- a/code/modules/mob/living/simple_animal/bot/bot_announcement.dm +++ b/code/modules/mob/living/simple_animal/bot/bot_announcement.dm @@ -153,7 +153,7 @@ if (!(bot_owner.bot_mode_flags & BOT_MODE_ON)) return - bot_owner.say(line) + INVOKE_ASYNC(bot_owner, TYPE_PROC_REF(/atom/movable, say), line) if (channel && bot_owner.internal_radio.channels[channel]) bot_owner.internal_radio.talk_into(bot_owner, message = line, channel = channel) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm index 275ba78c4f07..2cd694ae187f 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm @@ -81,7 +81,7 @@ Difficulty: Extremely Hard AddElement(/datum/element/knockback, 7, FALSE, TRUE) AddElement(/datum/element/lifesteal, 50) ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) - AddComponent(/datum/component/boss_music, 'sound/music/boss/bdm_boss.ogg', COMSIG_HOSTILE_FOUND_TARGET) // change to COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET) in basic conversion + AddComponent(/datum/component/boss_music, 'sound/music/boss/bdm_boss.ogg', COMSIG_HOSTILE_FOUND_TARGET) // change to COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET) in basic conversion /mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Destroy() frost_orbs = null diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index 5e992bced04f..ea465bfcd88d 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -102,7 +102,7 @@ Difficulty: Hard /mob/living/simple_animal/hostile/megafauna/hierophant/Initialize(mapload) . = ..() spawned_beacon_ref = WEAKREF(new /obj/effect/hierophant(loc)) - AddComponent(/datum/component/boss_music, 'sound/music/boss/hiero_boss.ogg', COMSIG_HOSTILE_FOUND_TARGET) // change to COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET) in basic conversion + AddComponent(/datum/component/boss_music, 'sound/music/boss/hiero_boss.ogg', COMSIG_HOSTILE_FOUND_TARGET) // change to COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET) in basic conversion /mob/living/simple_animal/hostile/megafauna/hierophant/Destroy() QDEL_NULL(spawned_beacon_ref) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index e2f8c0662c71..3bc485478020 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1383,9 +1383,6 @@ GAME_VERB_HIDDEN(/mob, DisDblClick, ".dblclick", argu = null as anything, sec = if(href_list[VV_HK_GIVE_AI]) return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/give_ai_controller, src) - if(href_list[VV_HK_GIVE_AI_SPEECH]) - return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/give_ai_speech, src) - if(href_list[VV_HK_GIVE_MOB_ACTION]) return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/give_mob_action, src) diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index bdba1f80a84d..9ef5866c1d96 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -509,6 +509,8 @@ GAME_VERB_SRC(/obj/item/paper, rename, usr, "Rename paper", null) ) /obj/item/paper/ui_interact(mob/user, datum/tgui/ui) + if(!user.client) //bro stop trying to open UI on AI man ur gonna drive me nuts man comeon man + return if(resistance_flags & ON_FIRE) return ui = SStgui.try_update_ui(user, src, ui) diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm index f8b0a4f429a7..76938d708031 100644 --- a/code/modules/projectiles/guns/magic/staff.dm +++ b/code/modules/projectiles/guns/magic/staff.dm @@ -103,7 +103,7 @@ var/obj/item/my_thing = pop(my_shit) user.dropItemToGround(my_thing) var/mob/living/angry_thing = my_thing.animate_atom_living() - angry_thing.ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, user) + angry_thing.ai_controller?.set_blackboard_key(BB_CURRENT_TARGET, user) angry_thing.ai_controller?.set_blackboard_key(BB_TARGET_MINIMUM_STAT, HARD_CRIT) angry_thing.ai_controller?.ai_interact(user, combat_mode = TRUE) user.apply_damage(35, BRUTE, forced = TRUE) // Mimics are not actually very strong so we pretend that it just bit us so we die faster, at least 3 charges & worn items should do it diff --git a/code/modules/projectiles/guns/magic/wands/wand_rebel.dm b/code/modules/projectiles/guns/magic/wands/wand_rebel.dm index 74cabaf9d0fb..93f164b1c544 100644 --- a/code/modules/projectiles/guns/magic/wands/wand_rebel.dm +++ b/code/modules/projectiles/guns/magic/wands/wand_rebel.dm @@ -71,5 +71,5 @@ var/mob/living/bully = animate_item.animate_atom_living(firer) if (!bully.ai_controller) return - bully.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, victim) + bully.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, victim) bully.ai_controller.ai_interact(victim, combat_mode = TRUE) diff --git a/code/modules/research/xenobiology/crossbreeding/_mobs.dm b/code/modules/research/xenobiology/crossbreeding/_mobs.dm index 9b37be5d1a5d..2d136602b3dc 100644 --- a/code/modules/research/xenobiology/crossbreeding/_mobs.dm +++ b/code/modules/research/xenobiology/crossbreeding/_mobs.dm @@ -48,7 +48,7 @@ Slimecrossing Mobs gold_core_spawnable = NO_SPAWN speak_emote = list("blorbles", "bubbles", "borks") -/mob/living/basic/pet/dog/corgi/puppy/slime/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) - speech.speak = string_list(list()) - speech.emote_hear = string_list(list("bubbles!", "splorts.", "splops!")) - speech.emote_see = string_list(list("gets goop everywhere.", "flops.", "jiggles!")) +/mob/living/basic/pet/dog/corgi/puppy/slime/update_dog_speech(list/speech_data) + speech_data[BB_EMOTE_SAY] = string_list(list()) + speech_data[BB_EMOTE_HEAR] = string_list(list("bubbles!", "splorts.", "splops!")) + speech_data[BB_EMOTE_SEE] = string_list(list("gets goop everywhere.", "flops.", "jiggles!")) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index d714702deeaf..099d9a2eff7f 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -179,7 +179,6 @@ #include "embedding.dm" #include "emoting.dm" #include "emp_flashlight.dm" -#include "ensure_subtree_operational_datum.dm" #include "ethereal_revival.dm" #include "explosion_action.dm" #include "firedoor_regions.dm" diff --git a/code/modules/unit_tests/ensure_subtree_operational_datum.dm b/code/modules/unit_tests/ensure_subtree_operational_datum.dm deleted file mode 100644 index 9ca78fbc6746..000000000000 --- a/code/modules/unit_tests/ensure_subtree_operational_datum.dm +++ /dev/null @@ -1,64 +0,0 @@ -/// The subtree that requires the operational datum. -#define REQUIRED_SUBTREE "required_subtree" -/// The list of typepaths of applicable operational datums that would satisfy the requirement. -#define REQUIRED_OPERATIONAL_DATUMS "required_operational_datums" - -/// Unit Test that ensure that if we add a specific planning subtree to a basic mob's planning tree, that we also have the operational datum needed for it (component/element). -/// This can be extended to other "mandatory" operational datums for certain subtrees to work. -/datum/unit_test/ensure_subtree_operational_datum - /// Associated list of mobs that we need to test this on. Key is the typepath of the mob, value is a list of the planning subtree and the operational datums that are required for it. - var/list/testable_mobs = list() - -/datum/unit_test/ensure_subtree_operational_datum/Run() - gather_testable_mobs() - test_applicable_mobs() - -/// First, look for all mobs that have a planning subtree that requires an element, then add it to the list for stuff to test afterwards. Done like this to not have one mumbo proc that's hard to read. -/datum/unit_test/ensure_subtree_operational_datum/proc/gather_testable_mobs() - for(var/mob/living/basic/checkable_mob as anything in subtypesof(/mob/living/basic)) - var/datum/ai_controller/testable_controller = initial(checkable_mob.ai_controller) - if(isnull(testable_controller)) - continue - - // we can't do inital() memes on lists so it's allocation time - testable_controller = allocate(testable_controller) - var/list/ai_planning_subtrees = testable_controller.planning_subtrees // list of instantiated datums. easy money - if(!length(ai_planning_subtrees)) - continue - - for(var/datum/ai_planning_subtree/testable_subtree as anything in ai_planning_subtrees) - var/list/necessary_datums = testable_subtree.operational_datums - if(isnull(necessary_datums)) - continue - - testable_mobs[checkable_mob] = list( - REQUIRED_OPERATIONAL_DATUMS = necessary_datums, - REQUIRED_SUBTREE = testable_subtree.type, - ) - -/// Then, test the mobs that we've found -/datum/unit_test/ensure_subtree_operational_datum/proc/test_applicable_mobs() - for(var/mob/living/basic/checkable_mob as anything in testable_mobs) - var/list/checkable_mob_data = testable_mobs[checkable_mob] - checkable_mob = allocate(checkable_mob) - - var/datum/ai_planning_subtree/test_subtree = checkable_mob_data[REQUIRED_SUBTREE] - var/list/trait_sources = GET_TRAIT_SOURCES(checkable_mob, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM) - if(!length(trait_sources)) // yes yes we could use `COUNT_TRAIT_SOURCES` but why invoke the same macro twice - TEST_FAIL("The mob [checkable_mob] ([checkable_mob.type]) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, but has a planning subtree ([test_subtree]) that requires it!") - continue - - var/has_element = FALSE - var/list/testable_operational_datums = checkable_mob_data[REQUIRED_OPERATIONAL_DATUMS] - for(var/iterable in trait_sources) - if(iterable in testable_operational_datums) - has_element = TRUE - break - - if(!has_element) - var/list/message_list = list("The mob [checkable_mob] ([checkable_mob.type]) has a planning subtree ([test_subtree]) that requires a component/element, but does not have any!") - message_list += "Needs one of the following to satisfy the requirement: ([testable_operational_datums.Join(", ")])" - TEST_FAIL(message_list.Join(" ")) - -#undef REQUIRED_SUBTREE -#undef REQUIRED_OPERATIONAL_DATUMS diff --git a/code/modules/unit_tests/mouse_bite_cable.dm b/code/modules/unit_tests/mouse_bite_cable.dm index dc7fd04d24cb..53a28bf10792 100644 --- a/code/modules/unit_tests/mouse_bite_cable.dm +++ b/code/modules/unit_tests/mouse_bite_cable.dm @@ -20,21 +20,19 @@ // Ai controlling processes expect a seconds_per_tick, supply a real-fake dt var/fake_dt = SSai_controllers.wait * 0.1 - // Set AI - AIs by default are off in z-levels with no client, we have to force it on. + // Force this fker to be on + biter.ai_controller.ai_traits |= RUN_WHILE_UNWATCHED biter.ai_controller.set_ai_status(AI_STATUS_ON) - biter.ai_controller.can_idle = FALSE - // Select behavior - this will queue finding the cable - biter.ai_controller.SelectBehaviors(fake_dt) - // Process behavior - this will execute the "locate the cable" behavior - biter.ai_controller.process(fake_dt) - // Check that the cable was found - TEST_ASSERT(biter.ai_controller.blackboard[BB_LOW_PRIORITY_HUNTING_TARGET] == wire, "Mouse, after executing find, did not set the cable as a target.") - // Select behavior - this will queue hunting - biter.ai_controller.SelectBehaviors(fake_dt) - // Process behavior - this will execute the hunt for the cable and cause a bite (as we're in the min range) - biter.ai_controller.process(fake_dt) - // Check that the cable was removed, as it was hunted correctly - TEST_ASSERT_NULL(biter.ai_controller.blackboard[BB_LOW_PRIORITY_HUNTING_TARGET], "Mouse, after executing hunt, did not clear their target blackboard.") + + // Mouse eating is chance-based, so we set the hunting target directly, yes, this messed with the test from what it was before, but I'm not sure how to do it better :( + biter.ai_controller.set_blackboard_key(BB_LOW_PRIORITY_HUNTING_TARGET, wire) + + // Tick the tree until the hunt branch moves onto and bites the cable. Can we do this better in unit tests?? idk.. + for(var/i in 1 to 5) + if(QDELETED(biter)) + break + biter.ai_controller.SelectBehaviors(fake_dt) + biter.ai_controller.process(fake_dt) // Now check that the bite went through - remember we qdel mice on death TEST_ASSERT(QDELETED(biter), "Mouse, did not die after biting a powered cable.") @@ -47,12 +45,3 @@ /// Dummy mouse that is guaranteed to die when biting shocked cables. /mob/living/basic/mouse/cable_lover cable_zap_prob = 100 - ai_controller = /datum/ai_controller/basic_controller/mouse/guaranteed_to_bite - -/// Dummy mouse's ai controller that is guaranteed to find and bite a cable beneath it -/datum/ai_controller/basic_controller/mouse/guaranteed_to_bite - planning_subtrees = list(/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables/guaranteed) - -/// Cable hunting subtree that's guarantee to hunt its target. -/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables/guaranteed - hunt_chance = 100 diff --git a/tgstation.dme b/tgstation.dme index 80726093cab6..17a02bb2ebc7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -288,6 +288,7 @@ #include "code\__DEFINES\zoom.dm" #include "code\__DEFINES\ai\ai.dm" #include "code\__DEFINES\ai\ai_blackboard.dm" +#include "code\__DEFINES\ai\behavior_trees.dm" #include "code\__DEFINES\ai\bot_keys.dm" #include "code\__DEFINES\ai\carp.dm" #include "code\__DEFINES\ai\haunted.dm" @@ -574,7 +575,6 @@ #include "code\_globalvars\time_vars.dm" #include "code\_globalvars\lists\achievements.dm" #include "code\_globalvars\lists\ambience.dm" -#include "code\_globalvars\lists\basic_ai.dm" #include "code\_globalvars\lists\canisters.dm" #include "code\_globalvars\lists\cargo.dm" #include "code\_globalvars\lists\client.dm" @@ -697,7 +697,7 @@ #include "code\controllers\subsystem\achievements.dm" #include "code\controllers\subsystem\admin_verbs.dm" #include "code\controllers\subsystem\ai_controllers.dm" -#include "code\controllers\subsystem\ai_idle_controllers.dm" +#include "code\controllers\subsystem\ai_controllers_low_priority.dm" #include "code\controllers\subsystem\air.dm" #include "code\controllers\subsystem\ambience.dm" #include "code\controllers\subsystem\area_contents.dm" @@ -781,8 +781,6 @@ #include "code\controllers\subsystem\transport.dm" #include "code\controllers\subsystem\tts.dm" #include "code\controllers\subsystem\tutorials.dm" -#include "code\controllers\subsystem\unplanned_ai_idle_controllers.dm" -#include "code\controllers\subsystem\unplanned_controllers.dm" #include "code\controllers\subsystem\verb_manager.dm" #include "code\controllers\subsystem\verbs.dm" #include "code\controllers\subsystem\vis_overlays.dm" @@ -826,8 +824,6 @@ #include "code\controllers\subsystem\persistence\trophy_fishes.dm" #include "code\controllers\subsystem\processing\acid.dm" #include "code\controllers\subsystem\processing\ai_basic_avoidance.dm" -#include "code\controllers\subsystem\processing\ai_behaviors.dm" -#include "code\controllers\subsystem\processing\ai_idle_behaviors.dm" #include "code\controllers\subsystem\processing\antag_hud.dm" #include "code\controllers\subsystem\processing\aura.dm" #include "code\controllers\subsystem\processing\clock_component.dm" @@ -969,111 +965,228 @@ #include "code\datums\actions\mobs\sequences\dash_attack.dm" #include "code\datums\actions\mobs\sequences\projectile.dm" #include "code\datums\ai\_ai_behavior.dm" +#include "code\datums\ai\_ai_bt_composites.dm" +#include "code\datums\ai\_ai_bt_decorators.dm" +#include "code\datums\ai\_ai_bt_node.dm" +#include "code\datums\ai\_ai_bt_subtree.dm" #include "code\datums\ai\_ai_controller.dm" -#include "code\datums\ai\_ai_planning_subtree.dm" #include "code\datums\ai\_item_behaviors.dm" +#include "code\datums\ai\bt_viewer.dm" #include "code\datums\ai\telegraph_effects.dm" #include "code\datums\ai\babies\babies_behaviors.dm" -#include "code\datums\ai\babies\babies_subtrees.dm" -#include "code\datums\ai\bane\bane_behaviors.dm" #include "code\datums\ai\bane\bane_controller.dm" -#include "code\datums\ai\bane\bane_subtrees.dm" #include "code\datums\ai\basic_mobs\admin_ai_templates.dm" #include "code\datums\ai\basic_mobs\base_basic_controller.dm" #include "code\datums\ai\basic_mobs\generic_controllers.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\befriend_target.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\clear_key.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\climb_tree.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\emote_with_target.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\call_reinforcements.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_flee_location.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_parent.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\interact_with_target.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targeting.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\pick_up_item.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\pull_target.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\run_away_from_target.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\set_travel_destination.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\step_towards_turf.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\play_dead.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\stop_and_stare.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeted_mob_ability.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\unbuckle_mob.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\use_mob_ability.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\ventcrawling.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\wounded_targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\write_on_paper.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\attack_adjacent_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\call_reinforcements.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\capricious_retaliate.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\climb_tree.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\drag_items.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\enrage.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\escape_captivity.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\express_happiness.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_paper_and_write.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\find_parent.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\find_targets_prioritize_traits.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\fishing.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\generic_hunger.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\generic_play_instrument.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\go_for_swim.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\maintain_distance.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\mine_walls.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\move_to_cardinal.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\opportunistic_ventcrawler.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\play_with_owners.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\prepare_travel_to_destination.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\ranged_skirmish.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\run_emote.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\shapechange_ambush.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_wounded_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\sleep_with_no_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\stare_at_thing.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\targeted_mob_ability.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\teleport_away_from_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\travel_to_point.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\use_mob_ability.dm" -#include "code\datums\ai\basic_mobs\pet_commands\fetch.dm" -#include "code\datums\ai\basic_mobs\pet_commands\pet_command_planning.dm" -#include "code\datums\ai\basic_mobs\pet_commands\pet_follow_friend.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\random_speech_loop.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\run_away_from_target.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\skittish_brawler_combat.dm" +#include "code\datums\ai\basic_mobs\pet_commands\pet_command_bt.dm" #include "code\datums\ai\basic_mobs\pet_commands\pet_use_targeted_ability.dm" -#include "code\datums\ai\basic_mobs\pet_commands\play_dead.dm" #include "code\datums\ai\basic_mobs\target_priority_strategies\_target_priority_strategy.dm" #include "code\datums\ai\basic_mobs\target_priority_strategies\mining_strategies.dm" +#include "code\datums\ai\basic_mobs\target_sources\_target_source.dm" +#include "code\datums\ai\basic_mobs\target_sources\held_items_then_oview.dm" +#include "code\datums\ai\basic_mobs\target_sources\held_items_typed.dm" +#include "code\datums\ai\basic_mobs\target_sources\mobs_in_oview.dm" +#include "code\datums\ai\basic_mobs\target_sources\near_village_humans.dm" +#include "code\datums\ai\basic_mobs\target_sources\oview_single_type.dm" +#include "code\datums\ai\basic_mobs\target_sources\oview_typed_from_bb_key.dm" +#include "code\datums\ai\basic_mobs\target_sources\range_turfs_typecache_visible.dm" +#include "code\datums\ai\basic_mobs\target_sources\slime_source.dm" +#include "code\datums\ai\basic_mobs\target_sources\turfs_in_oview.dm" #include "code\datums\ai\basic_mobs\targeting_strategies\_targeting_strategy.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\accessible_cable.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\ally_mob.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\baby_raptor.dm" #include "code\datums\ai\basic_mobs\targeting_strategies\basic_targeting_strategy.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\beamable_hydro.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\befriendable_cultist.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\cat_food.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\chargeable_apc.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\conscious_human.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\conscious_mob.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\conscious_snail.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\damaged_eyes.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\damaged_machine.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\dead_mob.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\decorated_donut.dm" #include "code\datums\ai\basic_mobs\targeting_strategies\dont_target_friends.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\drillable_ice.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\empty_paper.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\finished_stove.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\food_or_drink.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\goliath_diggable_turf.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\goose_edible.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\huntable.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\huntable_mouse.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\injured_mob.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\injured_raptor.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\legged_conscious_human.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\living_not_dead.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\non_stump_tree.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\pickup_item.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\playable_deer.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\playable_synthesizer.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\pollinatable_hydro.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\raptor_trough.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\slime_food.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\sniffable_hydro.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\stealable_item.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\stocked_beehive.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\treatable_hydro.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\trough_with_ore.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\unbroken_light.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\uncarried_egg.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\unlit_bonfire.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\valid_cat_home.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\valid_kitten.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\walkable_turf.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\water_dispenser.dm" #include "code\datums\ai\basic_mobs\targeting_strategies\with_object.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\working_machine.dm" +#include "code\datums\ai\bots\bot_decorators.dm" +#include "code\datums\ai\bots\bot_subtrees.dm" #include "code\datums\ai\cursed\cursed_behaviors.dm" #include "code\datums\ai\cursed\cursed_controller.dm" -#include "code\datums\ai\cursed\cursed_subtrees.dm" -#include "code\datums\ai\dog\dog_behaviors.dm" +#include "code\datums\ai\dog\dog_bt.dm" #include "code\datums\ai\dog\dog_controller.dm" -#include "code\datums\ai\dog\dog_subtrees.dm" -#include "code\datums\ai\generic\find_and_set.dm" -#include "code\datums\ai\generic\generic_behaviors.dm" -#include "code\datums\ai\generic\generic_subtrees.dm" +#include "code\datums\ai\generic_behaviors\acquire_injured_target.dm" +#include "code\datums\ai\generic_behaviors\acquire_target.dm" +#include "code\datums\ai\generic_behaviors\ai_interact.dm" +#include "code\datums\ai\generic_behaviors\attack_obstacles.dm" +#include "code\datums\ai\generic_behaviors\battle_screech.dm" +#include "code\datums\ai\generic_behaviors\break_out_of_object.dm" +#include "code\datums\ai\generic_behaviors\break_spine.dm" +#include "code\datums\ai\generic_behaviors\cancel_current_plan.dm" +#include "code\datums\ai\generic_behaviors\clear_key.dm" +#include "code\datums\ai\generic_behaviors\consume.dm" +#include "code\datums\ai\generic_behaviors\copy_bb_key.dm" +#include "code\datums\ai\generic_behaviors\drag_target.dm" +#include "code\datums\ai\generic_behaviors\drop_all_held_items.dm" +#include "code\datums\ai\generic_behaviors\express_happiness.dm" +#include "code\datums\ai\generic_behaviors\face_target_or_face_initial.dm" +#include "code\datums\ai\generic_behaviors\fail.dm" +#include "code\datums\ai\generic_behaviors\find_furthest_turf_from_target.dm" +#include "code\datums\ai\generic_behaviors\find_nearby.dm" +#include "code\datums\ai\generic_behaviors\find_target_facing_turf.dm" +#include "code\datums\ai\generic_behaviors\find_unwebbed_turf.dm" +#include "code\datums\ai\generic_behaviors\find_valid_teleport_location.dm" +#include "code\datums\ai\generic_behaviors\give.dm" +#include "code\datums\ai\generic_behaviors\grab_target.dm" +#include "code\datums\ai\generic_behaviors\heal_eye_damage.dm" +#include "code\datums\ai\generic_behaviors\hunt_target.dm" +#include "code\datums\ai\generic_behaviors\issue_pet_command.dm" +#include "code\datums\ai\generic_behaviors\keep_playing_instrument.dm" +#include "code\datums\ai\generic_behaviors\maintain_distance.dm" +#include "code\datums\ai\generic_behaviors\mine_walls.dm" +#include "code\datums\ai\generic_behaviors\move_to_target.dm" +#include "code\datums\ai\generic_behaviors\perform_emote.dm" +#include "code\datums\ai\generic_behaviors\pick_random_ability.dm" +#include "code\datums\ai\generic_behaviors\pick_up.dm" +#include "code\datums\ai\generic_behaviors\play_instrument.dm" +#include "code\datums\ai\generic_behaviors\random_walk.dm" +#include "code\datums\ai\generic_behaviors\resist.dm" +#include "code\datums\ai\generic_behaviors\run_emote.dm" +#include "code\datums\ai\generic_behaviors\set_bb_cooldown.dm" +#include "code\datums\ai\generic_behaviors\set_bb_key.dm" +#include "code\datums\ai\generic_behaviors\setup_instrument.dm" +#include "code\datums\ai\generic_behaviors\speech.dm" +#include "code\datums\ai\generic_behaviors\spin_web.dm" +#include "code\datums\ai\generic_behaviors\stop_dragging.dm" +#include "code\datums\ai\generic_behaviors\stuff_in_disposal.dm" +#include "code\datums\ai\generic_behaviors\succeed.dm" +#include "code\datums\ai\generic_behaviors\target_retaliate.dm" +#include "code\datums\ai\generic_behaviors\use_in_hand.dm" +#include "code\datums\ai\generic_behaviors\use_on_object.dm" +#include "code\datums\ai\generic_behaviors\virtual_pick_up_item.dm" +#include "code\datums\ai\generic_behaviors\wait.dm" +#include "code\datums\ai\generic_decorators\ability_available.dm" +#include "code\datums\ai\generic_decorators\bb_key_at_least.dm" +#include "code\datums\ai\generic_decorators\bb_key_equals.dm" +#include "code\datums\ai\generic_decorators\bb_key_list_min_count.dm" +#include "code\datums\ai\generic_decorators\bb_key_set.dm" +#include "code\datums\ai\generic_decorators\bb_key_true.dm" +#include "code\datums\ai\generic_decorators\buckle_target_dangerous.dm" +#include "code\datums\ai\generic_decorators\can_see_target.dm" +#include "code\datums\ai\generic_decorators\check_cooldown.dm" +#include "code\datums\ai\generic_decorators\check_rider_stat.dm" +#include "code\datums\ai\generic_decorators\container_attackable.dm" +#include "code\datums\ai\generic_decorators\is_at_distance.dm" +#include "code\datums\ai\generic_decorators\is_dragging.dm" +#include "code\datums\ai\generic_decorators\is_grabbing_target.dm" +#include "code\datums\ai\generic_decorators\is_holding_target.dm" +#include "code\datums\ai\generic_decorators\is_in_vent.dm" +#include "code\datums\ai\generic_decorators\is_target_stunned.dm" +#include "code\datums\ai\generic_decorators\item_inside_pawn.dm" +#include "code\datums\ai\generic_decorators\key_in_typelist.dm" +#include "code\datums\ai\generic_decorators\keys_different_gender.dm" +#include "code\datums\ai\generic_decorators\mob_stat_at_least.dm" +#include "code\datums\ai\generic_decorators\no_humans_watching.dm" +#include "code\datums\ai\generic_decorators\pawn_buckled_to_obj.dm" +#include "code\datums\ai\generic_decorators\pawn_contained_in_obj.dm" +#include "code\datums\ai\generic_decorators\pawn_farther_than_from_key.dm" +#include "code\datums\ai\generic_decorators\pawn_grabbed_by_enemy.dm" +#include "code\datums\ai\generic_decorators\pawn_has_gravity.dm" +#include "code\datums\ai\generic_decorators\pawn_has_trait_from.dm" +#include "code\datums\ai\generic_decorators\pawn_health_below.dm" +#include "code\datums\ai\generic_decorators\pawn_inside_mob.dm" +#include "code\datums\ai\generic_decorators\pawn_is_restrained.dm" +#include "code\datums\ai\generic_decorators\pawn_loc_is_type.dm" +#include "code\datums\ai\generic_decorators\pawn_nutrition_below.dm" +#include "code\datums\ai\generic_decorators\pawn_same_z_as_key.dm" +#include "code\datums\ai\generic_decorators\pawn_turf_has_trait.dm" +#include "code\datums\ai\generic_decorators\random_chance.dm" +#include "code\datums\ai\generic_decorators\random_chance_from_key.dm" +#include "code\datums\ai\generic_decorators\target_has_reagent.dm" +#include "code\datums\ai\generic_decorators\target_has_trait.dm" +#include "code\datums\ai\generic_decorators\target_health_below_fraction.dm" +#include "code\datums\ai\generic_decorators\target_holding_lit_item.dm" +#include "code\datums\ai\generic_decorators\target_is_holding_item.dm" +#include "code\datums\ai\generic_decorators\target_is_type.dm" +#include "code\datums\ai\generic_decorators\target_legcuffed.dm" +#include "code\datums\ai\generic_decorators\target_on_ground.dm" +#include "code\datums\ai\generic_decorators\true_for_time.dm" +#include "code\datums\ai\generic_subtrees\basic_find_target.dm" +#include "code\datums\ai\generic_subtrees\capricious_pick_target.dm" +#include "code\datums\ai\generic_subtrees\climb_tree.dm" +#include "code\datums\ai\generic_subtrees\find_food.dm" +#include "code\datums\ai\generic_subtrees\find_partner.dm" +#include "code\datums\ai\generic_subtrees\find_stealable_object.dm" +#include "code\datums\ai\generic_subtrees\forage_and_retaliate.dm" +#include "code\datums\ai\generic_subtrees\make_babies.dm" +#include "code\datums\ai\generic_subtrees\move_to_and_eat.dm" +#include "code\datums\ai\generic_subtrees\move_to_and_hunt.dm" +#include "code\datums\ai\generic_subtrees\move_to_reinforce.dm" +#include "code\datums\ai\generic_subtrees\pick_retaliate_target.dm" +#include "code\datums\ai\generic_subtrees\random_walk.dm" +#include "code\datums\ai\generic_subtrees\skittish_and_speak.dm" +#include "code\datums\ai\generic_subtrees\steal_and_flee.dm" +#include "code\datums\ai\hauntium\haunted_bt_nodes.dm" #include "code\datums\ai\hauntium\haunted_controller.dm" -#include "code\datums\ai\hauntium\hauntium_subtrees.dm" -#include "code\datums\ai\hunting_behavior\hunting_behaviors.dm" -#include "code\datums\ai\hunting_behavior\hunting_cockroach.dm" -#include "code\datums\ai\hunting_behavior\hunting_corpses.dm" -#include "code\datums\ai\hunting_behavior\hunting_lights.dm" -#include "code\datums\ai\hunting_behavior\hunting_mouse.dm" -#include "code\datums\ai\idle_behaviors\_idle_behavior.dm" -#include "code\datums\ai\idle_behaviors\idle_dog.dm" -#include "code\datums\ai\idle_behaviors\idle_haunted.dm" -#include "code\datums\ai\idle_behaviors\idle_monkey.dm" -#include "code\datums\ai\idle_behaviors\idle_random_walk.dm" -#include "code\datums\ai\monkey\monkey_behaviors.dm" +#include "code\datums\ai\monkey\monkey_bt_nodes.dm" #include "code\datums\ai\monkey\monkey_controller.dm" #include "code\datums\ai\monkey\monkey_subtrees.dm" #include "code\datums\ai\movement\_ai_movement.dm" @@ -1085,7 +1198,6 @@ #include "code\datums\ai\objects\vending_machines\vending_machine_controller.dm" #include "code\datums\ai\robot_customer\robot_customer_behaviors.dm" #include "code\datums\ai\robot_customer\robot_customer_controller.dm" -#include "code\datums\ai\robot_customer\robot_customer_subtrees.dm" #include "code\datums\ai_laws\ai_laws.dm" #include "code\datums\ai_laws\laws_antagonistic.dm" #include "code\datums\ai_laws\laws_neutral.dm" @@ -5324,6 +5436,7 @@ #include "code\modules\mob\living\basic\farm_animals\bee\_bee.dm" #include "code\modules\mob\living\basic\farm_animals\bee\bee_ai_behavior.dm" #include "code\modules\mob\living\basic\farm_animals\bee\bee_ai_subtree.dm" +#include "code\modules\mob\living\basic\farm_animals\bee\bee_bt.dm" #include "code\modules\mob\living\basic\farm_animals\chicken\chick.dm" #include "code\modules\mob\living\basic\farm_animals\chicken\chicken.dm" #include "code\modules\mob\living\basic\farm_animals\cow\_cow.dm" @@ -5428,10 +5541,10 @@ #include "code\modules\mob\living\basic\lavaland\mook\mook.dm" #include "code\modules\mob\living\basic\lavaland\mook\mook_abilities.dm" #include "code\modules\mob\living\basic\lavaland\mook\mook_ai.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_bt.dm" #include "code\modules\mob\living\basic\lavaland\mook\mook_village.dm" #include "code\modules\mob\living\basic\lavaland\node_drone\node_drone.dm" #include "code\modules\mob\living\basic\lavaland\raptor\_raptor.dm" -#include "code\modules\mob\living\basic\lavaland\raptor\raptor_ai_behavior.dm" #include "code\modules\mob\living\basic\lavaland\raptor\raptor_ai_controller.dm" #include "code\modules\mob\living\basic\lavaland\raptor\raptor_ai_subtrees.dm" #include "code\modules\mob\living\basic\lavaland\raptor\raptor_color.dm" @@ -5456,12 +5569,11 @@ #include "code\modules\mob\living\basic\pets\pet.dm" #include "code\modules\mob\living\basic\pets\pet_designer.dm" #include "code\modules\mob\living\basic\pets\sloth.dm" -#include "code\modules\mob\living\basic\pets\cat\bread_cat_ai.dm" #include "code\modules\mob\living\basic\pets\cat\cat.dm" #include "code\modules\mob\living\basic\pets\cat\cat_ai.dm" +#include "code\modules\mob\living\basic\pets\cat\cat_bt.dm" #include "code\modules\mob\living\basic\pets\cat\feral.dm" #include "code\modules\mob\living\basic\pets\cat\keeki.dm" -#include "code\modules\mob\living\basic\pets\cat\kitten_ai.dm" #include "code\modules\mob\living\basic\pets\cat\runtime.dm" #include "code\modules\mob\living\basic\pets\dog\_dog.dm" #include "code\modules\mob\living\basic\pets\dog\corgi.dm" @@ -5485,6 +5597,7 @@ #include "code\modules\mob\living\basic\pets\penguin\penguin_ai.dm" #include "code\modules\mob\living\basic\pets\pet_cult\pet_cult_abilities.dm" #include "code\modules\mob\living\basic\pets\pet_cult\pet_cult_ai.dm" +#include "code\modules\mob\living\basic\pets\pet_cult\pet_cult_bt.dm" #include "code\modules\mob\living\basic\ruin_defender\blob_of_flesh.dm" #include "code\modules\mob\living\basic\ruin_defender\cybersun_aicore.dm" #include "code\modules\mob\living\basic\ruin_defender\dark_wizard.dm" @@ -5510,7 +5623,7 @@ #include "code\modules\mob\living\basic\slime\ai\behaviours.dm" #include "code\modules\mob\living\basic\slime\ai\controller.dm" #include "code\modules\mob\living\basic\slime\ai\pet_command.dm" -#include "code\modules\mob\living\basic\slime\ai\subtrees.dm" +#include "code\modules\mob\living\basic\slime\ai\slime_bt.dm" #include "code\modules\mob\living\basic\snails\snail.dm" #include "code\modules\mob\living\basic\snails\snail_ability.dm" #include "code\modules\mob\living\basic\snails\snail_ai.dm" @@ -5545,7 +5658,6 @@ #include "code\modules\mob\living\basic\space_fauna\demon\demon_subtypes.dm" #include "code\modules\mob\living\basic\space_fauna\eyeball\_eyeball.dm" #include "code\modules\mob\living\basic\space_fauna\eyeball\eyeball_ability.dm" -#include "code\modules\mob\living\basic\space_fauna\eyeball\eyeball_ai_behavior.dm" #include "code\modules\mob\living\basic\space_fauna\eyeball\eyeball_ai_subtree.dm" #include "code\modules\mob\living\basic\space_fauna\hivebot\_hivebot.dm" #include "code\modules\mob\living\basic\space_fauna\hivebot\hivebot_behavior.dm" @@ -5571,13 +5683,11 @@ #include "code\modules\mob\living\basic\space_fauna\revenant\revenant_objectives.dm" #include "code\modules\mob\living\basic\space_fauna\snake\banded_snake.dm" #include "code\modules\mob\living\basic\space_fauna\snake\snake.dm" -#include "code\modules\mob\living\basic\space_fauna\snake\snake_ai.dm" #include "code\modules\mob\living\basic\space_fauna\space_dragon\dragon_breath.dm" #include "code\modules\mob\living\basic\space_fauna\space_dragon\dragon_gust.dm" #include "code\modules\mob\living\basic\space_fauna\space_dragon\space_dragon.dm" #include "code\modules\mob\living\basic\space_fauna\spider\spider.dm" #include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_ai.dm" -#include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_subtrees.dm" #include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spiders.dm" #include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\hivemind.dm" #include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\lay_eggs.dm" diff --git a/tgui/packages/tgui/interfaces/BehaviorTreeViewer/index.tsx b/tgui/packages/tgui/interfaces/BehaviorTreeViewer/index.tsx new file mode 100644 index 000000000000..98e72a7192e8 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BehaviorTreeViewer/index.tsx @@ -0,0 +1,642 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { + Box, + Button, + InfinitePlane, + Section, + Stack, +} from 'tgui-core/components'; + +import { resolveAsset } from '../../assets'; +import { useBackend } from '../../backend'; +import { Window } from '../../layouts'; +import { + type BehaviorTreeViewerData, + type BlackboardEntry, + BT_ABORT_BOTH, + BT_ABORT_LOWER_PRIORITY, + BT_ABORT_NONE, + BT_ABORT_SELF, + BT_NODE_DECORATOR, + BT_NODE_LEAF, + BT_NODE_PARALLEL, + BT_NODE_SELECTOR, + BT_NODE_SEQUENCE, + BT_NODE_SUBPLAN, + BT_NODE_SUBTREE, + type BtNodeData, +} from './types'; + +const NODE_WIDTH = 160; +const NODE_HEIGHT = 70; +const H_GAP = 12; +const V_GAP = 48; +const FADE_DURATION = 1500; + +type AnimContextType = { + recentNodes: Map; + now: number; +}; +const AnimContext = createContext({ + recentNodes: new Map(), + now: 0, +}); + +function fadeOf( + recentNodes: Map, + execIndex: number, + now: number, +): number { + const ts = recentNodes.get(execIndex); + if (ts === undefined) return 0; + const age = now - ts; + return age >= FADE_DURATION ? 0 : 1 - age / FADE_DURATION; +} + +function lineColor(f: number): string { + if (f < 0.01) return '#555'; + const v = Math.round(85 + 170 * f); + return `rgb(${v}, ${v}, ${v})`; +} + +function nodeTypeBadge(nodeType: number): string { + switch (nodeType) { + case BT_NODE_SEQUENCE: + return 'SEQ'; + case BT_NODE_SELECTOR: + return 'SEL'; + case BT_NODE_PARALLEL: + return 'PAR'; + case BT_NODE_DECORATOR: + return 'DEC'; + case BT_NODE_SUBTREE: + return 'SUB'; + case BT_NODE_SUBPLAN: + return 'PLAN'; + default: + return ''; + } +} + +// Returns the last execution index in the node's subtree. +function lastExecIndex(node: BtNodeData): number { + return node.last_exec_index ?? node.exec_index; +} + +function childNodes( + node: BtNodeData, + nodeMap: Map, +): BtNodeData[] { + return (node.children ?? []).flatMap((idx) => { + const n = nodeMap.get(idx); + return n ? [n] : []; + }); +} + +// The pixel width that the subtree rooted at `node` will occupy. +function treeWidth(node: BtNodeData, nodeMap: Map): number { + const kids = childNodes(node, nodeMap); + if (kids.length === 0) return NODE_WIDTH; + const total = kids.reduce( + (sum, kid, i) => sum + treeWidth(kid, nodeMap) + (i > 0 ? H_GAP : 0), + 0, + ); + return Math.max(NODE_WIDTH, total); +} + +function nodeColor( + node: BtNodeData, + activeIdx: number, + selectedDec: BtNodeData | null, +): string { + if ( + node.node_type === BT_NODE_LEAF && + activeIdx > 0 && + activeIdx === node.exec_index + ) { + return 'green'; + } + if ( + activeIdx > 0 && + activeIdx >= node.exec_index && + activeIdx <= lastExecIndex(node) + ) { + return 'good'; + } + if (selectedDec) { + const abort = selectedDec.observer_abort ?? BT_ABORT_NONE; + const inRange = + node.exec_index >= selectedDec.exec_index && + node.exec_index <= lastExecIndex(selectedDec); + const lowerPriority = node.exec_index > lastExecIndex(selectedDec); + if ( + (abort === BT_ABORT_SELF && inRange) || + (abort === BT_ABORT_LOWER_PRIORITY && lowerPriority) || + (abort === BT_ABORT_BOTH && (inRange || lowerPriority)) + ) { + return 'orange'; + } + if (node.exec_index === selectedDec.exec_index) { + return 'blue'; + } + } + return 'default'; +} + +type BtNodeProps = { + nodeIdx: number; + nodeMap: Map; + activeIdx: number; + selectedDec: BtNodeData | null; + onSelectDec: (idx: number | null) => void; +}; + +function BtNodeTree(props: BtNodeProps) { + const { nodeIdx, nodeMap, activeIdx, selectedDec, onSelectDec } = props; + const node = nodeMap.get(nodeIdx); + if (!node) return null; + const color = nodeColor(node, activeIdx, selectedDec); + const badge = nodeTypeBadge(node.node_type); + const abort = node.observer_abort ?? BT_ABORT_NONE; + const kids = childNodes(node, nodeMap); + const { recentNodes, now } = useContext(AnimContext); + const nodeFade = fadeOf(recentNodes, nodeIdx, now); + const kidFades = kids.map((k) => fadeOf(recentNodes, k.exec_index, now)); + const isSelectedDec = + selectedDec !== null && node.exec_index === selectedDec.exec_index; + const isClickableDec = + node.node_type === BT_NODE_DECORATOR && abort !== BT_ABORT_NONE; + + function handleClick() { + if (!isClickableDec) return; + onSelectDec(isSelectedDec ? null : nodeIdx); + } + + let borderColor: string; + if (color === 'green' || color === 'good') { + borderColor = '#00cc44'; + } else if (color === 'orange') { + borderColor = '#ff8800'; + } else if (color === 'blue') { + borderColor = '#4488ff'; + } else { + borderColor = '#444'; + } + + const bgColor = color === 'green' ? '#005522' : '#1a1a1a'; + + const nodeTreeWidth = treeWidth(node, nodeMap); + const connectorCx = nodeTreeWidth / 2; + let cumChildX = 0; + const childCenters = kids.map((kid) => { + const center = cumChildX + treeWidth(kid, nodeMap) / 2; + cumChildX += treeWidth(kid, nodeMap) + H_GAP; + return center; + }); + + return ( +

+
0.01 + ? `0 0 ${Math.round(4 + nodeFade * 14)}px rgba(255, 255, 255, ${(nodeFade * 0.85).toFixed(2)})` + : undefined, + boxSizing: 'border-box', + }} + > + {/* Top row: badge + priority */} +
+ {badge} + #{node.priority} +
+ + {/* Label */} +
+ {node.label} +
+ + {/* Decorator badges */} + {node.node_type === BT_NODE_DECORATOR && ( +
+ {abort !== BT_ABORT_NONE && ( + + OBS + + )} + {node.invert && ( + + NOT + + )} + {abort === BT_ABORT_SELF && ( + SELF + )} + {abort === BT_ABORT_LOWER_PRIORITY && ( + LO-PRI + )} + {abort === BT_ABORT_BOTH && ( + BOTH + )} +
+ )} +
+ + {/* Children */} + {kids.length > 0 && ( + <> + + {/* Vertical from parent center down to junction */} + + {/* Per-child: horizontal arm from spine to child, then vertical down */} + {kids.map((kid, i) => { + const kx = childCenters[i]; + const fade = kidFades[i] ?? 0; + return ( + + + + + ); + })} + +
+ {kids.map((child) => ( + + ))} +
+ + )} +
+ ); +} + +function BlackboardPanel({ entries }: { entries: BlackboardEntry[] }) { + return ( +
+
+ Blackboard +
+
+ {entries.length === 0 ? ( +
+ No entries +
+ ) : ( + entries.map((entry) => ( +
+
+ {entry.key} +
+
+ {entry.value} +
+
+ )) + )} +
+
+ ); +} + +export function BehaviorTreeViewer() { + const { act, data } = useBackend(); + const { + mob_name, + controller_type, + active_execution_index, + fired_indices, + awaiting_pick, + roots, + nodes, + blackboard, + } = data; + + const nodeMap = useMemo(() => { + const map = new Map(); + for (const node of nodes) { + map.set(node.exec_index, node); + } + return map; + }, [nodes]); + + const [selectedDecIdx, setSelectedDecIdx] = useState(null); + const selectedDec = + selectedDecIdx !== null ? (nodeMap.get(selectedDecIdx) ?? null) : null; + + const recentNodesRef = useRef>(new Map()); + const animRafRef = useRef(null); + const [animNow, setAnimNow] = useState(() => Date.now()); + + const startAnimLoop = useCallback(() => { + if (animRafRef.current !== null) return; + const tick = () => { + const curNow = Date.now(); + let anyFading = false; + for (const ts of recentNodesRef.current.values()) { + if (curNow - ts < FADE_DURATION) { + anyFading = true; + break; + } + } + if (anyFading) { + setAnimNow(curNow); + animRafRef.current = requestAnimationFrame(tick); + } else { + animRafRef.current = null; + } + }; + animRafRef.current = requestAnimationFrame(tick); + }, []); + + useEffect(() => { + return () => { + if (animRafRef.current !== null) { + cancelAnimationFrame(animRafRef.current); + } + }; + }, []); + + useEffect(() => { + if (!fired_indices?.length && active_execution_index <= 0) return; + const curNow = Date.now(); + const toStamp = new Set(fired_indices ?? []); + if (active_execution_index > 0) toStamp.add(active_execution_index); + for (const node of nodes) { + for (const idx of toStamp) { + if ( + node.exec_index <= idx && + idx <= (node.last_exec_index ?? node.exec_index) + ) { + recentNodesRef.current.set(node.exec_index, curNow); + } + } + } + startAnimLoop(); + }, [active_execution_index, fired_indices, nodes, startAnimLoop]); + + const animCtxValue = useMemo( + () => ({ recentNodes: recentNodesRef.current, now: animNow }), + [animNow], + ); + + return ( + + +
+
+ + + {mob_name ? ( + + {mob_name} + + {controller_type} + + + ) : ( + No mob selected + )} + + + + + + + + + {selectedDec && ( + + Observer: {selectedDec.label} — highlighted nodes would + be cancelled. Click node again to deselect. + {(selectedDec.observed_keys?.length ?? 0) > 0 && ( + + Watching: {selectedDec.observed_keys!.join(', ')} + + )} + + )} +
+
+
+ +
+ + + {roots && roots.length > 0 && ( +
+ {roots.map((rootIdx) => ( + + ))} +
+ )} +
+
+ {(!roots || roots.length === 0) && ( + + {mob_name + ? 'No behavior nodes in controller.' + : 'Pick a mob to view its behavior tree.'} + + )} +
+
+
+
+ ); +} diff --git a/tgui/packages/tgui/interfaces/BehaviorTreeViewer/types.ts b/tgui/packages/tgui/interfaces/BehaviorTreeViewer/types.ts new file mode 100644 index 000000000000..8fcbee70fdae --- /dev/null +++ b/tgui/packages/tgui/interfaces/BehaviorTreeViewer/types.ts @@ -0,0 +1,41 @@ +export type BlackboardEntry = { + key: string; // BB_* constant name + value: string; // stringified value +}; + +export type BehaviorTreeViewerData = { + mob_name: string | null; + controller_type: string | null; + active_execution_index: number; + fired_indices: number[]; // all leaf execution indices that fired since last poll + awaiting_pick: boolean; + roots: number[]; // execution indices of root nodes + nodes: BtNodeData[]; // flat list of all nodes in the tree + blackboard: BlackboardEntry[]; +}; + +// All dem nodes +export const BT_NODE_SELECTOR = 0; +export const BT_NODE_SEQUENCE = 1; +export const BT_NODE_PARALLEL = 2; +export const BT_NODE_DECORATOR = 3; +export const BT_NODE_LEAF = 4; +export const BT_NODE_SUBTREE = 5; +export const BT_NODE_SUBPLAN = 6; + +export type BtNodeData = { + exec_index: number; // unique key for this node instance + label: string; // display label + node_type: number; // BT_NODE_* constant + priority: number; // sibling priority (1-based) + last_exec_index?: number; // last execution index in subtree — omitted when same as exec_index + children?: number[]; // child exec_indices — omitted when no children + observer_abort?: 0 | 1 | 2 | 3; // abort scope — only on observing decorators + observed_keys?: string[]; // watched blackboard keys — only when non-empty + invert?: boolean; // condition is inverted — only when true +}; + +export const BT_ABORT_NONE = 0; +export const BT_ABORT_SELF = 1; +export const BT_ABORT_LOWER_PRIORITY = 2; +export const BT_ABORT_BOTH = 3; diff --git a/tools/build/build.ts b/tools/build/build.ts index f7954f98aa1e..19d7957da2f7 100644 --- a/tools/build/build.ts +++ b/tools/build/build.ts @@ -159,6 +159,19 @@ export const DmMapsIncludeTarget = new Juke.Target({ }, }); +export const BehaviorTreeCompilerTarget = new Juke.Target({ + inputs: ['code/**/*.bt.json', 'code/__DEFINES/**/*.dm'], + outputs: () => { + return Juke.glob('code/**/*.bt.json').map((file) => { + const rel = file.replace(/^code\//, '').replace(/\.bt\.json$/, ''); + return `build/behavior_trees/${rel}.bt.compiled.json`; + }); + }, + executes: async () => { + await Juke.exec('python', ['tools/build_bt.py']); + }, +}); + export const DmTarget = new Juke.Target({ parameters: [ DefineParameter, @@ -170,6 +183,7 @@ export const DmTarget = new Juke.Target({ dependsOn: ({ get }) => [ get(DefineParameter).includes('ALL_TEMPLATES') && DmMapsIncludeTarget, !get(SkipIconCutter) && IconCutterTarget, + BehaviorTreeCompilerTarget, ], inputs: [ '_maps/map_files/generic/**', diff --git a/tools/build_bt.py b/tools/build_bt.py new file mode 100644 index 000000000000..805d5d3d60f8 --- /dev/null +++ b/tools/build_bt.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python3 +""" +Behavior tree JSON compiler/parser. Converts the .bt.json formated files +into a compacted and runtime-readable converted json format. + +It does this by parsing all defines and replacing the defines in the json with their values. It also checks some specific macros like SECONDS, MINUTES and HOURS. + +This lets us use defines in the JSON :D + +Usage: + python tools/build_bt.py [--repo-root PATH] [--check] + +Options: + --check Verify that all generated files are up to date without writing anything. + Exits with code 1 if any file differs. Used by continious int (hopefully). +""" + +import ast +import json +import os +import re +import sys +import warnings +from pathlib import Path + +# The time macros, so we can use them in JSON files +TIMING_SUBS = [ + (re.compile(r'\bHOURS\b'), '*36000'), + (re.compile(r'\bMINUTES\b'), '*600'), + (re.compile(r'\bSECONDS\b'), '*10'), +] + +# Matches any DM identifier; used for single-pass define substitution. +_IDENT_RE = re.compile(r'\b([A-Za-z_]\w*)\b') + +# Shorthand for our nodes that never change, the others get subtyped, these in theory dont. +STATIC_NODES: dict[str, str] = { + 'selector': '/datum/bt_node/composite/selector', + 'sequence': '/datum/bt_node/composite/sequence', + 'parallel': '/datum/bt_node/composite/parallel', + 'subplan': '/datum/bt_node/composite/subplan', +} + +# Source JSON structural keys that are consumed/transformed during compilation. please keep these updated if u add new stuff :D +_CONSUMED_KEYS = frozenset({'type', 'children', 'child', 'decorator', 'behavior', 'vars', 'subtype', 'dm_type', 'bindings'}) + + + +def parse_defines(repo_root: Path) -> dict: + """ + Scan all .dm files under code/__DEFINES/ and resolve every #define to a + Python int, float, or str value. + + Uses multi-pass resolution so that defines referencing other defines work + regardless of file or declaration order. Stops when a full pass makes no + new progress. + """ + defines: dict = { + 'TRUE': 1, + 'FALSE': 0, + 'null': None, + } + + pending: list[tuple[str, str]] = [] + seen_names: set[str] = set(defines) + defines_dir = repo_root / 'code' + for fpath in sorted(defines_dir.rglob('*.dm')): + for line in fpath.read_text(encoding='utf-8', errors='ignore').splitlines(): + line = line.strip() + if not line.startswith('#define '): + continue + rest = line[len('#define '):] + parts = rest.split(None, 1) + if len(parts) < 2: + continue + name, raw = parts[0], parts[1].strip() + raw = re.sub(r'\s*//.*$', '', raw).strip() + if not raw or name in seen_names: + continue # value-less define or already seen (first wins) + seen_names.add(name) + pending.append((name, raw)) + + while pending: + resolved_this_pass = 0 + still_pending: list[tuple[str, str]] = [] + for name, raw in pending: + value = _resolve_expr(raw, defines) + if value is not None: + defines[name] = value + resolved_this_pass += 1 + else: + still_pending.append((name, raw)) + pending = still_pending + if resolved_this_pass == 0: + break # no new defines found + + return defines + + +def _resolve_expr(raw: str, defines: dict): + """ + Try to resolve a raw define RHS string to a Python number or str. + Returns None if the expression cannot be evaluated. + """ + def _sub(m): + name = m.group(1) + if name not in defines: + return name + val = defines[name] + if val is None: + return 'None' + if isinstance(val, str): + return repr(val) + if isinstance(val, (int, float)): + return str(val) + return name + + expr = _IDENT_RE.sub(_sub, raw) + + # Apply DM postfix timing operators + for pattern, replacement in TIMING_SUBS: + expr = pattern.sub(replacement, expr) + + expr = expr.strip() + + try: + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SyntaxWarning) + val = ast.literal_eval(expr) + if isinstance(val, (int, float, str)): + return val + return None + except Exception: + pass + + try: + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SyntaxWarning) + val = eval(expr, {'__builtins__': {}}, {}) # noqa: S307 + if isinstance(val, (int, float)): + return val + except Exception: + pass + + return None + + +def _split_list_args(inner: str) -> list[str]: + """Split comma-separated args respecting nested parentheses.""" + args: list[str] = [] + depth = 0 + current: list[str] = [] + for ch in inner: + if ch == ',' and depth == 0: + args.append(''.join(current).strip()) + current = [] + else: + if ch == '(': + depth += 1 + elif ch == ')': + depth -= 1 + current.append(ch) + if current: + args.append(''.join(current).strip()) + return [a for a in args if a] + + +def resolve_value(val, defines: dict): + """Resolve a JSON value: define lookup, then arithmetic/timing expression, then raw. + Strings matching list(...) are parsed into Python lists with each element resolved.""" + if not isinstance(val, str): + return val + if val.startswith('$'): + return val # binding reference placeholder — preserve as-is + stripped = val.strip() + if stripped.startswith('list(') and stripped.endswith(')'): + inner = stripped[5:-1] + return [resolve_value(a, defines) for a in _split_list_args(inner)] + if val in defines: + return defines[val] + resolved = _resolve_expr(val, defines) + return resolved if resolved is not None else val + + +def compile_node(src: dict, defines: dict) -> dict: + """Recursively compile one source JSON node into a DM descriptor dict.""" + # Read structural keys from defines + desc_type = defines.get('BT_DESC_TYPE', 'type') + desc_children = defines.get('BT_DESC_CHILDREN', 'children') + + node_type = src.get('type', '') + out: dict = {} + + #Resolve what typepath we need to use for the node + if node_type in STATIC_NODES: + out[desc_type] = STATIC_NODES[node_type] + elif node_type == 'decorator': + out[desc_type] = src['decorator'] + elif node_type == 'leaf': + out[desc_type] = src['behavior'] + elif node_type == 'subtree': + out[desc_type] = src.get('subtype', '/datum/bt_node/subtree') + else: + raise ValueError(f'Unknown node type: {node_type!r}') + + # Setup children nodes recursively + if 'children' in src: + out[desc_children] = [compile_node(c, defines) for c in src['children']] + elif 'child' in src: + out[desc_children] = [compile_node(src['child'], defines)] + + # Instance vars — "" means omit the key (cuz then we use the default) + for key, val in src.get('vars', {}).items(): + rv = resolve_value(val, defines) + if rv != '': + out[key] = rv + + # Bindings: declaration on a subtree definition file's root vs call-site overrides + if 'bindings' in src: + if node_type == 'subtree': + # Call-site overrides: resolve values through defines, emit as "bindings" (becomes node.vars) + # Empty string means "use the default from the declaration" — omit the key. + out['bindings'] = {k: rv for k, v in src['bindings'].items() if (rv := resolve_value(v, defines)) != ''} + else: + # Declaration: emit as "__bindings" with label + resolved default (consumed by DM runtime) + out['__bindings'] = { + name: {'label': info.get('label', name), 'default': resolve_value(info.get('default'), defines)} + for name, info in src['bindings'].items() + } + + # anything else + for key, val in src.items(): + if key in _CONSUMED_KEYS: + continue + out[key] = resolve_value(val, defines) + + return out + + +def main() -> int: + check_mode = '--check' in sys.argv + + # Determine repo root (two levels up from this script, IDK if theres a better way to do this) + repo_root = Path(__file__).resolve().parent.parent + for arg in sys.argv[1:]: + if arg.startswith('--repo-root='): + repo_root = Path(arg.split('=', 1)[1]).resolve() + elif arg == '--repo-root' and sys.argv.index(arg) + 1 < len(sys.argv): + idx = sys.argv.index(arg) + repo_root = Path(sys.argv[idx + 1]).resolve() + + generated_dir = repo_root / 'build' / 'behavior_trees' + code_dir = repo_root / 'code' + generated_dir.mkdir(parents=True, exist_ok=True) + + print('Parsing DM defines...') + defines = parse_defines(repo_root) + print(f' Resolved {len(defines)} defines.') + + bt_files = sorted(repo_root.glob('code/**/*.bt.json')) + print(f'Found {len(bt_files)} .bt.json source files.') + + errors = 0 + dirty = 0 + # Maps each target compiled path back to the source that produced it, so we can + # detect two sources colliding onto one output. shouldn't happen, can happen. + produced: dict[Path, Path] = {} + generated_paths: set[Path] = set() + + for src_path in bt_files: + # The compiled file mirrors the source path relative to code/, so trees that share a basename dont fucking break. + rel = src_path.relative_to(code_dir).as_posix() # "datums/ai/dog/dog.bt.json" + tree_name = rel[:-len('.json')] # "datums/ai/dog/dog.bt" + compiled_path = generated_dir / f'{tree_name}.compiled.json' + + prior = produced.get(compiled_path) + if prior is not None: + print( + f'ERROR: {src_path.relative_to(repo_root)} and {prior.relative_to(repo_root)} ' + f'both compile to {compiled_path.relative_to(repo_root)}', + file=sys.stderr, + ) + errors += 1 + continue + produced[compiled_path] = src_path + generated_paths.add(compiled_path) + + # compile json + try: + src_json = json.loads(src_path.read_text(encoding='utf-8')) + except Exception as exc: + print(f'ERROR reading {src_path.relative_to(repo_root)}: {exc}', file=sys.stderr) + errors += 1 + continue + + try: + compiled = compile_node(src_json, defines) + except Exception as exc: + print(f'ERROR compiling {src_path.relative_to(repo_root)}: {exc}', file=sys.stderr) + errors += 1 + continue + + compiled_text = json.dumps(compiled, separators=(',', ':')) + '\n' + + # either write or check depending on flag + if check_mode: + existing = compiled_path.read_text(encoding='utf-8') if compiled_path.exists() else '' + if existing != compiled_text: + print(f'OUT OF DATE: {compiled_path.relative_to(repo_root)}', file=sys.stderr) + dirty += 1 + else: + compiled_path.parent.mkdir(parents=True, exist_ok=True) + compiled_path.write_text(compiled_text, encoding='utf-8') + + # Remove stale compiled files that no longer correspond to a source tree — + stale = [p for p in generated_dir.rglob('*.compiled.json') if p not in generated_paths] + for path in stale: + if check_mode: + print(f'STALE: {path.relative_to(repo_root)}', file=sys.stderr) + dirty += 1 + else: + path.unlink() + + if not check_mode: + # Prune now-empty directories left behind under the generated tree. + for path in sorted(generated_dir.rglob('*'), reverse=True): + if path.is_dir() and not any(path.iterdir()): + path.rmdir() + + if check_mode: + if dirty: + print( + f'\n{dirty} file(s) are out of date. Run `python tools/build_bt.py` to regenerate.', + file=sys.stderr, + ) + return 1 + print('All generated BT files are up to date.') + return 0 + + if errors: + print(f'\n{errors} error(s) encountered.', file=sys.stderr) + return 1 + + print('Done.') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index 0240a6deb267..055a2d67b8b0 100644 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -7,6 +7,7 @@ MAP_CONFIG=${2:-""} echo Testing $MAP tools/deploy.sh ci_test + mkdir -p ci_test/config mkdir -p ci_test/data diff --git a/tools/deploy.sh b/tools/deploy.sh index 9aae08778ce7..0cb352d4f4b5 100755 --- a/tools/deploy.sh +++ b/tools/deploy.sh @@ -11,6 +11,7 @@ fi mkdir -p \ $1/_maps \ + $1/build/behavior_trees \ $1/code/datums/greyscale/json_configs \ $1/data/spritesheets \ $1/icons \ @@ -26,6 +27,7 @@ fi cp tgstation.dmb tgstation.rsc $1/ cp -r _maps/* $1/_maps/ +cp -r build/behavior_trees/* $1/build/behavior_trees/ cp -r code/datums/greyscale/json_configs/* $1/code/datums/greyscale/json_configs/ cp -r icons/* $1/icons/ cp -r sound/runtime/* $1/sound/runtime/ From f6c29476e25d267ff54911fdf0dfaa51d5f4e47d Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:50:07 +0000 Subject: [PATCH 011/445] Automatic changelog for PR #96628 [ci skip] --- html/changelogs/AutoChangeLog-pr-96628.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96628.yml diff --git a/html/changelogs/AutoChangeLog-pr-96628.yml b/html/changelogs/AutoChangeLog-pr-96628.yml new file mode 100644 index 000000000000..cc35b6250314 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96628.yml @@ -0,0 +1,4 @@ +author: "CabinetOnFire, Iamgoofball, SmartKar, Ben10omintrix" +delete-after: True +changes: + - refactor: "Replaces our AI system with behavior trees, porting all datum/ai to it" \ No newline at end of file From d502723d7983dfe92ab769f089c903d9beae61ac Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:14:02 -0500 Subject: [PATCH 012/445] Adds anomalous fishing hook / material rods no longer innately create material fish (#96507) ## About The Pull Request 1. Adds the "anomalous lure hook" A 40 point techweb is added after advanced fishing and anomaly research, allowing you to print an anomalous lure hook Anomalous lure hooks can accepts (almost) any type of active anomaly core, granting a unique effect depending on the core - Dimensional core adds a random chance for caught fish to have a random material. The material of the rod used is add to the pool of the random materials. - Bioscrambler core adds a random chance for caught fish to gain a random fish trait, and to have its weight randomly modified up or down. - Pyro core adds a random chance for caught fish to be automatically (deep) fried. - Ectoplasm core adds a random chacne to fish up a haunted fish which floats around. - Hallucination cores adds a random chance for caught fish to be visually smaller or larger (actual size unaffected). - Grav core significantly reduces the gravity of the bobber. - Vortex core signficantly reduces the bounciness of the bobber. Any unmentioned core (currently) has no effect Some of these effects are (vaguely) placeholders, which can surely be improved upon 2. Material rods no longer innately create material fish Material rods still have a variety of stats and effects, but no longer have an innate ability to catch material fish, as the effect is tied to the dimensional anomaly core. (Rod materials still may apply when using a dimensional anomaly core though.) ## Why It's Good For The Game Material rods creating material fish is fun from a sandbox perspective but does not really pass the sniff test. Intuiting that the material you make your rod out of affects the stats of the rod is completely sensible... but intuiting that it affects the atomic composition of the fish you catch is far less obvious (unless you're using one of the zanier materials like alien alloys). So I looked towards the designated "zany effects thing-a-mabobs" for a solution, anomaly cores. Moving the effects of material rods to an anomaly core feels pretty sensible to me: Cores are already known to cause weird side effects. It also opened up the opportunity to add effects for other cores, which adds a lot of potential for unique interactions that can easily pass the sniff test. ## Changelog :cl: Melbert add: Adds anomalous lure hook, a fishing hook which accepts (almost) any type of anomaly core, causing unique effects depending on the core used. del: Material fishing rods no longer innately catch material fish, you need a dimensional anomaly core in an anomalous lure hook to catch material fish now. /:cl: --------- Co-authored-by: san7890 --- code/__DEFINES/research/techweb_nodes.dm | 1 + code/modules/fishing/fish/fish_traits.dm | 22 ++ code/modules/fishing/fishing_equipment.dm | 219 +++++++++++++++++- code/modules/fishing/fishing_rod.dm | 29 +-- code/modules/research/anomaly/anomaly_core.dm | 12 + code/modules/research/designs/misc_designs.dm | 12 + .../research/techweb/nodes/service_nodes.dm | 10 + icons/obj/fishing.dmi | Bin 38902 -> 38820 bytes strings/fishing_tips.txt | 3 +- 9 files changed, 276 insertions(+), 32 deletions(-) diff --git a/code/__DEFINES/research/techweb_nodes.dm b/code/__DEFINES/research/techweb_nodes.dm index baf220fda688..207603d78d39 100644 --- a/code/__DEFINES/research/techweb_nodes.dm +++ b/code/__DEFINES/research/techweb_nodes.dm @@ -44,6 +44,7 @@ #define TECHWEB_NODE_EXP_TOOLS "exp_tools" #define TECHWEB_NODE_EXPLOSIVES "explosives" #define TECHWEB_NODE_EXTREME_OFFICE "extreme_office" +#define TECHWEB_NODE_FISHING_ANOMALOUS "fishing_anomalous" #define TECHWEB_NODE_FISHING_EQUIP "fishing_equip" #define TECHWEB_NODE_FISHING_EQUIP_ADV "fishing_equip_adv" #define TECHWEB_NODE_FOOD_PROC "food_proc" diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm index ae97235cfe56..3df656406186 100644 --- a/code/modules/fishing/fish/fish_traits.dm +++ b/code/modules/fishing/fish/fish_traits.dm @@ -41,6 +41,8 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) var/list/traits_to_add /// If set, the fish may return this infusion entry when get_infusion_entry is called instead of /datum/infuser_entry/fish var/infusion_entry + /// Chance of being added via bioscramble + var/bioscramble_weight = 0 /// Difficulty modifier from this mod, needs to return a list with two values /datum/fish_trait/proc/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman) @@ -130,6 +132,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/wary name = "Wary" catalog_description = "This fish will avoid visible fish lines, cloaked line recommended." + bioscramble_weight = 10 /datum/fish_trait/wary/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman) . = ..() @@ -142,6 +145,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/shiny_lover name = "Shiny Lover" catalog_description = "This fish loves shiny things and money, shiny lure recommended." + bioscramble_weight = 10 /datum/fish_trait/shiny_lover/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman) . = ..() @@ -158,6 +162,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/picky_eater name = "Picky Eater" catalog_description = "This fish is very picky and will ignore low quality bait (unless it's amongst its favorites)." + bioscramble_weight = 10 /datum/fish_trait/picky_eater/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) . = ..() @@ -189,6 +194,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/nocturnal name = "Nocturnal" catalog_description = "This fish avoids bright lights, fishing and storing in darkness recommended." + bioscramble_weight = 10 /datum/fish_trait/nocturnal/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) . = ..() @@ -398,6 +404,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /obj/item/fish/fryish/nessie = 0 ) traits_to_add = list(TRAIT_FISH_NO_MATING) + bioscramble_weight = 10 ///Prevent offsprings of fish with this trait from being of the same type (unless self-mating or the partner also has the trait) /datum/fish_trait/recessive @@ -405,11 +412,13 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "If crossbred, offsprings will always be of the mate species, unless it also possess the trait." inheritability = 0 traits_to_add = list(TRAIT_FISH_RECESSIVE) + bioscramble_weight = 10 /datum/fish_trait/revival name = "Self-Revival" catalog_description = "This fish shows a peculiar ability of reviving itself a minute or two after death." spontaneous_manifest_types = list(/obj/item/fish/boned = 100, /obj/item/fish/mastodon = 100) + bioscramble_weight = 10 /datum/fish_trait/revival/apply_to_fish(obj/item/fish/fish, initial = TRUE) . = ..() @@ -443,6 +452,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) name = "Predator" catalog_description = "It's a predatory fish. It'll hunt down and eat live fishes of smaller size when hungry." incompatible_traits = list(/datum/fish_trait/vegan) + bioscramble_weight = 10 /datum/fish_trait/predator/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) . = ..() @@ -474,6 +484,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish tastes so repulsive, other fishes won't try to eat it." reagents_to_add = list(/datum/reagent/yuck = 1.2) traits_to_add = list(TRAIT_YUCKY_FISH) + bioscramble_weight = 10 /datum/fish_trait/toxic name = "Toxic" @@ -533,11 +544,13 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) reagents_to_add = list(/datum/reagent/toxin/carpotoxin = 4) infusion_entry = null venom_mult = 6 + bioscramble_weight = 10 /datum/fish_trait/toxin_immunity name = "Toxin Immunity" catalog_description = "This fish has developed an ample-spected immunity to toxins." traits_to_add = list(TRAIT_FISH_TOXIN_IMMUNE) + bioscramble_weight = 10 /datum/fish_trait/crossbreeder name = "Crossbreeder" @@ -545,10 +558,12 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) inheritability = 40 incompatible_traits = list(/datum/fish_trait/no_mating) traits_to_add = list(TRAIT_FISH_CROSSBREEDER) + bioscramble_weight = 10 /datum/fish_trait/territorial name = "Territorial" catalog_description = "This fish will start attacking other fish if the aquarium has five or more." + bioscramble_weight = 10 /datum/fish_trait/territorial/apply_to_fish(obj/item/fish/fish, initial = TRUE) . = ..() @@ -579,6 +594,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish exudes a viscous, slippery lubrificant. It's recommended not to step on it." added_difficulty = 5 reagents_to_add = list(/datum/reagent/lube = 1.2) + bioscramble_weight = 10 /datum/fish_trait/lubed/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) . = ..() @@ -606,6 +622,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish has developed a primitive adaptation to life on both land and water." infusion_entry = /datum/infuser_entry/amphibious traits_to_add = list(TRAIT_FISH_AMPHIBIOUS) + bioscramble_weight = 10 /datum/fish_trait/amphibious/apply_to_fish(obj/item/fish/fish, initial = TRUE) . = ..() @@ -653,6 +670,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/anxiety name = "Anxiety" catalog_description = "This fish tends to die of stress when forced to be around too many other fish." + bioscramble_weight = 10 /datum/fish_trait/anxiety/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman) . = ..() @@ -771,6 +789,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) name = "Toxic Barbs" catalog_description = "The stinger or bill of this fish is coated in a simple, yet effective venom." spontaneous_manifest_types = list(/obj/item/fish/stingray = 35) + bioscramble_weight = 10 /datum/fish_trait/toxic_barbs/apply_to_fish(obj/item/fish/fish, initial = TRUE) . = ..() @@ -802,6 +821,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) name = "Hallucinogenic" catalog_description = "This fish is coated with hallucinogenic neurotoxin. We advise cooking it before consumption." reagents_to_add = list(/datum/reagent/toxin/mindbreaker/fish = 1) + bioscramble_weight = 10 /datum/fish_trait/hallucinogenic/add_reagents(obj/item/fish/fish, list/reagents) if(!HAS_TRAIT(src, TRAIT_FOOD_FRIED) && !HAS_TRAIT(src, TRAIT_FOOD_BBQ_GRILLED)) @@ -839,6 +859,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish possess a sac that produces ink." spontaneous_manifest_types = list(/obj/item/fish/squid = 35) infusion_entry = /datum/infuser_entry/squid + bioscramble_weight = 10 /datum/fish_trait/ink/apply_to_fish(obj/item/fish/fish, initial = TRUE) . = ..() @@ -875,6 +896,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish possess the ability to blend with its surroundings." spontaneous_manifest_types = list(/obj/item/fish/squid = 35) added_difficulty = 5 + bioscramble_weight = 10 /datum/fish_trait/camouflage/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame) minigame.special_effects |= FISHING_MINIGAME_RULE_CAMO diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm index 8963e4dd6323..fba2cecc9124 100644 --- a/code/modules/fishing/fishing_equipment.dm +++ b/code/modules/fishing/fishing_equipment.dm @@ -164,6 +164,7 @@ icon = 'icons/obj/fishing.dmi' icon_state = "hook" w_class = WEIGHT_CLASS_TINY + custom_materials = list(/datum/material/titanium = SHEET_MATERIAL_AMOUNT, /datum/material/diamond = SHEET_MATERIAL_AMOUNT * 0.2) /// A bitfield of traits that this fishing hook has, checked by fish traits and the minigame var/fishing_hook_traits @@ -253,14 +254,228 @@ /obj/item/fishing_hook/shiny/proc/on_fishing_rod_slotted(datum/source, obj/item/fishing_rod/rod, slot) SIGNAL_HANDLER - rod.material_fish_chance += 15 //Increases the chance of catching a shiny po... erh, material fish ADD_TRAIT(rod, TRAIT_ROD_ATTRACT_SHINY_LOVERS, REF(src)) /obj/item/fishing_hook/shiny/proc/on_fishing_rod_unslotted(datum/source, obj/item/fishing_rod/rod, slot) SIGNAL_HANDLER - rod.material_fish_chance -= 15 REMOVE_TRAIT(rod, TRAIT_ROD_ATTRACT_SHINY_LOVERS, REF(src)) +/obj/item/fishing_hook/anomaly + name = "anomalous lure hook" + icon_state = "anom" + wiki_desc = "You can slot in an active anomaly core to add a variety of effects to the fishing rod. \ + Most (but not all) cores will have a unique effect." + /// The actual anomaly core slotted in + var/obj/item/assembly/signaler/anomaly/core + +/obj/item/fishing_hook/anomaly/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ITEM_FISHING_ROD_SLOTTED, PROC_REF(on_fishing_rod_slotted)) + RegisterSignal(src, COMSIG_ITEM_FISHING_ROD_UNSLOTTED, PROC_REF(on_fishing_rod_unslotted)) + +/obj/item/fishing_hook/anomaly/examine(mob/user) + . = ..() + if(!isnull(core)) + . += span_info("There's \a [core] slotted into it.") + +/obj/item/fishing_hook/anomaly/examine_more(mob/user) + . = ..() + . += "• A [/obj/item/assembly/signaler/anomaly/dimensional::name] adds a chance for your catch to have its materials altered." + . += "• A [/obj/item/assembly/signaler/anomaly/bioscrambler::name] adds a chance for your catch to have its traits or stats altered." + . += "• A [/obj/item/assembly/signaler/anomaly/pyro::name] adds a chance for your catch to be cooked." + . += "• A [/obj/item/assembly/signaler/anomaly/ectoplasm::name] adds a chance to catch haunted fish." + . += "• A [/obj/item/assembly/signaler/anomaly/hallucination::name] adds a chance to cause your catches to randomly grow or shrink in size." + . += "• A [/obj/item/assembly/signaler/anomaly/grav::name] reduces the gravity of the bobber, causing it to fall slower." + . += "• A [/obj/item/assembly/signaler/anomaly/vortex::name] reduces the bobber's overall bounciness." + . += "• Unmentioned cores likely have no unique effect when applied." + +/obj/item/fishing_hook/anomaly/proc/on_fishing_rod_slotted(datum/source, obj/item/fishing_rod/rod, slot) + SIGNAL_HANDLER + + switch(core?.type) + if(/obj/item/assembly/signaler/anomaly/dimensional) + RegisterSignal(rod, COMSIG_FISHING_ROD_CAUGHT_FISH, PROC_REF(dimensional_catch_bonus)) + if(/obj/item/assembly/signaler/anomaly/bioscrambler) + RegisterSignal(rod, COMSIG_FISHING_ROD_CAUGHT_FISH, PROC_REF(bioscrambler_catch_bonus)) + if(/obj/item/assembly/signaler/anomaly/pyro) + RegisterSignal(rod, COMSIG_FISHING_ROD_CAUGHT_FISH, PROC_REF(pyro_catch_effect)) + if(/obj/item/assembly/signaler/anomaly/ectoplasm) + RegisterSignal(rod, COMSIG_FISHING_ROD_CAUGHT_FISH, PROC_REF(ectoplasm_catch_effect)) + if(/obj/item/assembly/signaler/anomaly/hallucination) + RegisterSignal(rod, COMSIG_FISHING_ROD_CAUGHT_FISH, PROC_REF(hallucination_catch_effect)) + if(/obj/item/assembly/signaler/anomaly/grav) + rod.gravity_mult *= 0.4 + if(/obj/item/assembly/signaler/anomaly/vortex) + rod.bounciness_mult *= 0.2 + else + rod.balloon_alert_to_viewers("no core effect!", vision_distance = 2) + +/obj/item/fishing_hook/anomaly/proc/on_fishing_rod_unslotted(datum/source, obj/item/fishing_rod/rod, slot) + SIGNAL_HANDLER + + switch(core?.type) + if(/obj/item/assembly/signaler/anomaly/grav) + rod.gravity_mult /= 0.4 + if(/obj/item/assembly/signaler/anomaly/vortex) + rod.bounciness_mult /= 0.2 + + UnregisterSignal(rod, COMSIG_FISHING_ROD_CAUGHT_FISH) + +/obj/item/fishing_hook/anomaly/proc/get_probability_bonuses(obj/item/fishing_rod/rod, mob/living/user) + var/skill_modifier = user.mind?.get_skill_level(/datum/skill/fishing) * 1.5 + var/bait_modifier = 0 + if(!isnull(rod.bait)) + if(HAS_TRAIT(rod.bait, TRAIT_GREAT_QUALITY_BAIT)) + bait_modifier += 16 + else if(HAS_TRAIT(rod.bait, TRAIT_GOOD_QUALITY_BAIT)) + bait_modifier += 8 + else if(HAS_TRAIT(rod.bait, TRAIT_BASIC_QUALITY_BAIT)) + bait_modifier += 4 + + // up to a ~25% bonus + return skill_modifier + bait_modifier + +/obj/item/fishing_hook/anomaly/proc/dimensional_catch_bonus(obj/item/fishing_rod/rod, obj/item/caught, mob/living/user) + SIGNAL_HANDLER + + if(!isfish(caught) || !HAS_TRAIT(caught, TRAIT_FISH_JUST_SPAWNED)) + return + + if(!prob(25 + get_probability_bonuses(rod, user))) + return + + var/obj/item/fish/caught_fish = caught + // number of materials applies to the fish + var/list/material_amounts = alist( + 1 = 16, + 2 = 3, + 3 = 1, + ) + // generic list of materials we may apply + var/list/material_weights = alist( + /datum/material/gold = 20, + /datum/material/silver = 20, + /datum/material/plastic = 10, + /datum/material/uranium = 5, + /datum/material/plasma = 5, + ) + + // adds chance of inheriting rod materials + if(rod.material_flags & MATERIAL_EFFECTS) + for(var/rod_material_type, rod_material_amount in rod.custom_materials) + material_weights[rod_material_type] = 40 * (rod_material_amount / values_sum(rod.custom_materials)) + + // select a number of materials, then select what materials to apply + var/num_materials = pick_weight(material_amounts) + var/list/material_setup = list() + for(var/i in 1 to num_materials) + material_setup[pick_weight(material_weights)] += round(caught_fish.weight * (1 / num_materials)) + + caught_fish.set_custom_materials(material_setup) + +/obj/item/fishing_hook/anomaly/proc/bioscrambler_catch_bonus(obj/item/fishing_rod/rod, obj/item/caught, mob/living/user) + SIGNAL_HANDLER + + if(!isfish(caught) || !HAS_TRAIT(caught, TRAIT_FISH_JUST_SPAWNED) || !prob(25 + get_probability_bonuses(rod, user))) + return + + var/list/random_pool = list() + for(var/datum/fish_trait/random_trait as anything in GLOB.fish_traits) + if(random_trait.bioscramble_weight > 0) + random_pool[random_trait.bioscramble_weight] = random_trait + + var/obj/item/fish/caught_fish = caught + caught_fish.update_size_and_weight(new_weight = caught_fish.weight * pick(0.6, 0.8, 1, 1.2, 1.4)) + + var/datum/fish_trait/random_trait = pick_weight(random_pool) + random_trait.apply_to_fish(caught_fish) + +/obj/item/fishing_hook/anomaly/proc/pyro_catch_effect(obj/item/fishing_rod/rod, obj/item/caught, mob/living/user) + SIGNAL_HANDLER + + if(!isfish(caught) || !HAS_TRAIT(caught, TRAIT_FISH_JUST_SPAWNED) || !prob(85 + get_probability_bonuses(rod, user))) + return + + var/alist/fry_times = alist( + FRYING_TIME_FRIED = 2, + FRYING_TIME_PERFECT = 5, + FRYING_TIME_BURNT = 3, + ) + + caught.AddElement(/datum/element/fried_item, pick_weight(fry_times)) + +/obj/item/fishing_hook/anomaly/proc/ectoplasm_catch_effect(obj/item/fishing_rod/rod, obj/item/caught, mob/living/user) + SIGNAL_HANDLER + + if(!isfish(caught) || !HAS_TRAIT(caught, TRAIT_FISH_JUST_SPAWNED) || !prob(50 + get_probability_bonuses(rod, user))) + return + + caught.AddComponent(/datum/component/haunted_item, \ + haunt_color = "#52336e", \ + haunt_duration = 6 MINUTES, \ + spawn_message = span_revenwarning("[caught] slowly rises upward, flopping menacingly in the air..."), \ + despawn_message = span_revenwarning("[caught] settles to the floor, looking like a normal fish again..."), \ + ) + +/obj/item/fishing_hook/anomaly/proc/hallucination_catch_effect(obj/item/fishing_rod/rod, obj/item/caught, mob/living/user) + SIGNAL_HANDLER + + if(!isfish(caught) || !HAS_TRAIT(caught, TRAIT_FISH_JUST_SPAWNED) || !prob(50 + get_probability_bonuses(rod, user))) + return + + caught.transform = caught.transform.Scale(pick(0.5, 0.75, 1, 1.25, 1.5)) + +/obj/item/fishing_hook/anomaly/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/assembly/signaler/anomaly)) + return NONE + if(!isnull(core)) + balloon_alert(user, "already has a core!") + return ITEM_INTERACT_BLOCKING + if(user.is_holding(tool)) + if(!user.transferItemToLoc(tool, src)) + return ITEM_INTERACT_BLOCKING + else + tool.forceMove(src) + + core = tool + balloon_alert(user, "core installed") + playsound(src, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) + update_appearance(UPDATE_OVERLAYS) + return ITEM_INTERACT_SUCCESS + +/obj/item/fishing_hook/anomaly/attack_self(mob/user, modifiers) + . = ..() + if(.) + return . + if(isnull(core)) + return . + + if(user.is_holding(src)) + user.put_in_hands(core) + else + core.forceMove(drop_location()) + + core = null + balloon_alert(user, "core removed") + playsound(src, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE) + update_appearance(UPDATE_OVERLAYS) + return TRUE + +/obj/item/fishing_hook/anomaly/update_overlays() + . = ..() + if(isnull(core)) + return + + var/mutable_appearance/core_base = mutable_appearance(icon, "anom_overlay_base", alpha = src.alpha) + . += core_base + + var/mutable_appearance/core_color = mutable_appearance(icon, "anom_overlay_light", alpha = src.alpha) + core_color.color = core.core_color + . += core_color + + var/mutable_appearance/core_emissive = emissive_appearance(icon, "anom_overlay_light", src, alpha = src.alpha) + . += core_emissive + /obj/item/fishing_hook/weighted name = "weighted hook" icon_state = "weighted" diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index 34c0db258ea8..67d5845bb45c 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -58,8 +58,6 @@ ///Prevents spamming the line casting, without affecting the player's click cooldown. COOLDOWN_DECLARE(casting_cd) - ///The chance of catching fish made of the same material of the fishing rod (if MATERIAL_EFFECTS is enabled) - var/material_fish_chance = 10 ///The multiplier of how much experience is gained when fishing with this rod. var/experience_multiplier = 1 ///The multiplier of the completion gain during the minigame @@ -162,12 +160,7 @@ list_clear_nulls(block) . += boxed_message(block.Join("\n")) - if(get_percent && (material_flags & MATERIAL_EFFECTS) && length(custom_materials)) - . += boxed_message(span_info("Right now, fish caught by this fishing rod have a [get_material_fish_chance(user)]% of being made of its same materials.")) - block = list() - if(HAS_TRAIT(src, TRAIT_ROD_ATTRACT_SHINY_LOVERS)) - block += span_info("This fishing rod will attract shiny-loving fish.") if(HAS_TRAIT(src, TRAIT_ROD_IGNORE_ENVIRONMENT)) block += span_info("Environment and light shouldn't be an issue with this rod.") if(HAS_TRAIT_NOT_FROM(src, TRAIT_ROD_REMOVE_FISHING_DUD, INNATE_TRAIT)) // Duds are innately removed by baits, we all know that. @@ -220,12 +213,6 @@ /obj/item/fishing_rod/proc/on_reward_caught(atom/movable/reward, mob/user) if(isnull(reward)) return - var/isfish = isfish(reward) - if((material_flags & MATERIAL_EFFECTS) && isfish && length(custom_materials) && HAS_TRAIT(reward, TRAIT_FISH_JUST_SPAWNED)) - if(prob(get_material_fish_chance(user))) - var/obj/item/fish/fish = reward - var/datum/material/material = get_master_material() - fish.set_custom_materials(list(material.type = fish.weight)) // catching things that aren't fish or alive mobs doesn't consume baits. if(isnull(bait) || HAS_TRAIT(bait, TRAIT_BAIT_UNCONSUMABLE)) return @@ -234,7 +221,7 @@ if(caught_mob.stat == DEAD) return else - if(!isfish) + if(!isfish(reward)) return var/obj/item/fish/fish = reward if(HAS_TRAIT(bait, TRAIT_POISONOUS_BAIT) && !HAS_TRAIT(fish, TRAIT_FISH_TOXIN_IMMUNE)) @@ -249,19 +236,6 @@ qdel(bait) update_icon() -///Returns the probability that a fish caught by this (custom material) rod will be of the same material. -/obj/item/fishing_rod/proc/get_material_fish_chance(mob/user) - var/material_chance = material_fish_chance - if(bait) - if(HAS_TRAIT(bait, TRAIT_GREAT_QUALITY_BAIT)) - material_chance += 16 - else if(HAS_TRAIT(bait, TRAIT_GOOD_QUALITY_BAIT)) - material_chance += 8 - else if(HAS_TRAIT(bait, TRAIT_BASIC_QUALITY_BAIT)) - material_chance += 4 - material_chance += user.mind?.get_skill_level(/datum/skill/fishing) * 1.5 - return material_chance - /obj/item/fishing_rod/proc/should_bane_fish_infusions(mob/living/target) return force > 0 && HAS_TRAIT(target, TRAIT_WATER_ADAPTATION) @@ -789,7 +763,6 @@ deceleration_mult = 1.2 bounciness_mult = 0.3 gravity_mult = 1.2 - material_fish_chance = 33 //if somehow you metalgen it. bait_height_mult = 1.4 /obj/item/fishing_rod/tech diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm index 54339a7aacff..7412b8280344 100644 --- a/code/modules/research/anomaly/anomaly_core.dm +++ b/code/modules/research/anomaly/anomaly_core.dm @@ -10,6 +10,8 @@ custom_materials = null assembly_behavior = ASSEMBLY_FUNCTIONAL_OUTPUT var/obj/effect/anomaly/anomaly_type = /obj/effect/anomaly + /// Color of the core's glow + var/core_color = COLOR_WHITE /obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal) if(!signal) @@ -44,6 +46,7 @@ desc = "The neutralized core of a pyroclastic anomaly. It feels warm to the touch. It'd probably be valuable for research." icon_state = "pyro_core" anomaly_type = /obj/effect/anomaly/pyro + core_color = COLOR_BRIGHT_ORANGE /obj/item/assembly/signaler/anomaly/pyro/signal() var/turf/our_turf = get_turf(src) @@ -58,6 +61,7 @@ desc = "The neutralized core of a gravitational anomaly. It feels much heavier than it looks. It'd probably be valuable for research." icon_state = "grav_core" anomaly_type = /obj/effect/anomaly/grav + core_color = COLOR_LIME /obj/item/assembly/signaler/anomaly/grav/signal() for(var/obj/object in orange(2, get_turf(src))) @@ -72,6 +76,7 @@ desc = "The neutralized core of a flux anomaly. Touching it makes your skin tingle. It'd probably be valuable for research." icon_state = "flux_core" anomaly_type = /obj/effect/anomaly/flux + core_color = COLOR_YELLOW /obj/item/assembly/signaler/anomaly/flux/signal() tesla_zap(get_turf(src), 0, 10 KILO JOULES, 5 KILO JOULES, ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_GENERATES_POWER) @@ -82,6 +87,7 @@ icon_state = "anomaly_core" anomaly_type = /obj/effect/anomaly/bluespace activation_cooldown = 15 SECONDS // Slightly longer than reactive teleport armor cooldown + core_color = COLOR_DARK_CYAN /obj/item/assembly/signaler/anomaly/bluespace/signal() var/atom/movable/to_teleport = get_teleportable_container(src, container_flags = TELEPORT_CONTAINER_INCLUDE_SEALED_MODSUIT) @@ -97,6 +103,7 @@ icon_state = "vortex_core" anomaly_type = /obj/effect/anomaly/bhole activation_cooldown = 5 SECONDS + core_color = COLOR_PURPLE var/turf_contents_pull_chance = 33 var/turf_destroy_chance = 25 var/max_explosion_force = EXPLODE_HEAVY @@ -139,6 +146,7 @@ icon_state = "bioscrambler_core" anomaly_type = /obj/effect/anomaly/bioscrambler activation_cooldown = 10 SECONDS + core_color = COLOR_OLIVE_GREEN /obj/item/assembly/signaler/anomaly/bioscrambler/signal() new /obj/effect/temp_visual/circle_wave/bioscrambler(get_turf(src)) @@ -151,6 +159,7 @@ icon_state = "hallucination_core" anomaly_type = /obj/effect/anomaly/hallucination activation_cooldown = 10 SECONDS + core_color = COLOR_SOFT_RED /obj/item/assembly/signaler/anomaly/hallucination/signal() visible_hallucination_pulse(get_turf(src), 2, 20 SECONDS, 1 MINUTES) @@ -161,6 +170,7 @@ icon_state = "dimensional_core" anomaly_type = /obj/effect/anomaly/dimensional activation_cooldown = 15 SECONDS // A bit longer than reactive barricade armor cooldown + core_color = COLOR_WHITE /obj/item/assembly/signaler/anomaly/dimensional/signal() var/turf/our_turf = get_turf(src) @@ -182,6 +192,7 @@ icon_state = "dimensional_core" anomaly_type = /obj/effect/anomaly/ectoplasm activation_cooldown = 60 SECONDS // A bit longer than reactive posession armor cooldown + core_color = COLOR_WHITE /obj/item/assembly/signaler/anomaly/ectoplasm/signal() haunt_outburst(get_turf(src), 2, 33, 30 SECONDS) @@ -191,6 +202,7 @@ desc = "The neutralized core of a weather anomaly. The sound of thunder can be heard in the distance. It'd probably be valuable for research." icon_state = "weather_core" anomaly_type = /obj/effect/anomaly/weather + core_color = COLOR_BLUE /// Used in weather towers to track uses before depleting var/charges = 8 diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm index c4efb2d2cce6..01a34d09b775 100644 --- a/code/modules/research/designs/misc_designs.dm +++ b/code/modules/research/designs/misc_designs.dm @@ -1216,3 +1216,15 @@ RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SCIENCE ) departmental_flags = DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/anomalous_fishing_hook + name = "Anomalous Fishing Hook" + desc = "A fishing hook with a slot for an anomaly core. Depending on the core inserted, it can have various effects on the rod or the fish caught." + id = "anomalous_fishing_hook" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/titanium = SHEET_MATERIAL_AMOUNT, /datum/material/diamond = SHEET_MATERIAL_AMOUNT * 0.2) + build_path = /obj/item/fishing_hook/anomaly + category = list( + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SERVICE + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE diff --git a/code/modules/research/techweb/nodes/service_nodes.dm b/code/modules/research/techweb/nodes/service_nodes.dm index c1e5abb7bd11..261bb6af9db4 100644 --- a/code/modules/research/techweb/nodes/service_nodes.dm +++ b/code/modules/research/techweb/nodes/service_nodes.dm @@ -215,3 +215,13 @@ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS) // only available if you've done the first fishing experiment (thus unlocking fishing tech), but not a strict requirement to get the tech discount_experiments = list(/datum/experiment/scanning/fish/second = TECHWEB_TIER_3_POINTS) + +/datum/techweb_node/fishing_anomalous + id = TECHWEB_NODE_FISHING_ANOMALOUS + display_name = "Anomalous Fishing" + description = "For the truly adventurous fisher, these experimental tools could augment your catches in unpredictable ways." + prereq_ids = list(TECHWEB_NODE_FISHING_EQUIP_ADV, TECHWEB_NODE_ANOMALY_RESEARCH) + design_ids = list( + "anomalous_fishing_hook", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) diff --git a/icons/obj/fishing.dmi b/icons/obj/fishing.dmi index a4a40d5a3b65edd5203ca24d71f6f5c5d17e616c..b97f3cc91d78ade5fcf8372e87a7dafca599795b 100644 GIT binary patch literal 38820 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 z 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 L>f+n;PWS!}oWtCs literal 38902 zcmZ^~1yCGo)FzCC5CViCK?5Xsg1fr}mk?YAcNl^@1b26r;4(PD-Q6964esvS-22_F zzy95=8mQ@>p6+AsInQ~{J3;cY-%#J;zlDK;L6r~}`3?gE`xp3Yd-EDNoQtors~WzKyB1ohjG~2F58hO+_Xqg#m>(0Mu>)NtcF`Ng=Y?waBjuSZ@7`o~dIQ zDr1P5{CLSrR&9r=R|QXWc6wp4+mG=S>+yVgzDw@z<{=s07hWRaxVydeRqf)3bKKDc zj}-RXLglB2Zs(K4qlo~xzCny3;mU$Qtp=&te!bDLzqjWfCVzq~o7Wfzn; zY*hb{Y0vW=yP@-wrkd*;?^F^@hc0ICeGVRDOMCJS4%gr6&L!RgVk%+{)X3%}*v@(c zubPF9y)$_^Mf2hcwd@iiR;}5nRK;lA&P9y^3GdNHUzGJvm}33-JSJ$^8>%sHGuu zS&RiFHWi(N|5~iJuL{?oJi3AStx_;7%mo2NefOVXl`lgnRN4Yodp=7YRyzkg`OT4Y^Aap!rZ zr3RC3rgf6tYCG2im9r>)g8S@_nN7Upwco{T|$2H3KLtwxjHvVfcPJ}cEh%E>FiO2 zzg>O$S(2cEj;peXAXI3@y#1VL8x_K%#&Bg&loB5QR`Z5|xeL-&ST$R4;*MV5CrU>* zN{wxA7Ipk#e3*)5eZ;@ubOaDfs+Hs3i?^&F zKR4CJ;U1ae>~d@^J2X{GZ<1gpY%imju>LX9Q_y#;TZh^wES_JOck^=RJ&JrDSe0-} zFdD_IitujM4q9ue1h@UMTL1GWO?u*aFyjJclRQabk2WiflKc??{ zS973ce_%<1QT}@0i|Hl*F;|aIRx3ohtC}i#h{v-uQm;42t&hk1Q}f3tZ{1(#D}`Bb zCDq`k;5P+-z`5Jkz19$T?~0+N5StYU4*k(k^U(HE(wfJS?TBr<{u?ADL6RtOGK`?w z*s(@fj35z`D6DV4VWogGBH8e{7gk=t8F4bvw6-<@bWBWm0W-6&u&}WH9W8mGUgQ}4 zr2S{5$3gTSxRe)G0bOjb5Du|7dgW#g&clL&bOl;Hq*K_2|88vfWMqJ{Y7Ae|M2Qo9 zX}Qr&wf_0VM=>9njJ7n>(Uy$u6O~EbTnlev5;G;fKPmxvJdK*_j>$!-7kWDFp~!{ei9r#~uemEblzQ~c|Pz;e%BlJWLpzQ#^Dn3Ef7Qp+blnxmn#Mv-QC zB5sF7=<#)-+N07V*T#%l83=3iIfS?;h)s)LaiX5gG5YxL`u85AJD2p<+*+bL7-Xod z!XksqL2vS2Z+Ex(7eoO80k8JaQMp8Eil*k~)62`;iu`T&-rL*Tpr+(ww;VzisNOs>)smTvNPPDZHrHkLuOTe2Bo z)=9Lt3$*D+*I0N7WpL%2_X099pYqQ$qQAqDpUEH<`UA#!sHuh{epsa`u9F%arj zNe^%WOo>`uxcmK;E(<>F$?5553JQPI$-J#XS|&J;J_V9hL)cc^saTY@KsgGD^BTta z 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 Date: Fri, 17 Jul 2026 19:14:26 +0000 Subject: [PATCH 013/445] Automatic changelog for PR #96507 [ci skip] --- html/changelogs/AutoChangeLog-pr-96507.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96507.yml diff --git a/html/changelogs/AutoChangeLog-pr-96507.yml b/html/changelogs/AutoChangeLog-pr-96507.yml new file mode 100644 index 000000000000..a4c800eb9cd3 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96507.yml @@ -0,0 +1,5 @@ +author: "Melbert" +delete-after: True +changes: + - rscadd: "Adds anomalous lure hook, a fishing hook which accepts (almost) any type of anomaly core, causing unique effects depending on the core used." + - rscdel: "Material fishing rods no longer innately catch material fish, you need a dimensional anomaly core in an anomalous lure hook to catch material fish now." \ No newline at end of file From 47175d5ed0d21cfd0934457c26f2bbbb48e56927 Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:28:57 +0200 Subject: [PATCH 014/445] Born, real monkeys retain their primitive brains when turned into humans. (#96977) ## About The Pull Request Yeah, suddenly a monkey that has spent its entirely life being a primate with the general intellect of a child becomes a relatively intelligent spaceman capable of using plenty of tools and computers as soon as they get their innate monkey genes deactivated, especially when we have the clever trait which is supposed to be the one handling that. This PR also deals with `/datum/mutation/headless/on_losing()` possibly regenerating organs in other zones of the body that are neither the head (eyes and tongue) or the torso, like a removed appendix. ## Why It's Good For The Game @CabinetOnFire asked me if I could do this, as he says that humanized monkeys losing their primitive brains is causing him some issues as the now human monkeys can perform advanced tool interactions when it really shouldn't fit their nature. ## Changelog :cl: balance: humanized monkeys (that were born as monkeys) should have their primitive ape brain untouched (not an advanced tool user unless they have the clever trait). /:cl: --- code/datums/mutations/body.dm | 3 ++- .../mob/living/carbon/human/_species.dm | 15 ++++---------- .../carbon/human/species_types/monkeys.dm | 6 ++++++ code/modules/surgery/organs/_organ.dm | 20 +++++++++++++++++++ 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index efdab6a92d2a..a97bf4bb8800 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -572,7 +572,8 @@ brain.zone = initial(brain.zone) brain.Insert(owner, special = TRUE, movement_flags = NO_ID_TRANSFER) - owner.dna.species.regenerate_organs(owner, replace_current = FALSE, excluded_zones = list(BODY_ZONE_CHEST)) //replace_current needs to be FALSE to prevent weird adding and removing mutation healing + var/list/excluded_zones = GLOB.all_body_zones - BODY_ZONE_HEAD + owner.dna.species.regenerate_organs(owner, replace_current = FALSE, excluded_zones = excluded_zones) //replace_current needs to be FALSE to prevent weird adding and removing mutation healing owner.apply_damage(damage = 50, damagetype = BRUTE, def_zone = BODY_ZONE_HEAD) //and this to DISCOURAGE organ farming, or at least not make it free. owner.visible_message(span_warning("[owner]'s head returns with a sickening crunch!"), span_warning("Your head regrows with a sickening crack! Ouch.")) new /obj/effect/gibspawner/generic(get_turf(owner), owner) diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 28d95bd3626d..79f0ea6fb5ee 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -291,22 +291,15 @@ GLOBAL_LIST_EMPTY(features_by_species) var/obj/item/organ/new_organ = get_mutant_organ_type_for_slot(slot) var/old_organ_type = old_species?.get_mutant_organ_type_for_slot(slot) + if(existing_organ && !existing_organ.get_replaceability(new_organ, old_organ_type, old_species, replace_current)) + continue // if we have an extra organ that before changing that the species didnt have, remove it - if(!new_organ) - if(existing_organ && (old_organ_type == existing_organ.type || replace_current)) + else if(!new_organ) + if(existing_organ) existing_organ.Remove(organ_holder) qdel(existing_organ) continue - if(existing_organ) - // we dont want to remove organs that were not from the old species (such as from freak surgery or prosthetics) - if(existing_organ.type != old_organ_type && !replace_current) - continue - - // we don't want to remove organs that are the same as the new one - if(existing_organ.type == new_organ) - continue - if(visual_only && (!initial(new_organ.bodypart_overlay) && !initial(new_organ.visual))) continue diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm index 556814211180..92f5a39904d3 100644 --- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm +++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm @@ -123,6 +123,12 @@ /// Will this monkey stumble if they are crossed by a simple mob or a carbon in combat mode? Toggable by monkeys with clients, and is messed automatically set to true by monkey AI. var/tripping = TRUE +/obj/item/organ/brain/primate/get_replaceability(obj/item/organ/new_organ_type, obj/item/organ/expected_organ_type, datum/species/old_species, replace_current = TRUE) + ///Real monkeys retain their ape brains when humanized (further species change can override it). If old_species is null, + if(HAS_TRAIT(owner, TRAIT_BORN_MONKEY) && (!old_species || istype(old_species, /datum/species/monkey))) + return FALSE + return ..() + /datum/action/item_action/organ_action/toggle_trip name = "Toggle Tripping" button_icon = 'icons/mob/actions/actions_changeling.dmi' diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm index 1ac1840418d6..79fec4cd2e51 100644 --- a/code/modules/surgery/organs/_organ.dm +++ b/code/modules/surgery/organs/_organ.dm @@ -431,6 +431,26 @@ INITIALIZE_IMMEDIATE(/obj/item/organ) /obj/item/organ/proc/get_availability(datum/species/owner_species, mob/living/owner_mob) return TRUE +/** + * Perform a series of get to see if this organ is elegible to be replaced by regenerate_orgas. While get_availability is for the new_organ + * this one is, conversely, for the old organ. + * + * * new_organ_type - the type of the new organ we ought to replace this with. + * * expected_organ_type - the old organ for this slot that the old species is supposed to have, which may or may not match what the mob currently has. + * * species - the old species datum, present if the regenerate_organs proc has been called as a result of a species change. + * * replace_current - boolean, *generally* force the organ to be deleted whether or not they pass the species' ability to keep that organ. + */ +/obj/item/organ/proc/get_replaceability(obj/item/organ/new_organ_type, obj/item/organ/expected_organ_type, datum/species/old_species, replace_current = TRUE) + // we don't want to remove organs that are the same as the new one + if(type == new_organ_type) + return FALSE + + // we dont want to remove organs that were not from the old species (such as from freak surgery or prosthetics) + if(replace_current || type == expected_organ_type) + return TRUE + + return FALSE + /// Called before organs are replaced in regenerate_organs with new ones /obj/item/organ/proc/before_organ_replacement(obj/item/organ/replacement) SHOULD_CALL_PARENT(TRUE) From 61be7c0f8a59e3834f64e75fb4f95f0584c90131 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 19:29:17 +0000 Subject: [PATCH 015/445] Automatic changelog for PR #96977 [ci skip] --- html/changelogs/AutoChangeLog-pr-96977.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96977.yml diff --git a/html/changelogs/AutoChangeLog-pr-96977.yml b/html/changelogs/AutoChangeLog-pr-96977.yml new file mode 100644 index 000000000000..2069018d813f --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96977.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - balance: "humanized monkeys (that were born as monkeys) should have their primitive ape brain untouched (not an advanced tool user unless they have the clever trait)." \ No newline at end of file From d31209cfbfe61049b42a67ce76d0b1209e5b2758 Mon Sep 17 00:00:00 2001 From: nPocToDark Date: Fri, 17 Jul 2026 22:30:13 +0300 Subject: [PATCH 016/445] Adds Pharaoh costumes to DeusVend (#96985) ## About The Pull Request Curse of Ra is back. Now you can buy Pharaoh Hat, Nemes Hat and Pharaoh Tunic in DeusVend (premium) dreamseeker_rt14sBNYyO N2GbZpuGpx ## Why It's Good For The Game Unused (for years) content is bad. Cool costplay is good. ## Changelog :cl: add: Curse of Ra is back. Now you can buy some pharaon costumes in DeusVend (premium) /:cl: --- code/modules/vending/wardrobes.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm index 262024004eb1..47649c8a8b01 100644 --- a/code/modules/vending/wardrobes.dm +++ b/code/modules/vending/wardrobes.dm @@ -587,6 +587,9 @@ GLOBAL_VAR_INIT(roaches_deployed, FALSE) premium = list( /obj/item/clothing/head/chaplain/bishopmitre = 1, /obj/item/clothing/suit/chaplainsuit/bishoprobe = 1, + /obj/item/clothing/head/costume/pharaoh = 1, + /obj/item/clothing/head/costume/nemes = 1, + /obj/item/clothing/suit/costume/nemes = 1, ) refill_canister = /obj/item/vending_refill/wardrobe/chap_wardrobe payment_department = ACCOUNT_SRV From 692faca426b489680d9520fa9bd9ed30fdd155ae Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 19:30:35 +0000 Subject: [PATCH 017/445] Automatic changelog for PR #96985 [ci skip] --- html/changelogs/AutoChangeLog-pr-96985.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96985.yml diff --git a/html/changelogs/AutoChangeLog-pr-96985.yml b/html/changelogs/AutoChangeLog-pr-96985.yml new file mode 100644 index 000000000000..5877899bf5ab --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96985.yml @@ -0,0 +1,4 @@ +author: "nPocToDark" +delete-after: True +changes: + - rscadd: "Curse of Ra is back. Now you can buy some pharaon costumes in DeusVend (premium)" \ No newline at end of file From 0c389f634221e4363b3931b61d381e8ec4903344 Mon Sep 17 00:00:00 2001 From: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:56:04 -0400 Subject: [PATCH 018/445] Wumborian Fugu floats (#97007) ## About The Pull Request Wumborian Fugu floats. Yes, floats. For sure. ## Why It's Good For The Game It didn't before but its friendly verbs are these so it seems like it probably should https://github.com/tgstation/tgstation/blob/0a646be8fca2c0609495d8dd8cb36fd728ae97da/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm#L35-L36 ## Changelog :cl: fix: Wumborian Fugu floats /:cl: --- .../living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm index dd966839bbec..613806024e65 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm @@ -52,6 +52,7 @@ . = ..() AddComponent(/datum/component/seethrough_mob) AddElement(/datum/element/death_drops, /obj/item/fugu_gland) + AddElement(/datum/element/simple_flying) add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE), ROUNDSTART_TRAIT) expand = new(src) expand.Grant(src) From afb40af7bbbeb3d56f49a2a043d93050f05c4222 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:56:23 +0000 Subject: [PATCH 019/445] Automatic changelog for PR #97007 [ci skip] --- html/changelogs/AutoChangeLog-pr-97007.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97007.yml diff --git a/html/changelogs/AutoChangeLog-pr-97007.yml b/html/changelogs/AutoChangeLog-pr-97007.yml new file mode 100644 index 000000000000..24e25a9a2aac --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97007.yml @@ -0,0 +1,4 @@ +author: "lelandkemble" +delete-after: True +changes: + - bugfix: "Wumborian Fugu floats" \ No newline at end of file From 64585acb432ba4c43479697433455f3c10e72184 Mon Sep 17 00:00:00 2001 From: Roxy <75404941+TealSeer@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:21:47 -0500 Subject: [PATCH 020/445] Fix flaky DNA infusion unit test (#97005) ## About The Pull Request Randomly picking a subtype can result in picking an abstract subtype ## Why It's Good For The Game Fixes #96979 ## Changelog N/A --- code/modules/unit_tests/dna_infusion.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/unit_tests/dna_infusion.dm b/code/modules/unit_tests/dna_infusion.dm index a8d8b2ea4ec7..96805ddbe728 100644 --- a/code/modules/unit_tests/dna_infusion.dm +++ b/code/modules/unit_tests/dna_infusion.dm @@ -7,7 +7,7 @@ if(ispath(input_type, /mob/living)) continue if(input_type == input_type::abstract_type) - input_type = pick(subtypesof(input_type)) + input_type = pick(valid_subtypesof(input_type)) var/atom/movable/movable = allocate(input_type) if(!HAS_TRAIT(movable, TRAIT_VALID_DNA_INFUSION)) From e9a5e566312791730625e3394ab0382f8322aa88 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 00:00:24 +0000 Subject: [PATCH 021/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-96507.yml | 5 ----- html/changelogs/AutoChangeLog-pr-96628.yml | 4 ---- html/changelogs/AutoChangeLog-pr-96977.yml | 4 ---- html/changelogs/AutoChangeLog-pr-96985.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97007.yml | 4 ---- html/changelogs/archive/2026-07.yml | 18 ++++++++++++++++++ 6 files changed, 18 insertions(+), 21 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-96507.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96628.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96977.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96985.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97007.yml diff --git a/html/changelogs/AutoChangeLog-pr-96507.yml b/html/changelogs/AutoChangeLog-pr-96507.yml deleted file mode 100644 index a4c800eb9cd3..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96507.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - rscadd: "Adds anomalous lure hook, a fishing hook which accepts (almost) any type of anomaly core, causing unique effects depending on the core used." - - rscdel: "Material fishing rods no longer innately catch material fish, you need a dimensional anomaly core in an anomalous lure hook to catch material fish now." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96628.yml b/html/changelogs/AutoChangeLog-pr-96628.yml deleted file mode 100644 index cc35b6250314..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96628.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "CabinetOnFire, Iamgoofball, SmartKar, Ben10omintrix" -delete-after: True -changes: - - refactor: "Replaces our AI system with behavior trees, porting all datum/ai to it" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96977.yml b/html/changelogs/AutoChangeLog-pr-96977.yml deleted file mode 100644 index 2069018d813f..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96977.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - balance: "humanized monkeys (that were born as monkeys) should have their primitive ape brain untouched (not an advanced tool user unless they have the clever trait)." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96985.yml b/html/changelogs/AutoChangeLog-pr-96985.yml deleted file mode 100644 index 5877899bf5ab..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96985.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "nPocToDark" -delete-after: True -changes: - - rscadd: "Curse of Ra is back. Now you can buy some pharaon costumes in DeusVend (premium)" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97007.yml b/html/changelogs/AutoChangeLog-pr-97007.yml deleted file mode 100644 index 24e25a9a2aac..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97007.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "lelandkemble" -delete-after: True -changes: - - bugfix: "Wumborian Fugu floats" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index 2e6afab9ea52..7f4d06607baa 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -548,3 +548,21 @@ lelandkemble: - rscadd: Added craftable anti-mech tripwires. A mech that foolishly steps on one will fall down and be immobilized temporarily. Don't let it fall on you! +2026-07-18: + CabinetOnFire, Iamgoofball, SmartKar, Ben10omintrix: + - refactor: Replaces our AI system with behavior trees, porting all datum/ai to + it + Ghommie: + - balance: humanized monkeys (that were born as monkeys) should have their primitive + ape brain untouched (not an advanced tool user unless they have the clever trait). + Melbert: + - rscadd: Adds anomalous lure hook, a fishing hook which accepts (almost) any type + of anomaly core, causing unique effects depending on the core used. + - rscdel: Material fishing rods no longer innately catch material fish, you need + a dimensional anomaly core in an anomalous lure hook to catch material fish + now. + lelandkemble: + - bugfix: Wumborian Fugu floats + nPocToDark: + - rscadd: Curse of Ra is back. Now you can buy some pharaon costumes in DeusVend + (premium) From 55108c9c15498839f65de5c8a027a1d54edf9104 Mon Sep 17 00:00:00 2001 From: YesterdaysPromise <122572637+YesterdaysPromise@users.noreply.github.com> Date: Sat, 18 Jul 2026 03:03:50 +0200 Subject: [PATCH 022/445] Manic Titanic Panic: or, survival pod floors and walls are no longer made from unobtainium, and have two more variants each. (#96838) ## About The Pull Request Hey hey h3y, ev3ry b0dy! Your least favourite stinky stupid sacriligious fool stumbled from under the carpet. Making a larger (LARGER) pull request and was recommended I should cut this sizeable chunk out, so, who am I to refuse. This one, then, makes pod floors and walls actually craftable by human in game hands. Fake walls included. Simultaniously it adds a new feature; a 'wall-filling', tile equivalent for griders, in-hand reskinnable coating item that makes it so a single material can make more than one wall. Potential applications are as endless as tile possibilities, as you might witness in the larger PR. Importantly, there is no _actual_ mapping changes. It all comes from refactor of walls and floors to plastitanium. image Here you go if you wish to witness the new variants. Nothing too stunning, I know. ## Why It's Good For The Game Picture this: You are a miner. Your pod's wall blew up. You need to replace it. Now its ugly patchwork. Well, now not. The new variants mostly serve mappers, and player's creativity, plus, consistency just bothered me. ## Changelog :cl: refactor: Podfloors (floors normaly seen in Survival Pods of miners) are now considered a subtype of plastitanium tiles, and thus craftable. add: To match with plastitanium, pofloors recieved 2 red variants. add: Plastitanium can now make a new wall-filling item which you can reskin in hand to make regular plastitanium walls, pod walls, as well as added unmarked pod walls ('dark shuttle') and red unmarked pod walls ('dark red shuttle'). /:cl: --------- Co-authored-by: Jacquerel --- .../icemoon_underground_outpost31.dmm | 58 ++++---- .../LavaRuins/lavaland_biodome_beach.dmm | 32 ++--- .../lavaland_surface_biodome_winter.dmm | 54 +++---- .../LavaRuins/lavaland_surface_crashsite.dmm | 28 ++-- .../LavaRuins/lavaland_surface_hermit.dmm | 16 +-- .../lavaland_surface_survivalpod.dmm | 32 ++--- _maps/RandomRuins/SpaceRuins/crashedship.dmm | 20 +-- _maps/RandomRuins/SpaceRuins/derelict9.dmm | 20 +-- .../SpaceRuins/derelict_sulaco.dmm | 82 +++++------ _maps/RandomRuins/SpaceRuins/film_studio.dmm | 34 ++--- .../RandomRuins/SpaceRuins/forgottenship.dmm | 18 +-- .../SpaceRuins/infested_frigate.dmm | 90 ++++++------ _maps/RandomRuins/SpaceRuins/oldstation.dmm | 2 +- _maps/RandomRuins/SpaceRuins/vaporwave.dmm | 2 +- _maps/RandomZLevels/TheBeach.dmm | 2 +- _maps/RandomZLevels/heretic.dmm | 40 +++--- _maps/RandomZLevels/moonoutpost19.dmm | 34 ++--- _maps/minigame/CTF/cruiser.dmm | 78 +++++----- _maps/minigame/deathmatch/secu_ring.dmm | 20 +-- _maps/safehouses/den.dmm | 32 ++--- _maps/safehouses/ice.dmm | 6 +- _maps/shuttles/arrival/arrival_northstar.dmm | 38 ++--- _maps/shuttles/cargo/cargo_catwalk.dmm | 4 +- _maps/shuttles/cargo/cargo_northstar.dmm | 4 +- .../emergency/emergency_northstar.dmm | 18 +-- _maps/shuttles/ert_bounty.dmm | 78 +++++----- .../escape_pod/escape_pod_cramped.dmm | 4 +- _maps/shuttles/ferry/ferry_fancy.dmm | 22 +-- _maps/shuttles/hunter/hunter_bounty.dmm | 62 ++++---- _maps/shuttles/hunter/hunter_russian.dmm | 82 +++++------ .../infiltrator/infiltrator_advanced.dmm | 80 +++++------ .../mining/mining_common_northstar.dmm | 28 ++-- _maps/shuttles/mining/mining_northstar.dmm | 30 ++-- _maps/shuttles/pirate/pirate_default.dmm | 40 +++--- _maps/shuttles/pirate/pirate_grey.dmm | 12 +- _maps/shuttles/pirate/pirate_irs.dmm | 104 +++++++------- _maps/shuttles/pirate/pirate_silverscale.dmm | 80 +++++------ .../shuttles/ruin/ruin_syndicate_dropship.dmm | 12 +- _maps/shuttles/whiteship/whiteship_cere.dmm | 46 +++--- _maps/shuttles/whiteship/whiteship_kilo.dmm | 18 +-- _maps/shuttles/whiteship/whiteship_pubby.dmm | 20 +-- _maps/shuttles/whiteship/whiteship_tram.dmm | 32 ++--- _maps/templates/battlecruiser_starfury.dmm | 128 ++++++++--------- _maps/templates/shelter_1.dmm | 30 ++-- _maps/templates/shelter_2.dmm | 34 ++--- _maps/templates/shelter_3.dmm | 68 ++++----- _maps/templates/shelter_t.dmm | 8 +- _maps/virtual_domains/beach_bar.dmm | 24 ++-- _maps/virtual_domains/island_brawl.dmm | 8 +- _maps/virtual_domains/syndicate_assault.dmm | 18 +-- _maps/virtual_domains/vaporwave.dmm | 2 +- code/__DEFINES/icon_smoothing.dm | 4 +- code/_globalvars/lists/girder_wall_recipes.dm | 9 ++ .../effects/spawners/random/decoration.dm | 6 +- .../objects/items/stacks/sheets/mineral.dm | 1 + .../items/stacks/tiles/tile_mineral.dm | 48 ++++++- .../objects/items/stacks/tiles/tile_types.dm | 31 ---- .../game/objects/items/stacks/wall_filling.dm | 134 ++++++++++++++++++ code/game/objects/structures/false_walls.dm | 49 +++++++ code/game/turfs/closed/wall/mineral_walls.dm | 72 +++++++--- code/game/turfs/open/floor/mineral_floor.dm | 23 +++ code/game/turfs/open/floor/misc_floor.dm | 14 -- icons/obj/stack_objects.dmi | Bin 64518 -> 65703 bytes icons/obj/tiles.dmi | Bin 33342 -> 34157 bytes icons/turf/floors.dmi | Bin 411735 -> 409583 bytes icons/turf/walls/dark_pod.dmi | Bin 0 -> 14106 bytes icons/turf/walls/dark_pod.png | Bin 0 -> 1754 bytes icons/turf/walls/dark_pod.png.toml | 2 + icons/turf/walls/false_walls.dmi | Bin 49104 -> 57718 bytes icons/turf/walls/red_pod.dmi | Bin 0 -> 13673 bytes icons/turf/walls/red_pod.png | Bin 0 -> 1481 bytes icons/turf/walls/red_pod.png.toml | 2 + tgstation.dme | 1 + .../96838_podstuff_to_plastitatanium.txt | 3 + 74 files changed, 1242 insertions(+), 991 deletions(-) create mode 100644 code/game/objects/items/stacks/wall_filling.dm create mode 100644 icons/turf/walls/dark_pod.dmi create mode 100644 icons/turf/walls/dark_pod.png create mode 100644 icons/turf/walls/dark_pod.png.toml create mode 100644 icons/turf/walls/red_pod.dmi create mode 100644 icons/turf/walls/red_pod.png create mode 100644 icons/turf/walls/red_pod.png.toml create mode 100644 tools/UpdatePaths/Scripts/96838_podstuff_to_plastitatanium.txt diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_outpost31.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_outpost31.dmm index b6531a220082..8769d34900c5 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_underground_outpost31.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_underground_outpost31.dmm @@ -442,7 +442,7 @@ /area/ruin/outpost31/lab) "hN" = ( /obj/effect/decal/cleanable/dirt/dust, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "hT" = ( /obj/machinery/light/warm/directional/east, @@ -512,7 +512,7 @@ dir = 8 }, /obj/effect/decal/cleanable/dirt/dust, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "jP" = ( /obj/effect/turf_decal/stripes/line{ @@ -557,7 +557,7 @@ "kP" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "kX" = ( /obj/effect/turf_decal/siding/wood{ @@ -600,7 +600,7 @@ /area/ruin/outpost31) "lM" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "lO" = ( /obj/effect/decal/cleanable/dirt/dust, @@ -762,7 +762,7 @@ /turf/open/floor/wood, /area/ruin/outpost31/crewquarters) "oF" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "oJ" = ( /obj/structure/chair/stool/directional/east, @@ -849,12 +849,12 @@ }, /obj/effect/decal/cleanable/dirt/dust, /obj/machinery/light/small/dim/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/outpost31) "qI" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4, /obj/item/flashlight/lantern, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/outpost31) "qK" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer2, @@ -1048,7 +1048,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4, /obj/structure/cable, /obj/effect/decal/cleanable/dirt/dust, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "uA" = ( /obj/structure/cable, @@ -1071,7 +1071,7 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ cycle_id = "o31a" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "uR" = ( /obj/effect/turf_decal/siding/white{ @@ -1084,7 +1084,7 @@ name = "winter gear closet" }, /obj/effect/decal/cleanable/dirt/dust, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/outpost31) "vU" = ( /obj/structure/cable, @@ -1213,7 +1213,7 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ cycle_id = "o31B" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "yo" = ( /turf/closed/indestructible/reinforced, @@ -1254,7 +1254,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4, /obj/structure/cable, /obj/effect/mapping_helpers/airlock/autoname, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "ze" = ( /obj/structure/table/optable, @@ -1307,7 +1307,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "zF" = ( /obj/machinery/duct, @@ -1338,7 +1338,7 @@ /obj/structure/barricade/wooden/crude/snow{ opacity = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "Ae" = ( /obj/effect/decal/cleanable/dirt/dust, @@ -1425,7 +1425,7 @@ /obj/structure/tank_dispenser/oxygen{ oxygentanks = 5 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/outpost31) "BI" = ( /obj/effect/decal/cleanable/dirt/dust, @@ -1460,7 +1460,7 @@ }, /obj/effect/decal/cleanable/dirt/dust, /obj/machinery/light/small/dim/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/outpost31) "Ca" = ( /obj/structure/closet/emcloset, @@ -1475,7 +1475,7 @@ /obj/structure/cable, /obj/effect/decal/cleanable/dirt/dust, /obj/item/crowbar, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/ruin/outpost31) "Ce" = ( /obj/effect/decal/cleanable/dirt, @@ -1746,7 +1746,7 @@ /obj/machinery/atmospherics/components/unary/airlock_pump{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "HF" = ( /obj/effect/decal/cleanable/dirt, @@ -1838,7 +1838,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "Jq" = ( /obj/effect/spawner/random/entertainment/arcade, @@ -1891,7 +1891,7 @@ /obj/structure/closet/preopen{ name = "winter gear closet" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/outpost31) "Lk" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4, @@ -1911,7 +1911,7 @@ }, /obj/effect/decal/cleanable/dirt/dust, /obj/machinery/light/small/broken/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/outpost31) "LA" = ( /turf/closed/wall, @@ -1981,7 +1981,7 @@ /obj/structure/tank_dispenser/oxygen{ oxygentanks = 5 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "MO" = ( /obj/effect/turf_decal/stripes/line{ @@ -2004,7 +2004,7 @@ }, /obj/effect/decal/cleanable/dirt/dust, /obj/machinery/light/small/dim/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "MW" = ( /obj/structure/cable, @@ -2032,7 +2032,7 @@ /obj/structure/barricade/wooden/crude/snow{ opacity = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "Nl" = ( /obj/machinery/power/apc/auto_name/directional/south, @@ -2286,7 +2286,7 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ cycle_id = "o31c" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "SN" = ( /obj/machinery/atmospherics/components/unary/passive_vent/layer4{ @@ -2463,7 +2463,7 @@ }, /obj/item/clothing/shoes/winterboots/ice_boots/eva, /obj/item/clothing/suit/hooded/wintercoat/eva, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/outpost31) "VR" = ( /obj/structure/closet/crate/bin, @@ -2498,7 +2498,7 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ cycle_id = "o31B" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "Wu" = ( /obj/effect/decal/cleanable/dirt/dust, @@ -2516,7 +2516,7 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ cycle_id = "o31c" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/outpost31) "WQ" = ( /turf/template_noop, @@ -2624,7 +2624,7 @@ name = "winter gear closet" }, /obj/machinery/light/small/dim/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/outpost31) "Zq" = ( /obj/structure/window/spawner/directional/east, diff --git a/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm b/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm index f426b8fe1d93..db975b54ba5f 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm @@ -117,7 +117,7 @@ density = 1 }, /obj/effect/decal/fakelattice, -/turf/open/floor/pod/light{ +/turf/open/floor/mineral/plastitanium/pod/light{ density = 1 }, /area/ruin/powered/beach) @@ -195,7 +195,7 @@ "eA" = ( /obj/item/melee/skateboard/hoverboard, /obj/machinery/light/directional/west, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "eE" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -242,7 +242,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "gF" = ( /obj/effect/turf_decal/sand, @@ -332,7 +332,7 @@ /area/ruin/powered/beach) "hX" = ( /obj/structure/punching_bag, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/beach) "iH" = ( /obj/structure/flora/bush/stalky/style_random, @@ -443,12 +443,12 @@ /area/ruin/powered/beach) "og" = ( /obj/structure/weightmachine/weightlifter, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/beach) "oF" = ( /obj/structure/table/reinforced, /obj/machinery/reagentgrinder, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "oL" = ( /obj/structure/extinguisher_cabinet/directional/south, @@ -460,7 +460,7 @@ /area/ruin/powered/beach) "oU" = ( /obj/machinery/seed_extractor, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "pz" = ( /turf/open/water/beach, @@ -494,7 +494,7 @@ /area/ruin/powered/beach) "qf" = ( /obj/structure/reagent_dispensers/watertank, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "qg" = ( /obj/structure/flora/bush/large/style_random, @@ -556,7 +556,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "rU" = ( /obj/item/reagent_containers/condiment/enzyme, @@ -633,7 +633,7 @@ /turf/open/misc/asteroid/basalt/lava_land_surface/biome_replace, /area/lavaland/surface/outdoors/explored) "wY" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "xa" = ( /obj/effect/mapping_helpers/broken_floor, @@ -749,7 +749,7 @@ /area/ruin/powered/beach) "AE" = ( /obj/item/melee/skateboard/hoverboard, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "AG" = ( /obj/structure/flora/rock/pile/jungle/style_random, @@ -858,7 +858,7 @@ /obj/structure/table/reinforced, /obj/item/secateurs, /obj/item/reagent_containers/cup/bottle/nutrient/ez, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "Fa" = ( /obj/structure/chair/stool/bar/directional/south, @@ -952,7 +952,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "Iv" = ( /turf/open/floor/iron/stairs/right, @@ -1010,7 +1010,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "Kn" = ( /obj/structure/flora/bush/large/style_random, @@ -1122,7 +1122,7 @@ desc = "A sink used for washing one's hands and face. It looks rusty and home-made"; name = "old sink" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/beach) "Qf" = ( /obj/effect/turf_decal/sand, @@ -1318,7 +1318,7 @@ /turf/open/floor/plating, /area/ruin/powered/beach) "VO" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/beach) "VY" = ( /obj/structure/flora/coconuts, diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm index 35c88aa39992..c730e94ad3c7 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm @@ -178,11 +178,11 @@ "aO" = ( /obj/machinery/door/airlock/multi_tile/public/glass, /obj/structure/fans/tiny, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "aP" = ( /obj/structure/fans/tiny, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "aQ" = ( /obj/structure/flora/grass/both/style_random, @@ -239,24 +239,24 @@ /area/ruin/powered/snow_biodome) "dS" = ( /obj/machinery/light/small/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "eb" = ( /obj/machinery/light/small/directional/east, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "eg" = ( /obj/machinery/vending/coffee, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "gh" = ( /obj/machinery/light/small/directional/west, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "gz" = ( /obj/structure/chair/stool/directional/south, /mob/living/basic/skeleton/settler, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "hr" = ( /mob/living/basic/skeleton/settler, @@ -268,7 +268,7 @@ "hA" = ( /obj/machinery/light/empty/directional/north, /obj/effect/mapping_helpers/no_lava, -/turf/open/floor/pod/dark{ +/turf/open/floor/mineral/plastitanium/pod/dark{ initial_gas_mix = "LAVALAND_ATMOS" }, /area/ruin/powered/snow_biodome) @@ -289,7 +289,7 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "sn" = ( /obj/structure/bed{ @@ -305,7 +305,7 @@ /turf/open/misc/asteroid/basalt/lava_land_surface/biome_replace, /area/lavaland/surface/outdoors/explored) "tl" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/powered/snow_biodome) "uO" = ( /obj/item/bedsheet/blue{ @@ -328,7 +328,7 @@ /area/ruin/powered/snow_biodome) "xU" = ( /obj/item/storage/toolbox/mechanical, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "zT" = ( /obj/machinery/door/airlock/silver, @@ -339,7 +339,7 @@ /obj/structure/table, /obj/item/pen, /obj/item/paper, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "BM" = ( /obj/effect/turf_decal/siding/wood/corner{ @@ -352,16 +352,16 @@ /obj/item/clothing/suit/hooded/wintercoat/science, /obj/item/clothing/shoes/winterboots, /obj/item/clothing/gloves/fingerless, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "Ef" = ( /obj/structure/reagent_dispensers/fueltank, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "Ez" = ( /obj/structure/fans/tiny, /obj/machinery/door/airlock/multi_tile/public/glass, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "EA" = ( /obj/effect/turf_decal/siding/wood/corner, @@ -373,30 +373,30 @@ /turf/open/misc/asteroid/snow, /area/ruin/powered/snow_biodome) "HP" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "HR" = ( /obj/structure/closet/secure_closet/freezer/fridge/open, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "JZ" = ( /obj/structure/table, /obj/machinery/microwave, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "KS" = ( /obj/item/chainsaw, /obj/structure/closet, /obj/machinery/light/small/directional/east, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "Mp" = ( /obj/item/clothing/mask/balaclava, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "Oj" = ( /obj/structure/table, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "PD" = ( /obj/machinery/door/airlock/hatch, @@ -404,13 +404,13 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "PK" = ( /obj/structure/table, /obj/item/pen, /obj/item/paper_bin, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "PM" = ( /obj/effect/turf_decal/siding/wood, @@ -430,7 +430,7 @@ "QK" = ( /obj/structure/table, /obj/item/storage/fancy/cigarettes/cigpack_carp, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "QN" = ( /obj/effect/spawner/structure/window, @@ -438,17 +438,17 @@ /area/ruin/powered/snow_biodome) "Sj" = ( /obj/effect/decal/cleanable/blood/oil, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "Ub" = ( /obj/structure/filingcabinet, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "UM" = ( /obj/machinery/computer/monitor{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered/snow_biodome) "VE" = ( /obj/effect/turf_decal/siding/wood/corner{ diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_crashsite.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_crashsite.dmm index 660d01dedff3..ac5eeb236235 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_crashsite.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_crashsite.dmm @@ -11,13 +11,13 @@ "c" = ( /obj/item/gps/computer, /obj/structure/tubes, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "d" = ( /obj/structure/tubes, /obj/structure/rack, /obj/item/survivalcapsule, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "e" = ( /obj/structure/chair/comfy/shuttle{ @@ -32,7 +32,7 @@ /obj/item/bedsheet/black{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "g" = ( /obj/structure/rack, @@ -40,7 +40,7 @@ /obj/item/clothing/suit/space/orange, /obj/item/clothing/head/helmet/space/fragile, /obj/item/clothing/head/helmet/space/fragile, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "h" = ( /obj/effect/decal/cleanable/rubble, @@ -66,7 +66,7 @@ /area/lavaland/surface/outdoors) "m" = ( /obj/machinery/door/airlock/survival_pod/glass, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "n" = ( /turf/closed/wall/mineral/titanium{ @@ -80,7 +80,7 @@ /obj/structure/rack, /obj/item/storage/toolbox/emergency, /obj/item/storage/toolbox/emergency, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "q" = ( /turf/closed/wall/mineral/titanium/interior, @@ -92,7 +92,7 @@ /turf/open/floor/mineral/titanium/blue/lavaland_atmos, /area/ruin/unpowered) "s" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "t" = ( /obj/structure/flora/tree/stump, @@ -167,11 +167,11 @@ desc = "A heated storage unit. This one's seen better days."; name = "dusty survival pod storage" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "K" = ( /obj/structure/fans, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "L" = ( /obj/item/storage/toolbox/fishing, @@ -182,10 +182,10 @@ /obj/item/pickaxe/emergency, /obj/item/pickaxe/emergency, /obj/item/crowbar/large/emergency, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "N" = ( -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/ruin/powered) "O" = ( /obj/effect/spawner/structure/window/reinforced/shuttle, @@ -205,11 +205,11 @@ "S" = ( /obj/structure/bed/pod, /obj/item/bedsheet/black, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "T" = ( /obj/structure/tubes, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "U" = ( /obj/item/bait_can/worm/premium, @@ -218,7 +218,7 @@ "V" = ( /obj/structure/table/survival_pod, /obj/item/food/meat/steak/goliath, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "W" = ( /turf/closed/mineral/volcanic/lava_land_surface/biome_replace, diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm index c7c0e7e32836..38d285aa34df 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm @@ -87,7 +87,7 @@ /turf/open/misc/asteroid/basalt/lava_land_surface/biome_replace, /area/lavaland/surface/outdoors) "t" = ( -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/ruin/powered) "u" = ( /obj/structure/bed/pod, @@ -96,18 +96,18 @@ /area/ruin/powered) "v" = ( /obj/structure/fans, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "w" = ( /obj/machinery/smartfridge/survival_pod/preloaded, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "x" = ( /obj/effect/mob_spawn/ghost_role/human/hermit, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "y" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "z" = ( /obj/machinery/light/small/directional/east, @@ -121,13 +121,13 @@ /area/ruin/powered) "B" = ( /obj/structure/table/survival_pod, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "C" = ( /obj/structure/tubes, /obj/item/crowbar, /obj/effect/decal/cleanable/blood/drip, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "D" = ( /obj/effect/decal/cleanable/blood/footprints{ @@ -135,7 +135,7 @@ }, /obj/machinery/door/airlock/survival_pod/glass, /obj/structure/fans/tiny, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "E" = ( /obj/machinery/power/shuttle_engine/propulsion/burst{ diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm index 9e01f5f0063c..56d2f0dd53be 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm @@ -7,36 +7,36 @@ /area/lavaland/surface/outdoors) "c" = ( /obj/effect/turf_decal/mining, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/ruin/powered) "d" = ( -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/ruin/powered) "e" = ( /obj/effect/turf_decal/mining/survival, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/ruin/powered) "f" = ( /obj/structure/fans, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "g" = ( /obj/machinery/smartfridge/survival_pod{ desc = "A heated storage unit. This one's seen better days."; name = "dusty survival pod storage" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "h" = ( /obj/item/gps/computer, /obj/structure/tubes, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "i" = ( /obj/effect/turf_decal/mining/survival{ dir = 8 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/ruin/powered) "j" = ( /obj/effect/decal/cleanable/blood/drip, @@ -44,19 +44,19 @@ /area/lavaland/surface/outdoors) "k" = ( /obj/machinery/sleeper/survival_pod, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "l" = ( /obj/item/pickaxe, /obj/effect/decal/cleanable/blood, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "m" = ( /obj/structure/bed/pod, /obj/item/bedsheet/black, /obj/structure/tubes, /obj/machinery/light/small/broken/directional/east, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "n" = ( /obj/effect/decal/cleanable/blood, @@ -70,7 +70,7 @@ "p" = ( /obj/structure/table/survival_pod, /obj/item/knife/combat/survival, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "q" = ( /obj/effect/mob_spawn/corpse/human/miner/explorer{ @@ -82,13 +82,13 @@ /obj/effect/decal/cleanable/blood/footprints{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "r" = ( /obj/structure/tubes, /obj/item/crowbar, /obj/effect/decal/cleanable/blood/drip, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "s" = ( /obj/effect/decal/cleanable/blood/footprints, @@ -99,7 +99,7 @@ dir = 1 }, /obj/machinery/door/airlock/survival_pod/glass, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/powered) "u" = ( /obj/effect/decal/cleanable/blood/footprints{ @@ -119,7 +119,7 @@ /obj/effect/turf_decal/mining/survival{ dir = 4 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/ruin/powered) "x" = ( /obj/effect/decal/cleanable/blood/drip, @@ -132,7 +132,7 @@ /obj/effect/turf_decal/mining/survival{ dir = 1 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/ruin/powered) "z" = ( /obj/effect/decal/cleanable/blood/footprints{ diff --git a/_maps/RandomRuins/SpaceRuins/crashedship.dmm b/_maps/RandomRuins/SpaceRuins/crashedship.dmm index 13428e3432f3..cebb697e2177 100644 --- a/_maps/RandomRuins/SpaceRuins/crashedship.dmm +++ b/_maps/RandomRuins/SpaceRuins/crashedship.dmm @@ -94,7 +94,7 @@ /obj/structure/chair/comfy/shuttle{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/crashedship/aft) "eC" = ( /obj/effect/turf_decal/stripes/line{ @@ -141,7 +141,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/crashedship/aft) "gC" = ( /obj/item/reagent_containers/cup/glass/bottle/beer/almost_empty{ @@ -174,7 +174,7 @@ }, /obj/effect/spawner/random/maintenance/two, /obj/machinery/light/small/broken/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/crashedship/aft) "ip" = ( /obj/machinery/light/broken/directional/north, @@ -423,7 +423,7 @@ }, /obj/effect/turf_decal/trimline/blue/line, /obj/structure/closet/firecloset, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/crashedship/aft) "qI" = ( /obj/item/stack/ore/silver, @@ -843,7 +843,7 @@ /obj/machinery/door/airlock/grunge{ name = "Emergency Supplies" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/crashedship/aft) "Ff" = ( /obj/effect/turf_decal/siding/white{ @@ -873,7 +873,7 @@ /obj/effect/turf_decal/trimline/blue/line, /obj/machinery/atmospherics/components/unary/portables_connector/layer4, /obj/machinery/portable_atmospherics/canister/air, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/crashedship/aft) "FP" = ( /obj/effect/spawner/random/maintenance, @@ -1163,7 +1163,7 @@ }, /obj/structure/table, /obj/item/door_seal, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/crashedship/aft) "Ph" = ( /obj/effect/turf_decal/delivery/white{ @@ -1228,7 +1228,7 @@ }, /obj/effect/turf_decal/trimline/blue/line, /obj/effect/spawner/random/structure/tank_holder, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/crashedship/aft) "Uc" = ( /obj/effect/turf_decal/trimline/yellow/filled/end{ @@ -1243,7 +1243,7 @@ }, /obj/effect/turf_decal/trimline/blue/line, /obj/structure/chair/comfy/shuttle, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/crashedship/aft) "Uz" = ( /turf/closed/mineral/gibtonite, @@ -1351,7 +1351,7 @@ "Yw" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/visible/layer2, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/crashedship/aft) "YU" = ( /obj/item/stack/rods, diff --git a/_maps/RandomRuins/SpaceRuins/derelict9.dmm b/_maps/RandomRuins/SpaceRuins/derelict9.dmm index 2fa948a60fae..f2b174e00347 100644 --- a/_maps/RandomRuins/SpaceRuins/derelict9.dmm +++ b/_maps/RandomRuins/SpaceRuins/derelict9.dmm @@ -20,7 +20,7 @@ /area/ruin/space) "j" = ( /obj/structure/closet/crate/secure/loot, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav) "l" = ( /turf/template_noop, @@ -50,22 +50,22 @@ /obj/structure/rack, /obj/effect/spawner/random/clothing/beret_or_rabbitears, /obj/effect/spawner/random/clothing/mafia_outfit, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav) "x" = ( -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/ruin/space/has_grav) "B" = ( /obj/structure/rack, /obj/effect/spawner/random/clothing/funny_hats, /obj/effect/spawner/random/clothing/gloves, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav) "C" = ( /obj/structure/rack, /obj/effect/spawner/random/clothing/mafia_outfit, /obj/effect/spawner/random/clothing/pirate_or_bandana, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav) "E" = ( /obj/structure/mineral_door/wood, @@ -74,7 +74,7 @@ "G" = ( /obj/structure/safe, /obj/item/clothing/head/collectable/petehat, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav) "I" = ( /obj/machinery/porta_turret/syndicate/pod, @@ -99,7 +99,7 @@ /turf/open/floor/plating/airless, /area/ruin/space) "Q" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav) "S" = ( /obj/item/flashlight/lantern, @@ -112,17 +112,17 @@ /obj/structure/rack, /obj/effect/spawner/random/clothing/kittyears_or_rabbitears, /obj/effect/spawner/random/clothing/lizardboots, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav) "X" = ( /obj/structure/rack, /obj/effect/spawner/random/clothing/twentyfive_percent_cyborg_mask, /obj/effect/spawner/random/clothing/costume, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav) "Y" = ( /obj/machinery/light/dim/directional/north, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/ruin/space/has_grav) "Z" = ( /turf/closed/indestructible/fakedoor, diff --git a/_maps/RandomRuins/SpaceRuins/derelict_sulaco.dmm b/_maps/RandomRuins/SpaceRuins/derelict_sulaco.dmm index b3cd97c716b9..664719d67761 100644 --- a/_maps/RandomRuins/SpaceRuins/derelict_sulaco.dmm +++ b/_maps/RandomRuins/SpaceRuins/derelict_sulaco.dmm @@ -12,7 +12,7 @@ /obj/effect/decal/cleanable/blood/gibs, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "bO" = ( /obj/structure/window/reinforced/spawner/directional/south, @@ -34,7 +34,7 @@ pixel_x = 5; pixel_y = 9 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "bR" = ( /obj/effect/decal/cleanable/blood/gibs/robot_debris/up, @@ -42,7 +42,7 @@ /area/ruin/space/has_grav/derelictsulaco) "bV" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "cu" = ( /obj/effect/decal/cleanable/dirt, @@ -98,7 +98,7 @@ dir = 1 }, /obj/structure/cable, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "dM" = ( /obj/structure/alien/weeds, @@ -122,7 +122,7 @@ dir = 1; anchored = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "eZ" = ( /obj/effect/mapping_helpers/burnt_floor, @@ -156,7 +156,7 @@ /obj/structure/table/reinforced, /obj/item/paper_bin, /obj/item/pen/fountain, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "gx" = ( /obj/structure/alien/weeds, @@ -227,14 +227,14 @@ /obj/structure/window/reinforced/spawner/directional/west, /obj/structure/window/reinforced/spawner/directional/south, /obj/structure/table/reinforced, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "ib" = ( /obj/structure/window/reinforced/spawner/directional/south, /obj/structure/frame/computer{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "iu" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, @@ -251,7 +251,7 @@ /turf/open/floor/circuit, /area/ruin/space/has_grav/derelictsulaco) "jn" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "jo" = ( /obj/machinery/door/airlock/command/glass{ @@ -277,7 +277,7 @@ "jI" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "kb" = ( /obj/effect/decal/cleanable/blood/footprints{ @@ -291,7 +291,7 @@ /area/ruin/space/has_grav/derelictsulaco) "kd" = ( /obj/item/trash/flare, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "kB" = ( /obj/machinery/door/window/brigdoor/left/directional/east, @@ -329,7 +329,7 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "lf" = ( /obj/effect/turf_decal/siding/wideplating/dark, @@ -415,7 +415,7 @@ /obj/machinery/computer/terminal/sulaco/map, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "nn" = ( /obj/structure/alien/weeds, @@ -441,7 +441,7 @@ /obj/structure/window/reinforced/spawner/directional/north, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "ol" = ( /obj/structure/window/reinforced/spawner/directional/north, @@ -463,11 +463,11 @@ /obj/structure/barricade/sandbags, /obj/structure/cable, /obj/item/light/tube/broken, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "pv" = ( /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "pF" = ( /obj/effect/spawner/structure/window/reinforced, @@ -475,7 +475,7 @@ /area/ruin/space/has_grav/derelictsulaco) "qS" = ( /obj/item/shard, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "qY" = ( /obj/structure/window/reinforced/spawner/directional/west, @@ -491,7 +491,7 @@ "rb" = ( /obj/structure/window/reinforced/spawner/directional/south, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "ri" = ( /obj/item/circuitboard/machine/telecomms/processor, @@ -499,7 +499,7 @@ /area/ruin/space/has_grav/derelictsulaco) "rA" = ( /obj/machinery/fax, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "rJ" = ( /obj/structure/alien/weeds, @@ -535,7 +535,7 @@ /obj/structure/frame/computer{ anchored = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "so" = ( /obj/structure/alien/weeds, @@ -576,7 +576,7 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "tm" = ( /obj/effect/decal/cleanable/dirt, @@ -630,7 +630,7 @@ "uP" = ( /obj/effect/decal/cleanable/blood/footprints, /obj/structure/cable, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "uS" = ( /obj/structure/window/reinforced/spawner/directional/north, @@ -663,7 +663,7 @@ /area/ruin/space/has_grav/derelictsulaco) "vi" = ( /obj/effect/mapping_helpers/broken_floor, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "vj" = ( /obj/effect/spawner/structure/window/reinforced, @@ -686,7 +686,7 @@ /obj/structure/frame/machine{ anchored = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "wd" = ( /obj/structure/alien/weeds, @@ -727,7 +727,7 @@ /obj/structure/table/reinforced, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "yy" = ( /obj/structure/frame/machine{ @@ -750,7 +750,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/structure/cable, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "zf" = ( /obj/structure/alien/weeds, @@ -802,7 +802,7 @@ /obj/machinery/computer/terminal/sulaco/overwatch/main{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "Aa" = ( /obj/structure/alien/weeds, @@ -840,7 +840,7 @@ "CC" = ( /obj/effect/decal/cleanable/blood/gibs/robot_debris, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "CF" = ( /obj/effect/decal/cleanable/dirt, @@ -896,12 +896,12 @@ /obj/effect/decal/cleanable/glass, /obj/item/shard, /obj/structure/cable, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "EK" = ( /obj/effect/mapping_helpers/broken_floor, /obj/structure/barricade/sandbags, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "EQ" = ( /obj/structure/alien/weeds, @@ -949,7 +949,7 @@ "FP" = ( /obj/effect/decal/cleanable/blood, /obj/item/clothing/suit/armor/vest/marine/sulaco, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "FQ" = ( /obj/effect/turf_decal/tile/red{ @@ -972,7 +972,7 @@ "GL" = ( /obj/effect/mapping_helpers/burnt_floor, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "Ha" = ( /obj/structure/sign/poster/official/terragov/directional/east, @@ -1166,7 +1166,7 @@ /obj/effect/decal/cleanable/glass, /obj/structure/cable, /obj/machinery/light/floor/broken, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "LW" = ( /obj/structure/cable, @@ -1228,7 +1228,7 @@ /obj/machinery/photocopier/prebuilt, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "Ne" = ( /obj/item/stack/tile/iron/smooth_half, @@ -1351,7 +1351,7 @@ /area/ruin/space/has_grav/derelictsulaco) "QP" = ( /obj/structure/barricade/sandbags, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "RA" = ( /obj/machinery/door/airlock/command/glass{ @@ -1391,7 +1391,7 @@ /obj/machinery/door/window/brigdoor/left/directional/south, /obj/effect/decal/cleanable/dirt, /obj/structure/cable, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "SD" = ( /obj/machinery/camera/directional/west{ @@ -1547,13 +1547,13 @@ dir = 8 }, /obj/structure/cable, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "WM" = ( /obj/structure/chair/office{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "WN" = ( /turf/closed/wall/r_wall, @@ -1576,7 +1576,7 @@ /obj/machinery/power/floodlight{ anchored = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "Xu" = ( /obj/structure/lattice, @@ -1614,7 +1614,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/layer2{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "Yn" = ( /obj/structure/alien/resin/wall, @@ -1665,7 +1665,7 @@ dir = 1 }, /obj/structure/cable, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/derelictsulaco) "ZZ" = ( /obj/structure/lattice, diff --git a/_maps/RandomRuins/SpaceRuins/film_studio.dmm b/_maps/RandomRuins/SpaceRuins/film_studio.dmm index c426b7d2bb02..815804958398 100644 --- a/_maps/RandomRuins/SpaceRuins/film_studio.dmm +++ b/_maps/RandomRuins/SpaceRuins/film_studio.dmm @@ -129,7 +129,7 @@ /area/ruin/space/has_grav/film_studio/stage) "cC" = ( /obj/machinery/door/window/right/directional/west, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "cG" = ( /obj/effect/spawner/random/trash, @@ -860,7 +860,7 @@ /obj/structure/window/reinforced/spawner/directional/south, /obj/item/storage/box/papersack, /obj/structure/window/reinforced/spawner/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "qE" = ( /obj/item/storage/medkit/ancient, @@ -877,7 +877,7 @@ /obj/structure/filingcabinet/white, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/window/reinforced/spawner/directional/west, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "qM" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ @@ -950,7 +950,7 @@ "sj" = ( /obj/machinery/computer/old, /obj/structure/window/reinforced/spawner/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "sm" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, @@ -969,7 +969,7 @@ /obj/structure/chair/office{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "sK" = ( /obj/structure/table, @@ -1276,7 +1276,7 @@ pixel_y = 6 }, /obj/structure/window/reinforced/spawner/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "xz" = ( /obj/machinery/fax{ @@ -1317,7 +1317,7 @@ dir = 8 }, /obj/structure/window/reinforced/spawner/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "yr" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, @@ -1373,7 +1373,7 @@ /obj/structure/table/reinforced, /obj/structure/window/reinforced/spawner/directional/north, /obj/structure/window/reinforced/spawner/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "zL" = ( /turf/open/floor/iron/dark, @@ -1401,7 +1401,7 @@ dir = 4 }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "Af" = ( /obj/item/kirbyplants/random, @@ -1422,7 +1422,7 @@ /area/ruin/space/has_grav/film_studio/dorms) "Av" = ( /obj/machinery/door/window/left/directional/west, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "AB" = ( /obj/effect/turf_decal/siding/wood{ @@ -1774,7 +1774,7 @@ /obj/structure/table/reinforced, /obj/structure/window/reinforced/spawner/directional/south, /obj/item/modular_computer/laptop/preset/civilian, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "Fo" = ( /obj/structure/closet/crate{ @@ -2150,7 +2150,7 @@ /obj/item/storage/box/papersack, /obj/structure/window/reinforced/spawner/directional/west, /obj/structure/window/reinforced/spawner/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "LD" = ( /turf/open/floor/iron/white/smooth_half{ @@ -2444,7 +2444,7 @@ /turf/open/floor/plating, /area/ruin/space/has_grav/film_studio/solars) "PS" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "Qe" = ( /obj/structure/table, @@ -2499,7 +2499,7 @@ /obj/machinery/light/floor, /obj/item/paper_bin, /obj/structure/window/reinforced/spawner/directional/west, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "QW" = ( /obj/machinery/door/airlock/maintenance{ @@ -2600,7 +2600,7 @@ }, /obj/machinery/door/window/elevator/right/directional/east, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "SE" = ( /obj/structure/table, @@ -2625,7 +2625,7 @@ "Te" = ( /obj/structure/filingcabinet/white, /obj/structure/window/reinforced/spawner/directional/west, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "Ti" = ( /obj/structure/table/reinforced, @@ -2791,7 +2791,7 @@ "VU" = ( /obj/structure/table/reinforced, /obj/machinery/door/window/left/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/ruin/space/has_grav/film_studio/starboard) "We" = ( /obj/effect/turf_decal/siding/thinplating_new/terracotta{ diff --git a/_maps/RandomRuins/SpaceRuins/forgottenship.dmm b/_maps/RandomRuins/SpaceRuins/forgottenship.dmm index 1544232c33d2..2567a7da08b0 100644 --- a/_maps/RandomRuins/SpaceRuins/forgottenship.dmm +++ b/_maps/RandomRuins/SpaceRuins/forgottenship.dmm @@ -218,7 +218,7 @@ }, /obj/item/stack/sheet/glass/fifty, /obj/item/stack/rods/fifty, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/powered/syndicate_forgotten_vault) "be" = ( /obj/structure/cable, @@ -374,7 +374,7 @@ }, /obj/item/clothing/head/helmet/space/syndicate/black/engie, /obj/item/clothing/suit/space/syndicate/black/engie, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/powered/syndicate_forgotten_vault) "bC" = ( /obj/structure/closet/crate/medical, @@ -650,7 +650,7 @@ /obj/item/stack/sheet/mineral/uranium{ amount = 15 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/powered/syndicate_forgotten_vault) "cA" = ( /obj/structure/table/reinforced, @@ -855,10 +855,10 @@ req_access = list("syndicate") }, /obj/item/language_manual/codespeak_manual/unlimited, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/powered/syndicate_forgotten_vault) "de" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/powered/syndicate_forgotten_vault) "df" = ( /obj/structure/closet/syndicate{ @@ -908,7 +908,7 @@ req_access = list("syndicate") }, /obj/item/disk/surgery/forgottenship, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/powered/syndicate_forgotten_vault) "dj" = ( /turf/closed/mineral/random/high_chance, @@ -923,7 +923,7 @@ name = "Syndicate Vault Airlock" }, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/powered/syndicate_forgotten_vault) "dm" = ( /obj/structure/closet/crate/secure/gear{ @@ -952,7 +952,7 @@ amount = 30 }, /obj/machinery/light/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/powered/syndicate_forgotten_vault) "dp" = ( /obj/structure/closet/crate/secure/gear{ @@ -1140,7 +1140,7 @@ }, /obj/item/melee/energy/sword/saber/red, /obj/machinery/light/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/powered/syndicate_forgotten_vault) "IP" = ( /obj/structure/cable, diff --git a/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm b/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm index 96b778786af6..667a5167548d 100644 --- a/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm +++ b/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm @@ -12,7 +12,7 @@ }, /obj/structure/barricade/security, /obj/structure/alien/resin/membrane, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "aA" = ( /obj/structure/table_frame, @@ -77,7 +77,7 @@ /obj/effect/mob_spawn/corpse/human/syndicatecommando/lessenedgear{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "be" = ( /obj/structure/broken_flooring/plating/directional/east, @@ -100,7 +100,7 @@ }, /obj/structure/cable, /obj/machinery/suit_storage_unit/open, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "bJ" = ( /obj/structure/cable, @@ -160,7 +160,7 @@ /obj/item/radio{ pixel_x = 5 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "cA" = ( /obj/effect/turf_decal{ @@ -268,7 +268,7 @@ dir = 1 }, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "dq" = ( /obj/effect/decal/cleanable/glass, @@ -385,7 +385,7 @@ name = "STERILIZATION ORDERS"; desc = "These are particularly boring for what they entail." }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "eZ" = ( /obj/item/clothing/mask/facehugger/impregnated, @@ -576,7 +576,7 @@ icon_state = "warningline_red"; dir = 9 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "ip" = ( /obj/structure/alien/egg/burst, @@ -649,7 +649,7 @@ name = "syndicate navigation console" }, /obj/effect/decal/cleanable/glass, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "kc" = ( /obj/effect/turf_decal{ @@ -825,7 +825,7 @@ /obj/structure/closet/syndicate, /obj/structure/cable, /obj/item/gun/syringe, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "nm" = ( /obj/machinery/vending/cigarette/syndicate, @@ -833,7 +833,7 @@ icon_state = "warningline_white"; dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "nG" = ( /mob/living/basic/alien{ @@ -921,7 +921,7 @@ /obj/structure/sign/warning/fire/directional/east, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, /obj/machinery/light/broken/directional/east, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "pl" = ( /obj/item/shard, @@ -974,7 +974,7 @@ name = "prisoner isolation shutter"; id = "captivity" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "qC" = ( /obj/effect/turf_decal/tile/bar{ @@ -1098,7 +1098,7 @@ }, /obj/item/clothing/head/utility/welding, /obj/effect/decal/cleanable/plastic, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "rO" = ( /obj/structure/broken_flooring/pile/directional/south, @@ -1130,7 +1130,7 @@ /area/ruin/space/has_grav/infested_frigate) "sE" = ( /obj/machinery/door/airlock/hatch, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "sQ" = ( /obj/structure/cable, @@ -1190,7 +1190,7 @@ }, /obj/item/ammo_casing/spent, /obj/effect/spawner/random/entertainment/arcade, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "uy" = ( /obj/structure/cable, @@ -1202,7 +1202,7 @@ dir = 8 }, /obj/machinery/vending/medical/infested_frigate, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "uC" = ( /obj/effect/turf_decal, @@ -1224,7 +1224,7 @@ /mob/living/basic/alien{ faction = list("syndicate","xenomorph") }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "vj" = ( /obj/machinery/door/poddoor{ @@ -1256,7 +1256,7 @@ pixel_x = -8 }, /obj/structure/table/glass/plasmaglass, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "vx" = ( /obj/structure/cable, @@ -1303,7 +1303,7 @@ /obj/machinery/computer/operating{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "wc" = ( /obj/structure/cable, @@ -1398,7 +1398,7 @@ /obj/item/ammo_casing/spent, /obj/item/ammo_casing/spent, /obj/effect/mob_spawn/corpse/human/doctor, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "wE" = ( /obj/machinery/vending/donksofttoyvendor, @@ -1406,7 +1406,7 @@ icon_state = "warningline_white" }, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "wJ" = ( /obj/effect/turf_decal{ @@ -1430,7 +1430,7 @@ /obj/structure/sign/poster/contraband/random/directional/south, /obj/structure/cable, /obj/structure/alien/weeds, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "wP" = ( /turf/closed/wall/mineral/plastitanium, @@ -1597,7 +1597,7 @@ minimum_growtime = 120000; growtime = 150000 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "yP" = ( /obj/item/storage/belt/military, @@ -1662,7 +1662,7 @@ icon_state = "warningline_white" }, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "Ah" = ( /obj/effect/mob_spawn/corpse/human/syndicatepilot, @@ -1808,7 +1808,7 @@ /obj/item/clothing/under/syndicate/combat, /obj/item/clothing/shoes/combat, /obj/item/clothing/shoes/combat, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "CE" = ( /obj/effect/mapping_helpers/broken_floor, @@ -1899,7 +1899,7 @@ pixel_x = 8; pixel_y = 5 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "EQ" = ( /obj/structure/sign/poster/contraband/random/directional/north, @@ -1977,7 +1977,7 @@ dir = 4 }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "FJ" = ( /obj/machinery/suit_storage_unit/open, @@ -1988,7 +1988,7 @@ icon_state = "warningline_white" }, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "FP" = ( /obj/effect/mapping_helpers/airlock/locked, @@ -2022,7 +2022,7 @@ /obj/item/food/burger/xeno, /obj/structure/cable, /obj/effect/spawner/random/trash, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "Hc" = ( /obj/effect/decal/cleanable/blood/gibs{ @@ -2122,7 +2122,7 @@ /obj/machinery/vending/cola/black{ icon_state = "cola_black-broken" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "IN" = ( /obj/item/shard{ @@ -2165,7 +2165,7 @@ icon_state = "warningline_white" }, /obj/machinery/newscaster/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "Jw" = ( /obj/effect/spawner/random/exotic/antag_gear_weak{ @@ -2178,7 +2178,7 @@ /obj/effect/spawner/random/exotic/antag_gear, /obj/structure/table/glass/plasmaglass, /obj/effect/spawner/random/contraband/permabrig_gear, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "Jx" = ( /mob/living/basic/alien{ @@ -2271,7 +2271,7 @@ /obj/item/ammo_casing/spent, /obj/effect/decal/cleanable/glass, /obj/effect/decal/cleanable/plastic, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "KG" = ( /obj/structure/cable, @@ -2330,7 +2330,7 @@ /obj/effect/decal/cleanable/glass, /obj/item/ammo_casing/spent, /obj/structure/alien/weeds, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "LF" = ( /obj/structure/cable, @@ -2444,7 +2444,7 @@ dir = 1 }, /obj/effect/spawner/random/exotic/antag_gear, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "MW" = ( /obj/effect/turf_decal{ @@ -2482,7 +2482,7 @@ dir = 4 }, /obj/machinery/computer/operating, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "NX" = ( /obj/structure/table/glass, @@ -2511,7 +2511,7 @@ req_access = list("syndicate") }, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "Or" = ( /obj/item/stack/rods{ @@ -2687,7 +2687,7 @@ icon_state = "warningline_white"; dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "RG" = ( /obj/effect/turf_decal/tile/bar{ @@ -2746,7 +2746,7 @@ pixel_y = -1 }, /obj/structure/table/glass/plasmaglass, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "SB" = ( /obj/machinery/power/shuttle_engine/large{ @@ -2778,7 +2778,7 @@ /obj/item/ammo_casing/spent, /obj/item/ammo_casing/spent, /obj/item/ammo_casing/spent, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "SM" = ( /obj/structure/cable, @@ -2807,7 +2807,7 @@ dir = 4 }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "SX" = ( /obj/effect/decal/cleanable/dirt, @@ -2863,7 +2863,7 @@ icon_state = "gib6-old"; dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "Ux" = ( /obj/effect/turf_decal/tile/bar{ @@ -2932,7 +2932,7 @@ icon_state = "warningline_white" }, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "VR" = ( /obj/item/tank/internals/oxygen/empty, @@ -3075,7 +3075,7 @@ icon_state = "warningline_white" }, /obj/item/kirbyplants/random, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "YW" = ( /obj/effect/turf_decal/tile/bar{ @@ -3106,7 +3106,7 @@ dir = 1 }, /obj/effect/spawner/random/medical/minor_healing, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/infested_frigate) "Ze" = ( /obj/item/shard{ diff --git a/_maps/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/RandomRuins/SpaceRuins/oldstation.dmm index 85a4b3f82bb1..257fcfba689f 100644 --- a/_maps/RandomRuins/SpaceRuins/oldstation.dmm +++ b/_maps/RandomRuins/SpaceRuins/oldstation.dmm @@ -5105,7 +5105,7 @@ /obj/machinery/shower/directional/west{ has_water_reclaimer = 0 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/ancientstation/delta/biolab) "BS" = ( /obj/effect/decal/cleanable/dirt, diff --git a/_maps/RandomRuins/SpaceRuins/vaporwave.dmm b/_maps/RandomRuins/SpaceRuins/vaporwave.dmm index 830bd7508ec9..2e4b00ff7f2d 100644 --- a/_maps/RandomRuins/SpaceRuins/vaporwave.dmm +++ b/_maps/RandomRuins/SpaceRuins/vaporwave.dmm @@ -102,7 +102,7 @@ "z" = ( /obj/structure/fans/tiny, /obj/machinery/door/airlock/hatch, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/ruin/space/has_grav/powered/aesthetic) "A" = ( /obj/effect/turf_decal/sand, diff --git a/_maps/RandomZLevels/TheBeach.dmm b/_maps/RandomZLevels/TheBeach.dmm index 3b71facaec68..c0ac8f3f84fc 100644 --- a/_maps/RandomZLevels/TheBeach.dmm +++ b/_maps/RandomZLevels/TheBeach.dmm @@ -3842,7 +3842,7 @@ density = 1 }, /obj/effect/decal/fakelattice, -/turf/open/floor/pod/light{ +/turf/open/floor/mineral/plastitanium/pod/light{ density = 1 }, /area/awaymission/beach) diff --git a/_maps/RandomZLevels/heretic.dmm b/_maps/RandomZLevels/heretic.dmm index 619ff9c739f5..86b568994eb6 100644 --- a/_maps/RandomZLevels/heretic.dmm +++ b/_maps/RandomZLevels/heretic.dmm @@ -139,7 +139,7 @@ /area/awaymission/caves/heretic_laboratory) "aK" = ( /obj/structure/chair/comfy/shuttle, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "aL" = ( /obj/structure/fluff/tram_rail{ @@ -210,7 +210,7 @@ /obj/machinery/computer/records/security{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/beach/heretic) "bc" = ( /obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, @@ -1243,7 +1243,7 @@ "gC" = ( /obj/structure/table, /obj/machinery/recharger, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "gF" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4, @@ -1303,7 +1303,7 @@ /obj/machinery/computer/crew{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/beach/heretic) "gU" = ( /obj/machinery/conveyor/auto{ @@ -1485,7 +1485,7 @@ /obj/structure/chair/comfy/shuttle{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "hM" = ( /obj/machinery/door/airlock{ @@ -2458,7 +2458,7 @@ /obj/structure/table/reinforced, /obj/item/storage/medkit/toxin, /obj/item/storage/medkit/toxin, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "na" = ( /obj/structure/flora/bush/jungle/a/style_random, @@ -3024,7 +3024,7 @@ /obj/structure/table/reinforced, /obj/item/storage/medkit, /obj/item/storage/medkit, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "pH" = ( /obj/structure/fluff/tram_rail/anchor, @@ -3383,7 +3383,7 @@ /obj/structure/table/reinforced, /obj/item/storage/medkit/brute, /obj/item/storage/medkit/brute, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/beach/heretic) "rC" = ( /obj/structure/chair/wood{ @@ -3498,7 +3498,7 @@ /turf/open/floor/black, /area/awaymission/caves/heretic_laboratory_clean) "se" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "sf" = ( /obj/machinery/vending/donksofttoyvendor, @@ -4364,7 +4364,7 @@ /area/awaymission/caves/heretic_laboratory) "wF" = ( /obj/machinery/suit_storage_unit, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "wH" = ( /obj/structure/table/glass, @@ -4766,7 +4766,7 @@ "yk" = ( /obj/structure/curtain/cloth/fancy/mechanical/start_closed, /obj/effect/spawner/structure/window/reinforced/shuttle, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/beach/heretic) "yo" = ( /obj/structure/disposalpipe/segment, @@ -5270,7 +5270,7 @@ /area/awaymission/caves/heretic_laboratory) "Be" = ( /obj/machinery/gateway, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "Bf" = ( /obj/item/clothing/head/collectable/welding, @@ -5391,7 +5391,7 @@ /turf/open/floor/engine, /area/awaymission/caves/heretic_laboratory) "BV" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/beach/heretic) "BW" = ( /obj/machinery/door/airlock/highsecurity{ @@ -6321,7 +6321,7 @@ /area/awaymission/caves/heretic_laboratory_clean) "GD" = ( /obj/structure/rack, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "GE" = ( /obj/machinery/light/directional/east, @@ -6908,7 +6908,7 @@ /obj/structure/chair/comfy/shuttle{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "Jy" = ( /obj/effect/mob_spawn/corpse/human/old_pirate_captain, @@ -7092,7 +7092,7 @@ /obj/item/storage/toolbox/emergency, /obj/item/clothing/head/utility/welding, /obj/item/clothing/head/utility/welding, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "Km" = ( /obj/machinery/light/directional/east, @@ -7263,7 +7263,7 @@ /area/awaymission/caves/heretic_laboratory) "Ln" = ( /obj/machinery/door/airlock/titanium, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "Lo" = ( /obj/item/paper_bin, @@ -7868,7 +7868,7 @@ "OP" = ( /obj/machinery/light/directional/north, /obj/structure/chair/comfy/shuttle, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/beach/heretic) "OQ" = ( /obj/machinery/vending/boozeomat, @@ -7939,7 +7939,7 @@ /area/awaymission/caves/heretic_laboratory) "Ph" = ( /obj/machinery/light/small/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/beach/heretic) "Pj" = ( /obj/machinery/light/directional/south, @@ -8026,7 +8026,7 @@ "PK" = ( /obj/machinery/door/airlock/titanium, /obj/effect/mapping_helpers/airlock/locked, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/beach/heretic) "PL" = ( /obj/structure/window/spawner/directional/east, diff --git a/_maps/RandomZLevels/moonoutpost19.dmm b/_maps/RandomZLevels/moonoutpost19.dmm index 232f152d0583..8ff6799f9d48 100644 --- a/_maps/RandomZLevels/moonoutpost19.dmm +++ b/_maps/RandomZLevels/moonoutpost19.dmm @@ -162,7 +162,7 @@ desc = "Second place prize for excellence in archaeology. Wonder who got first place?"; name = "silver trophy (second place)" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/moonoutpost19/tent) "aQ" = ( /obj/structure/extinguisher_cabinet/directional/west, @@ -2475,7 +2475,7 @@ /obj/structure/fluff/paper/corner, /obj/structure/cable, /obj/machinery/power/apc/auto_name/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/moonoutpost19/tent) "ra" = ( /obj/effect/decal/cleanable/dirt, @@ -3130,7 +3130,7 @@ /obj/structure/closet/crate/bin, /obj/item/food/badrecipe/moldy/bacteria, /obj/structure/fluff/paper, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/moonoutpost19/tent) "vk" = ( /obj/structure/extinguisher_cabinet/directional/north, @@ -3269,7 +3269,7 @@ /obj/item/stock_parts/power_store/cell/crap/empty{ pixel_x = -4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/moonoutpost19/tent) "wh" = ( /obj/structure/alien/weeds/node, @@ -3362,7 +3362,7 @@ /area/awaymission/moonoutpost19/arrivals) "xa" = ( /obj/machinery/light/cold/directional/west, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/moonoutpost19/tent) "xb" = ( /obj/structure/dresser, @@ -3813,7 +3813,7 @@ /turf/open/misc/asteroid/moon, /area/awaymission/moonoutpost19/mines) "Au" = ( -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/awaymission/moonoutpost19/tent) "Ay" = ( /obj/machinery/door/firedoor, @@ -3970,7 +3970,7 @@ /area/awaymission/moonoutpost19/arrivals/shed) "Bq" = ( /obj/structure/filingcabinet/chestdrawer, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/moonoutpost19/tent) "Bs" = ( /obj/machinery/door/airlock/research/glass{ @@ -4017,7 +4017,7 @@ "BE" = ( /obj/structure/cable, /obj/effect/spawner/structure/window/survival_pod, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/moonoutpost19/tent) "BG" = ( /obj/effect/decal/cleanable/blood/old, @@ -4214,7 +4214,7 @@ /area/awaymission/moonoutpost19/main) "CP" = ( /obj/structure/cable, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/moonoutpost19/tent) "CU" = ( /obj/structure/fluff/minepost, @@ -4810,7 +4810,7 @@ "HJ" = ( /obj/structure/safe, /obj/item/camera, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/moonoutpost19/tent) "HN" = ( /obj/item/stack/rods, @@ -4988,10 +4988,10 @@ /obj/machinery/door/airlock/survival_pod/glass, /obj/structure/fans/tiny, /obj/effect/turf_decal/lunar_sand/plating, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/moonoutpost19/tent) "IP" = ( -/turf/closed/wall/mineral/titanium/survival, +/turf/closed/wall/mineral/plastitanium/survival, /area/awaymission/moonoutpost19/tent) "IW" = ( /obj/effect/turf_decal/stripes/corner{ @@ -6011,7 +6011,7 @@ /area/awaymission/moonoutpost19/main) "Qx" = ( /obj/effect/turf_decal/lunar_sand/plating, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/moonoutpost19/tent) "Qy" = ( /obj/effect/turf_decal/tile/red{ @@ -6559,7 +6559,7 @@ /obj/structure/chair/office{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/moonoutpost19/tent) "Uy" = ( /obj/structure/table, @@ -6750,7 +6750,7 @@ pixel_y = 4 }, /obj/machinery/cell_charger, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/moonoutpost19/tent) "Wg" = ( /obj/structure/flora/rock/style_random{ @@ -7198,7 +7198,7 @@ /turf/open/misc/asteroid/moon/dug, /area/awaymission/moonoutpost19/mines) "Zo" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/awaymission/moonoutpost19/tent) "Zp" = ( /obj/effect/decal/cleanable/cobweb, @@ -7327,7 +7327,7 @@ pixel_x = -6 }, /obj/item/pen/red, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/awaymission/moonoutpost19/tent) (1,1,1) = {" diff --git a/_maps/minigame/CTF/cruiser.dmm b/_maps/minigame/CTF/cruiser.dmm index 897ea48c860e..4297fcaf98bd 100644 --- a/_maps/minigame/CTF/cruiser.dmm +++ b/_maps/minigame/CTF/cruiser.dmm @@ -13,7 +13,7 @@ /obj/effect/turf_decal/tile/green{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "bN" = ( /obj/effect/turf_decal/siding/purple/corner{ @@ -23,7 +23,7 @@ /area/centcom/ctf) "cb" = ( /obj/effect/turf_decal/tile/green/anticorner/contrasted, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "ck" = ( /turf/closed/indestructible/syndicate, @@ -46,7 +46,7 @@ /obj/effect/turf_decal/tile/red/half/contrasted{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "cK" = ( /obj/effect/spawner/structure/window/reinforced/shuttle/indestructible, @@ -60,7 +60,7 @@ /obj/effect/turf_decal/tile/red{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "dt" = ( /obj/structure/window/reinforced/spawner/directional/north{ @@ -80,7 +80,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "es" = ( /obj/machinery/computer{ @@ -190,7 +190,7 @@ color = "#121212"; dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "lI" = ( /obj/machinery/power/shuttle_engine/heater, @@ -199,7 +199,7 @@ "lL" = ( /obj/structure/table/reinforced/ctf, /obj/effect/turf_decal/tile/red/anticorner/contrasted, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "lS" = ( /obj/structure/sign/warning/radiation/rad_area{ @@ -231,7 +231,7 @@ /area/centcom/ctf) "nj" = ( /obj/structure/barricade/security/ctf, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "nx" = ( /obj/machinery/computer{ @@ -256,7 +256,7 @@ /obj/effect/turf_decal/tile/green{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "oL" = ( /obj/structure/window/reinforced/spawner/directional/north{ @@ -287,14 +287,14 @@ /area/centcom/ctf) "sc" = ( /obj/effect/turf_decal/tile/red, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "sz" = ( /turf/open/floor/mineral/titanium/purple, /area/centcom/ctf) "tM" = ( /obj/effect/turf_decal/tile/red/fourcorners, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "tN" = ( /obj/structure/table/reinforced/ctf, @@ -316,7 +316,7 @@ /obj/effect/turf_decal/tile/red{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "uz" = ( /obj/structure/table/reinforced/ctf, @@ -332,20 +332,20 @@ /obj/effect/turf_decal/tile/green/anticorner/contrasted{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "vy" = ( /obj/effect/turf_decal/tile/red/anticorner/contrasted{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "vL" = ( /obj/effect/turf_decal/siding/white{ color = "#121212"; dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "vN" = ( /obj/effect/turf_decal/siding/purple{ @@ -361,16 +361,16 @@ /obj/effect/turf_decal/tile/red/anticorner/contrasted{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "wO" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "xl" = ( /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "xK" = ( /obj/structure/window/reinforced/spawner/directional/east, @@ -378,7 +378,7 @@ /area/centcom/ctf) "xS" = ( /obj/effect/turf_decal/tile/green/half/contrasted, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "xU" = ( /obj/machinery/computer{ @@ -391,13 +391,13 @@ color = "#121212"; dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "yn" = ( /obj/effect/turf_decal/siding/white/corner{ color = "#121212" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "yw" = ( /obj/effect/turf_decal/siding/white/corner{ @@ -418,7 +418,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "zs" = ( /obj/structure/barricade/security/ctf, @@ -449,7 +449,7 @@ color = "#121212"; dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "Bh" = ( /obj/effect/turf_decal/siding/blue{ @@ -507,7 +507,7 @@ /area/centcom/ctf) "FM" = ( /obj/effect/turf_decal/tile/red/half/contrasted, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "FP" = ( /obj/machinery/power/shuttle_engine/heater{ @@ -545,7 +545,7 @@ /obj/effect/turf_decal/tile/green/anticorner/contrasted{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "HN" = ( /obj/structure/window/reinforced/spawner/directional/west{ @@ -571,7 +571,7 @@ color = "#121212"; dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "IN" = ( /obj/effect/turf_decal/trimline/red/warning{ @@ -583,11 +583,11 @@ /obj/effect/turf_decal/tile/green{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "Jr" = ( /obj/effect/turf_decal/tile/green/fourcorners, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "Jt" = ( /turf/open/floor/mineral/titanium/tiled/white, @@ -609,7 +609,7 @@ /obj/effect/turf_decal/tile/red{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "KO" = ( /obj/machinery/power/shuttle_engine/heater, @@ -638,7 +638,7 @@ /obj/effect/turf_decal/tile/green/anticorner/contrasted{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "ML" = ( /obj/effect/turf_decal/siding/white/corner{ @@ -662,7 +662,7 @@ /obj/effect/turf_decal/tile/green/anticorner/contrasted{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "Ou" = ( /obj/effect/turf_decal/trimline/red/warning{ @@ -688,7 +688,7 @@ /area/centcom/ctf) "Pm" = ( /obj/effect/turf_decal/tile/red/anticorner/contrasted, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "Pn" = ( /obj/structure/trap/ctf/green, @@ -721,7 +721,7 @@ /obj/effect/turf_decal/tile/red/half/contrasted{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/centcom/ctf) "Ri" = ( /obj/structure/trap/ctf/red, @@ -738,19 +738,19 @@ /obj/effect/turf_decal/tile/red/anticorner/contrasted{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "RH" = ( /obj/effect/turf_decal/tile/red/half/contrasted{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "RT" = ( /obj/effect/turf_decal/tile/green/anticorner/contrasted{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "RV" = ( /turf/open/floor/mineral/titanium/tiled, @@ -770,7 +770,7 @@ /turf/open/floor/mineral/titanium/blue, /area/centcom/ctf) "Us" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "Vl" = ( /obj/effect/turf_decal/siding/purple/corner, @@ -799,7 +799,7 @@ /obj/effect/turf_decal/tile/red/anticorner/contrasted{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "WB" = ( /obj/effect/turf_decal/trimline/green/warning{ @@ -825,7 +825,7 @@ /area/centcom/ctf) "Zf" = ( /obj/effect/turf_decal/tile/green, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/centcom/ctf) "Zk" = ( /obj/machinery/computer{ diff --git a/_maps/minigame/deathmatch/secu_ring.dmm b/_maps/minigame/deathmatch/secu_ring.dmm index 615103813faa..3e8263ed19dd 100644 --- a/_maps/minigame/deathmatch/secu_ring.dmm +++ b/_maps/minigame/deathmatch/secu_ring.dmm @@ -13,41 +13,41 @@ /turf/open/floor/mineral/plastitanium/red, /area/deathmatch) "n" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/deathmatch) "o" = ( /obj/machinery/recharger, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/deathmatch) "t" = ( /obj/structure/barricade/security/ctf, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/deathmatch) "u" = ( -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/deathmatch) "G" = ( /obj/structure/barricade/security/ctf, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/deathmatch) "H" = ( /obj/machinery/light{ dir = 8 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/deathmatch) "I" = ( /turf/closed/indestructible/reinforced, /area/deathmatch) "M" = ( /obj/machinery/light, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/deathmatch) "N" = ( /obj/machinery/light{ dir = 4 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/deathmatch) "O" = ( /obj/machinery/light{ @@ -59,7 +59,7 @@ /obj/machinery/light{ dir = 1 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/deathmatch) "S" = ( /obj/machinery/light{ @@ -72,7 +72,7 @@ /turf/open/floor/mineral/plastitanium/red, /area/deathmatch) "W" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/deathmatch) "X" = ( /turf/open/floor/mineral/plastitanium/red, diff --git a/_maps/safehouses/den.dmm b/_maps/safehouses/den.dmm index 9b0b37f53515..f989fcec14b4 100644 --- a/_maps/safehouses/den.dmm +++ b/_maps/safehouses/den.dmm @@ -5,7 +5,7 @@ }, /obj/effect/decal/cleanable/dirt/dust, /obj/machinery/light/small/directional/south, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/virtual_domain/safehouse) "b" = ( /obj/modular_map_connector, @@ -26,7 +26,7 @@ pixel_x = 4; pixel_y = 2 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/safehouse) "i" = ( /obj/effect/spawner/structure/window/reinforced, @@ -40,16 +40,16 @@ /obj/structure/railing/corner/end{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/safehouse) "p" = ( -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/virtual_domain/safehouse) "r" = ( /obj/effect/turf_decal/trimline/yellow/line{ dir = 9 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/safehouse) "u" = ( /obj/structure/railing, @@ -66,7 +66,7 @@ dir = 4 }, /obj/effect/landmark/bitrunning/hololadder_spawn, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/safehouse) "C" = ( /turf/closed/wall, @@ -79,7 +79,7 @@ /obj/effect/turf_decal/trimline/yellow/corner{ dir = 1 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/virtual_domain/safehouse) "E" = ( /obj/effect/turf_decal/trimline/yellow/line{ @@ -87,7 +87,7 @@ }, /obj/effect/decal/cleanable/generic, /obj/effect/landmark/bitrunning/hololadder_spawn, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/safehouse) "G" = ( /obj/effect/spawner/structure/window/reinforced, @@ -102,7 +102,7 @@ dir = 5 }, /obj/effect/landmark/bitrunning/hololadder_spawn, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/safehouse) "J" = ( /obj/effect/spawner/structure/window/reinforced, @@ -115,28 +115,28 @@ "K" = ( /obj/effect/spawner/random/vending/colavend, /obj/machinery/light/small/directional/south, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/virtual_domain/safehouse) "M" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/safehouse) "O" = ( /obj/effect/turf_decal/trimline/yellow/line{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/safehouse) "R" = ( /obj/effect/turf_decal/trimline/yellow/line{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/safehouse) "U" = ( /obj/effect/decal/cleanable/dirt/dust, /obj/structure/sign/departments/cargo/directional/west, /obj/machinery/light/small/directional/west, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/virtual_domain/safehouse) "W" = ( /obj/effect/spawner/structure/window/reinforced, @@ -161,13 +161,13 @@ pixel_y = 6; pixel_x = -10 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/safehouse) "Z" = ( /obj/effect/turf_decal/trimline/yellow/warning{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/safehouse) (1,1,1) = {" diff --git a/_maps/safehouses/ice.dmm b/_maps/safehouses/ice.dmm index aedeaba8c642..aa2e366c6fb0 100644 --- a/_maps/safehouses/ice.dmm +++ b/_maps/safehouses/ice.dmm @@ -40,7 +40,7 @@ dir = 1 }, /obj/effect/landmark/bitrunning/hololadder_spawn, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/virtual_domain/safehouse) "n" = ( /obj/effect/turf_decal/weather/snow/corner{ @@ -82,7 +82,7 @@ dir = 5 }, /obj/effect/landmark/bitrunning/hololadder_spawn, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/virtual_domain/safehouse) "x" = ( /obj/effect/turf_decal/weather/snow/corner{ @@ -136,7 +136,7 @@ dir = 9 }, /obj/effect/landmark/bitrunning/hololadder_spawn, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/virtual_domain/safehouse) "I" = ( /obj/effect/decal/cleanable/dirt/dust, diff --git a/_maps/shuttles/arrival/arrival_northstar.dmm b/_maps/shuttles/arrival/arrival_northstar.dmm index fadde8f9df42..689074884390 100644 --- a/_maps/shuttles/arrival/arrival_northstar.dmm +++ b/_maps/shuttles/arrival/arrival_northstar.dmm @@ -25,7 +25,7 @@ pixel_y = 4 }, /obj/item/storage/toolbox/emergency, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "g" = ( /obj/machinery/power/shuttle_engine/propulsion/burst/right{ @@ -35,7 +35,7 @@ /area/shuttle/arrival) "j" = ( /obj/structure/closet/firecloset/full, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "k" = ( /obj/structure/table/reinforced/plastitaniumglass, @@ -44,43 +44,43 @@ /obj/item/clothing/mask/breath, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/emergency_oxygen, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "n" = ( /obj/effect/turf_decal/trimline/red/warning{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "q" = ( /obj/structure/closet/wardrobe/grey, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "r" = ( /obj/effect/turf_decal/trimline/red/warning{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "s" = ( /obj/effect/turf_decal/trimline/red/corner{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "t" = ( -/turf/closed/wall/mineral/titanium/survival, +/turf/closed/wall/mineral/plastitanium/survival, /area/shuttle/arrival) "u" = ( /obj/effect/turf_decal/trimline/red/corner, /obj/effect/turf_decal/trimline/red/corner{ dir = 8 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/arrival) "v" = ( /obj/structure/closet/wardrobe/mixed, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "w" = ( /obj/machinery/power/shuttle_engine/propulsion/burst/left{ @@ -92,14 +92,14 @@ /obj/effect/turf_decal/trimline/red/corner{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "z" = ( /obj/structure/extinguisher_cabinet/directional/south, /obj/effect/turf_decal/trimline/red/warning{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "A" = ( /obj/effect/turf_decal/trimline/red/warning{ @@ -108,7 +108,7 @@ /obj/effect/turf_decal/trimline/red/warning{ dir = 4 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/arrival) "C" = ( /obj/effect/turf_decal/trimline/red/corner{ @@ -117,7 +117,7 @@ /obj/effect/turf_decal/trimline/red/corner{ dir = 1 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/arrival) "D" = ( /obj/machinery/power/shuttle_engine/propulsion{ @@ -132,7 +132,7 @@ /obj/effect/turf_decal/trimline/red/warning{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "F" = ( /obj/structure/chair/comfy/shuttle, @@ -150,7 +150,7 @@ dir = 1 }, /obj/machinery/light/red/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "L" = ( /obj/machinery/power/shuttle_engine/heater{ @@ -163,7 +163,7 @@ /obj/effect/turf_decal/trimline/red/corner{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "N" = ( /obj/structure/chair/comfy/shuttle, @@ -171,7 +171,7 @@ /area/shuttle/arrival) "P" = ( /obj/effect/turf_decal/trimline/red/warning, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/arrival) "V" = ( /obj/structure/chair/comfy/shuttle, @@ -180,7 +180,7 @@ /area/shuttle/arrival) "W" = ( /obj/effect/turf_decal/trimline/red/corner, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/arrival) "Z" = ( /obj/effect/spawner/structure/window/survival_pod, diff --git a/_maps/shuttles/cargo/cargo_catwalk.dmm b/_maps/shuttles/cargo/cargo_catwalk.dmm index 7136321584f2..a8fd77f5ab9f 100644 --- a/_maps/shuttles/cargo/cargo_catwalk.dmm +++ b/_maps/shuttles/cargo/cargo_catwalk.dmm @@ -1,6 +1,6 @@ //MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE "a" = ( -/turf/closed/wall/mineral/titanium/survival, +/turf/closed/wall/mineral/plastitanium/survival, /area/shuttle/supply) "c" = ( /obj/machinery/light/directional/east, @@ -49,7 +49,7 @@ /turf/open/floor/iron/smooth_large, /area/shuttle/supply) "p" = ( -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/shuttle/supply) "r" = ( /obj/structure/railing, diff --git a/_maps/shuttles/cargo/cargo_northstar.dmm b/_maps/shuttles/cargo/cargo_northstar.dmm index 59232e5a1e5b..d28eaaab1365 100644 --- a/_maps/shuttles/cargo/cargo_northstar.dmm +++ b/_maps/shuttles/cargo/cargo_northstar.dmm @@ -53,10 +53,10 @@ /turf/open/floor/catwalk_floor/iron_smooth, /area/shuttle/supply) "h" = ( -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/shuttle/supply) "i" = ( -/turf/closed/wall/mineral/titanium/survival, +/turf/closed/wall/mineral/plastitanium/survival, /area/shuttle/supply) "j" = ( /obj/machinery/conveyor{ diff --git a/_maps/shuttles/emergency/emergency_northstar.dmm b/_maps/shuttles/emergency/emergency_northstar.dmm index 0bb628683fb6..014d73088549 100644 --- a/_maps/shuttles/emergency/emergency_northstar.dmm +++ b/_maps/shuttles/emergency/emergency_northstar.dmm @@ -233,7 +233,7 @@ name = "Emergency Shuttle Bridge" }, /obj/effect/mapping_helpers/airlock/access/all/command/general, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/escape) "uy" = ( /obj/machinery/door/airlock/survival_pod/glass{ @@ -249,7 +249,7 @@ /turf/open/floor/catwalk_floor/iron_dark, /area/shuttle/escape) "vO" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/escape) "wy" = ( /turf/open/floor/mineral/plastitanium/red, @@ -277,7 +277,7 @@ /turf/open/floor/plating, /area/shuttle/escape) "AW" = ( -/turf/closed/wall/mineral/titanium/survival, +/turf/closed/wall/mineral/plastitanium/survival, /area/shuttle/escape) "Bk" = ( /obj/structure/chair/comfy/shuttle{ @@ -295,7 +295,7 @@ /obj/structure/railing/corner{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/escape) "CO" = ( /turf/open/floor/catwalk_floor/iron_smooth, @@ -360,7 +360,7 @@ /turf/template_noop, /area/template_noop) "FQ" = ( -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/shuttle/escape) "HE" = ( /obj/structure/chair/comfy/shuttle, @@ -397,7 +397,7 @@ /obj/structure/railing/corner{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/escape) "JC" = ( /obj/structure/railing{ @@ -427,7 +427,7 @@ /obj/structure/railing/corner{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/escape) "NR" = ( /obj/effect/turf_decal/trimline/white/filled/line{ @@ -460,7 +460,7 @@ "Pa" = ( /obj/machinery/status_display/evac/directional/east, /obj/machinery/light/red/directional/east, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/escape) "Qo" = ( /obj/structure/chair/comfy/shuttle{ @@ -480,7 +480,7 @@ }, /area/shuttle/escape) "Qw" = ( -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/shuttle/escape) "QF" = ( /obj/structure/railing{ diff --git a/_maps/shuttles/ert_bounty.dmm b/_maps/shuttles/ert_bounty.dmm index b1fbf895fa58..991d43161b25 100644 --- a/_maps/shuttles/ert_bounty.dmm +++ b/_maps/shuttles/ert_bounty.dmm @@ -23,7 +23,7 @@ preferred_direction = 4 }, /obj/structure/fans/tiny, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "d" = ( /obj/structure/table, @@ -35,7 +35,7 @@ pixel_x = 4; pixel_y = -4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "f" = ( /obj/structure/table, @@ -51,7 +51,7 @@ pixel_x = -6; pixel_y = -7 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "h" = ( /obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, @@ -60,11 +60,11 @@ "i" = ( /obj/structure/tank_dispenser/oxygen, /obj/effect/turf_decal/stripes/full, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "j" = ( /obj/structure/curtain/bounty, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "k" = ( /obj/structure/table/reinforced, @@ -76,7 +76,7 @@ pixel_x = 8; pixel_y = 14 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "l" = ( /obj/machinery/power/shuttle_engine/propulsion{ @@ -90,7 +90,7 @@ }, /obj/effect/mapping_helpers/airlock/cyclelink_helper, /obj/machinery/door/airlock/external/ruin, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "n" = ( /turf/closed/wall/mineral/plastitanium, @@ -99,39 +99,39 @@ /obj/effect/turf_decal/stripes/line{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "q" = ( /obj/structure/closet/crate/bin, /obj/item/grenade/chem_grenade/glitter/pink, /obj/item/trash/can, /obj/item/cigbutt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "r" = ( /obj/structure/chair/office{ dir = 8 }, /obj/effect/landmark/ert_shuttle_spawn, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "s" = ( /obj/machinery/computer/shuttle/hunter{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "t" = ( /obj/machinery/suit_storage_unit/standard_unit{ storage_type = /obj/item/tank/internals/oxygen/yellow }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "u" = ( /obj/structure/chair/comfy/shuttle{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "v" = ( /obj/machinery/power/shuttle_engine/heater{ @@ -145,43 +145,43 @@ dir = 1 }, /obj/structure/sign/warning/vacuum/directional/west, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "x" = ( /obj/structure/chair/office{ dir = 4 }, /obj/effect/landmark/ert_shuttle_spawn, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "y" = ( /obj/structure/chair/office{ dir = 4 }, /obj/effect/landmark/ert_shuttle_spawn, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "z" = ( /obj/structure/table/reinforced, /obj/item/storage/fancy/cigarettes/cigars/havana{ pixel_y = 6 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "A" = ( /obj/effect/turf_decal/stripes/line{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "B" = ( /obj/machinery/light/floor, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "D" = ( /obj/machinery/power/smes, /obj/effect/turf_decal/stripes/full, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "E" = ( /obj/structure/table, @@ -193,17 +193,17 @@ pixel_x = -4; pixel_y = 15 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "F" = ( /obj/effect/mapping_helpers/airlock/cyclelink_helper, /obj/machinery/door/airlock/external/ruin, /obj/structure/fans/tiny, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "G" = ( /obj/machinery/light/small/directional/west, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "H" = ( /obj/machinery/computer/camera_advanced/shuttle_docker/syndicate/hunter{ @@ -211,14 +211,14 @@ y_offset = 6; x_offset = 3 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "I" = ( /obj/structure/chair/comfy/shuttle{ dir = 4 }, /obj/effect/landmark/ert_shuttle_brief_spawn, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "J" = ( /obj/effect/turf_decal/stripes/line, @@ -226,28 +226,28 @@ dir = 1 }, /obj/machinery/door/airlock/external/ruin, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "K" = ( /obj/structure/chair/office{ dir = 8 }, /obj/effect/landmark/ert_shuttle_spawn, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "L" = ( /obj/effect/landmark/ert_shuttle_spawn, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "M" = ( /obj/machinery/suit_storage_unit/standard_unit{ storage_type = /obj/item/tank/internals/oxygen/yellow }, /obj/machinery/light/small/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "O" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "P" = ( /obj/structure/table, @@ -259,12 +259,12 @@ pixel_x = 10; pixel_y = 5 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "Q" = ( /obj/effect/turf_decal/stripes/line, /obj/structure/sign/warning/vacuum/directional/west, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "R" = ( /obj/structure/table, @@ -273,7 +273,7 @@ pixel_y = 16; pixel_x = -7 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "S" = ( /obj/structure/table/reinforced, @@ -292,17 +292,17 @@ /obj/item/pen/fourcolor{ pixel_x = -4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "T" = ( /obj/effect/turf_decal/stripes/line, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "V" = ( /obj/machinery/computer/camera_advanced{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "X" = ( /obj/structure/rack, @@ -319,7 +319,7 @@ pixel_y = -1 }, /obj/effect/turf_decal/stripes/full, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "Y" = ( /obj/structure/table, @@ -336,11 +336,11 @@ pixel_x = -9; pixel_y = 6 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "Z" = ( /obj/structure/bookcase/random/reference, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) (1,1,1) = {" diff --git a/_maps/shuttles/escape_pod/escape_pod_cramped.dmm b/_maps/shuttles/escape_pod/escape_pod_cramped.dmm index 5de7563f1552..3a424f502b48 100644 --- a/_maps/shuttles/escape_pod/escape_pod_cramped.dmm +++ b/_maps/shuttles/escape_pod/escape_pod_cramped.dmm @@ -1,6 +1,6 @@ //MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE "B" = ( -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/shuttle/pod_1) "N" = ( /obj/docking_port/mobile/pod{ @@ -25,7 +25,7 @@ /area/shuttle/pod_1) "Z" = ( /obj/machinery/power/shuttle_engine/propulsion/burst, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/shuttle/pod_1) (1,1,1) = {" diff --git a/_maps/shuttles/ferry/ferry_fancy.dmm b/_maps/shuttles/ferry/ferry_fancy.dmm index a8e553fef1a6..1d2bdd614f93 100644 --- a/_maps/shuttles/ferry/ferry_fancy.dmm +++ b/_maps/shuttles/ferry/ferry_fancy.dmm @@ -11,7 +11,7 @@ /area/shuttle/transport) "d" = ( /obj/machinery/door/airlock/external/ruin, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/transport) "e" = ( /obj/machinery/power/shuttle_engine/propulsion/left{ @@ -28,14 +28,14 @@ /area/shuttle/transport) "g" = ( /obj/structure/chair/comfy/shuttle, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/transport) "h" = ( /obj/machinery/light/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/transport) "i" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/transport) "j" = ( /obj/machinery/power/shuttle_engine/heater{ @@ -46,17 +46,17 @@ /area/shuttle/transport) "k" = ( /obj/machinery/light/small/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/transport) "l" = ( /obj/machinery/door/airlock/titanium, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/transport) "m" = ( /obj/machinery/computer/shuttle/ferry/request{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/transport) "n" = ( /obj/machinery/door/airlock/titanium, @@ -66,7 +66,7 @@ name = "ferry shuttle"; preferred_direction = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/transport) "o" = ( /obj/machinery/power/shuttle_engine/heater{ @@ -81,15 +81,15 @@ /obj/structure/chair/comfy/shuttle{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/transport) "q" = ( /obj/machinery/light/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/transport) "r" = ( /obj/machinery/door/airlock/external/ruin, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/transport) (1,1,1) = {" diff --git a/_maps/shuttles/hunter/hunter_bounty.dmm b/_maps/shuttles/hunter/hunter_bounty.dmm index 2932196835c0..6ea2a3853142 100644 --- a/_maps/shuttles/hunter/hunter_bounty.dmm +++ b/_maps/shuttles/hunter/hunter_bounty.dmm @@ -47,21 +47,21 @@ /obj/structure/chair/office{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "n" = ( /obj/structure/table, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "o" = ( /obj/structure/chair/office{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "p" = ( /obj/effect/mob_spawn/ghost_role/human/fugitive/bounty/hook, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "q" = ( /obj/machinery/power/shuttle_engine/heater{ @@ -74,76 +74,76 @@ /obj/machinery/computer/launchpad{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "s" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "t" = ( /obj/effect/turf_decal/stripes/line{ dir = 9 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "u" = ( /obj/structure/curtain/bounty, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "v" = ( /obj/structure/chair/office{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "w" = ( /obj/structure/table, /obj/item/phone, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "x" = ( /obj/machinery/fugitive_locator, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "y" = ( /obj/structure/chair/office{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "z" = ( /obj/structure/chair/comfy/shuttle{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "A" = ( /obj/machinery/computer/shuttle/hunter{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "B" = ( /obj/machinery/launchpad, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "E" = ( /obj/structure/table, /obj/item/binoculars, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "F" = ( /obj/machinery/power/smes, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "G" = ( /obj/machinery/fugitive_capture, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "H" = ( /obj/effect/turf_decal/stripes/line{ dir = 10 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "I" = ( /obj/machinery/computer/camera_advanced/shuttle_docker/syndicate/hunter{ @@ -151,19 +151,19 @@ y_offset = 6; x_offset = 3 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "K" = ( /obj/machinery/suit_storage_unit/standard_unit{ storage_type = /obj/item/tank/internals/oxygen/yellow }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "M" = ( /obj/effect/mob_spawn/ghost_role/human/fugitive/bounty/armor{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "N" = ( /obj/effect/mapping_helpers/airlock/cyclelink_helper, @@ -174,13 +174,13 @@ /area/shuttle/hunter) "O" = ( /obj/machinery/light/floor, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "P" = ( /obj/structure/fluff/empty_sleeper{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "Q" = ( /obj/effect/mapping_helpers/airlock/cyclelink_helper{ @@ -196,17 +196,17 @@ /obj/item/pinpointer/shuttle{ pixel_y = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "S" = ( /obj/structure/table, /obj/item/storage/toolbox/mechanical, /obj/machinery/light/small/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "T" = ( /obj/structure/tank_dispenser/oxygen, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "U" = ( /obj/effect/mapping_helpers/airlock/cyclelink_helper, @@ -246,23 +246,23 @@ storage_type = /obj/item/tank/internals/oxygen/yellow }, /obj/machinery/light/small/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) "X" = ( /obj/effect/turf_decal/stripes/line{ dir = 8 }, /obj/machinery/light/floor, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "Y" = ( /obj/structure/table, /obj/item/multitool, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter) "Z" = ( /obj/effect/mob_spawn/ghost_role/human/fugitive/bounty/synth, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter) (1,1,1) = {" diff --git a/_maps/shuttles/hunter/hunter_russian.dmm b/_maps/shuttles/hunter/hunter_russian.dmm index 46840f7cdd78..ce6b2163e322 100644 --- a/_maps/shuttles/hunter/hunter_russian.dmm +++ b/_maps/shuttles/hunter/hunter_russian.dmm @@ -7,7 +7,7 @@ /obj/effect/turf_decal/siding/red{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "bi" = ( /obj/machinery/computer/shuttle/hunter{ @@ -41,13 +41,13 @@ /obj/effect/turf_decal/bot, /obj/structure/reagent_dispensers/fueltank, /obj/item/weldingtool/largetank, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "cM" = ( /obj/effect/turf_decal/siding/red/corner{ dir = 1 }, -/turf/closed/wall/mineral/titanium/survival, +/turf/closed/wall/mineral/plastitanium/survival, /area/shuttle/hunter/russian) "cU" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -75,7 +75,7 @@ }, /obj/effect/turf_decal/bot_red, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "gp" = ( /obj/machinery/airalarm/directional/east, @@ -88,7 +88,7 @@ /obj/effect/turf_decal/siding/red{ dir = 10 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "gv" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -103,7 +103,7 @@ /obj/effect/decal/cleanable/dirt, /obj/machinery/fugitive_capture, /obj/structure/sign/poster/contraband/bountyhunters/directional/west, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "ho" = ( /obj/effect/decal/cleanable/dirt, @@ -122,7 +122,7 @@ /obj/item/reagent_containers/cup/glass/bottle/vodka, /obj/item/crowbar/large/old, /obj/item/lighter/greyscale, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "iV" = ( /turf/template_noop, @@ -153,7 +153,7 @@ pixel_y = 2 }, /obj/item/lighter, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "lS" = ( /obj/machinery/computer/crew/syndie{ @@ -177,7 +177,7 @@ /obj/item/ammo_box/speedloader/strilka310, /obj/item/ammo_box/speedloader/strilka310, /obj/item/ammo_box/speedloader/strilka310, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "qF" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -186,7 +186,7 @@ /obj/effect/turf_decal/siding/red, /obj/effect/decal/cleanable/dirt, /obj/machinery/meter, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "rB" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -197,7 +197,7 @@ name = "Cockpit Access" }, /obj/effect/mapping_helpers/airlock/access/all/hunter, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter/russian) "sR" = ( /obj/structure/cable, @@ -206,7 +206,7 @@ }, /obj/effect/turf_decal/siding/red, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "tj" = ( /turf/open/floor/mineral/plastitanium, @@ -220,7 +220,7 @@ }, /obj/effect/mapping_helpers/airlock/cutaiwire, /obj/effect/mapping_helpers/airlock/access/all/hunter, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter/russian) "uT" = ( /obj/machinery/suit_storage_unit/standard_unit{ @@ -228,7 +228,7 @@ storage_type = /obj/item/tank/internals/oxygen/yellow }, /obj/effect/turf_decal/siding/red, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "vZ" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on{ @@ -247,21 +247,21 @@ pixel_x = 5; pixel_y = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "wF" = ( /obj/effect/turf_decal/bot_red, /obj/effect/turf_decal/siding/red, /obj/effect/decal/cleanable/dirt, /obj/effect/mob_spawn/ghost_role/human/fugitive/russian/leader, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "xd" = ( /obj/effect/turf_decal/siding/red{ dir = 1 }, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "xf" = ( /obj/machinery/door/airlock/security/glass{ @@ -272,7 +272,7 @@ }, /obj/effect/mapping_helpers/airlock/cutaiwire, /obj/effect/mapping_helpers/airlock/access/all/hunter, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter/russian) "xE" = ( /obj/machinery/door/airlock/security/glass{ @@ -283,13 +283,13 @@ }, /obj/effect/mapping_helpers/airlock/cutaiwire, /obj/effect/mapping_helpers/airlock/access/all/hunter, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter/russian) "yo" = ( /obj/effect/turf_decal/bot, /obj/structure/mecha_wreckage/ripley, /obj/item/book/manual/ripley_build_and_repair, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "yp" = ( /obj/structure/chair/comfy/shuttle{ @@ -304,7 +304,7 @@ }, /obj/effect/turf_decal/bot_red, /obj/machinery/light/small/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "zk" = ( /obj/machinery/door/airlock/external/glass/ruin{ @@ -316,7 +316,7 @@ }, /obj/effect/mapping_helpers/airlock/locked, /obj/effect/mapping_helpers/airlock/access/all/hunter, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter/russian) "zQ" = ( /obj/machinery/atmospherics/components/tank/air, @@ -357,13 +357,13 @@ dir = 1 }, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "Dr" = ( /obj/effect/turf_decal/siding/red/corner{ dir = 8 }, -/turf/closed/wall/mineral/titanium/survival, +/turf/closed/wall/mineral/plastitanium/survival, /area/shuttle/hunter/russian) "Du" = ( /obj/effect/decal/cleanable/dirt, @@ -400,7 +400,7 @@ /obj/item/clothing/mask/breath, /obj/item/crowbar, /obj/item/crowbar, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "Ej" = ( /obj/machinery/power/apc/auto_name/directional/east, @@ -414,7 +414,7 @@ dir = 9 }, /obj/effect/mapping_helpers/apc/cut_AI_wire, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "EK" = ( /obj/machinery/power/shuttle_engine/heater{ @@ -451,7 +451,7 @@ "GF" = ( /obj/effect/turf_decal/bot, /obj/machinery/fugitive_locator, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "IV" = ( /obj/structure/table, @@ -473,7 +473,7 @@ dir = 9 }, /obj/machinery/light/small/directional/east, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "Jh" = ( /obj/effect/turf_decal/stripes/line, @@ -513,18 +513,18 @@ }, /obj/effect/turf_decal/siding/red, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "LD" = ( /obj/effect/turf_decal/bot, /obj/effect/decal/cleanable/dirt, /obj/structure/kitchenspike, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "LQ" = ( /obj/effect/turf_decal/siding/red, /obj/effect/decal/cleanable/generic, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "LT" = ( /obj/machinery/door/airlock/security/glass{ @@ -536,14 +536,14 @@ }, /obj/effect/mapping_helpers/airlock/cutaiwire, /obj/effect/mapping_helpers/airlock/access/all/hunter, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter/russian) "LW" = ( /obj/effect/spawner/structure/window/survival_pod, /turf/open/floor/plating, /area/shuttle/hunter/russian) "Me" = ( -/turf/closed/wall/mineral/titanium/survival, +/turf/closed/wall/mineral/plastitanium/survival, /area/shuttle/hunter/russian) "MO" = ( /obj/effect/turf_decal/siding/red{ @@ -552,7 +552,7 @@ /obj/effect/decal/cleanable/dirt, /obj/machinery/airalarm/directional/north, /obj/effect/mapping_helpers/airalarm/all_access, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "Nk" = ( /obj/machinery/door/airlock/external/glass/ruin{ @@ -573,7 +573,7 @@ name = "Cargo Hold Access" }, /obj/effect/mapping_helpers/airlock/access/all/hunter, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter/russian) "Se" = ( /obj/effect/turf_decal/stripes/line{ @@ -590,7 +590,7 @@ dir = 1 }, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "Th" = ( /obj/effect/mob_spawn/ghost_role/human/fugitive/russian{ @@ -599,7 +599,7 @@ /obj/effect/turf_decal/bot_red, /obj/effect/decal/cleanable/dirt, /obj/structure/sign/poster/contraband/kss13/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "Uu" = ( /obj/effect/turf_decal/siding/red{ @@ -613,7 +613,7 @@ /obj/item/ammo_box/magazine/m9mm, /obj/item/ammo_box/speedloader/strilka310, /obj/item/crowbar, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "UD" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on{ @@ -643,7 +643,7 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper, /obj/effect/mapping_helpers/airlock/locked, /obj/effect/mapping_helpers/airlock/access/all/hunter, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/hunter/russian) "VA" = ( /obj/machinery/light/small/directional/west, @@ -651,7 +651,7 @@ /obj/effect/decal/cleanable/dirt, /obj/machinery/portable_atmospherics/canister/oxygen, /obj/structure/sign/poster/contraband/communist_state/directional/west, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "WG" = ( /obj/structure/cable, @@ -673,7 +673,7 @@ /obj/item/storage/medkit/ancient, /obj/item/storage/medkit/ancient, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/hunter/russian) "XW" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ diff --git a/_maps/shuttles/infiltrator/infiltrator_advanced.dmm b/_maps/shuttles/infiltrator/infiltrator_advanced.dmm index 988b06f2c7ff..66b68ae5aeec 100644 --- a/_maps/shuttles/infiltrator/infiltrator_advanced.dmm +++ b/_maps/shuttles/infiltrator/infiltrator_advanced.dmm @@ -42,7 +42,7 @@ /obj/item/storage/toolbox/syndicate, /obj/item/assembly/voice, /obj/item/crowbar/red, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "aj" = ( /obj/machinery/computer/shuttle/syndicate, @@ -193,7 +193,7 @@ /obj/effect/mapping_helpers/airlock/cyclelink_helper, /obj/structure/cable, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/bridge) "aB" = ( /obj/machinery/porta_turret/syndicate/shuttle{ @@ -294,7 +294,7 @@ }, /obj/effect/turf_decal/delivery, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/eva) "aU" = ( /obj/effect/turf_decal/stripes/line{ @@ -323,7 +323,7 @@ }, /obj/structure/cable, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/hallway) "aX" = ( /obj/structure/table/reinforced, @@ -387,7 +387,7 @@ /obj/machinery/status_display/ai{ pixel_y = 32 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "be" = ( /obj/effect/turf_decal/delivery, @@ -402,7 +402,7 @@ }, /obj/structure/cable, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/hallway) "bf" = ( /turf/closed/wall/mineral/plastitanium/nodiagonal, @@ -432,7 +432,7 @@ /obj/machinery/suit_storage_unit/syndicate, /obj/effect/turf_decal/box, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/eva) "bj" = ( /obj/effect/turf_decal/stripes/line{ @@ -492,7 +492,7 @@ /obj/machinery/status_display/evac{ pixel_x = -32 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "bp" = ( /obj/machinery/door/poddoor{ @@ -507,7 +507,7 @@ name = "Infiltrator Port Hatch Control"; req_access = list("syndicate") }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/airlock) "bq" = ( /obj/effect/turf_decal/delivery, @@ -734,7 +734,7 @@ /obj/effect/mapping_helpers/apc/cut_AI_wire, /obj/effect/mapping_helpers/apc/syndicate_access, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/eva) "bT" = ( /obj/structure/closet/crate, @@ -783,7 +783,7 @@ /obj/item/clothing/suit/apron/surgical, /obj/structure/table/reinforced, /obj/structure/window/reinforced/spawner/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "bX" = ( /obj/effect/turf_decal/stripes/corner{ @@ -818,7 +818,7 @@ name = "Infiltrator Port Airlock" }, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/airlock) "ca" = ( /turf/closed/wall/r_wall/plastitanium/syndicate/nodiagonal, @@ -858,7 +858,7 @@ dir = 1 }, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/eva) "ce" = ( /turf/closed/wall/mineral/plastitanium/nodiagonal, @@ -877,7 +877,7 @@ /obj/effect/turf_decal/box, /obj/machinery/airalarm/directional/south, /obj/effect/mapping_helpers/airalarm/syndicate_access, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/eva) "ci" = ( /obj/structure/table/reinforced, @@ -901,7 +901,7 @@ /obj/item/assembly/infra, /obj/item/assembly/flash/handheld, /obj/item/assembly/flash/handheld, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "cj" = ( /obj/structure/rack, @@ -1008,7 +1008,7 @@ /obj/machinery/airalarm/directional/west, /obj/effect/mapping_helpers/airalarm/syndicate_access, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "cx" = ( /obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, @@ -1167,7 +1167,7 @@ name = "Infiltrator Starboard Hatch Control"; req_access = list("syndicate") }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/airlock) "cX" = ( /obj/effect/turf_decal/stripes/line{ @@ -1201,7 +1201,7 @@ pixel_x = 4; pixel_y = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "db" = ( /obj/structure/table/reinforced, @@ -1215,7 +1215,7 @@ }, /obj/item/stock_parts/power_store/cell/high, /obj/item/multitool, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "dc" = ( /obj/effect/turf_decal/stripes/line{ @@ -1266,7 +1266,7 @@ }, /obj/structure/cable, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/eva) "dh" = ( /obj/effect/turf_decal/stripes/line{ @@ -1304,7 +1304,7 @@ /obj/item/grenade/c4, /obj/item/grenade/c4, /obj/item/grenade/c4, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "dk" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on{ @@ -1483,7 +1483,7 @@ }, /obj/effect/turf_decal/box, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "dE" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on{ @@ -1520,7 +1520,7 @@ /obj/effect/mapping_helpers/apc/syndicate_access, /obj/effect/decal/cleanable/dirt, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "dI" = ( /obj/effect/turf_decal/stripes/line{ @@ -1557,14 +1557,14 @@ /obj/effect/mapping_helpers/apc/cut_AI_wire, /obj/effect/mapping_helpers/apc/syndicate_access, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "dN" = ( /obj/machinery/recharge_station, /obj/effect/turf_decal/delivery, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, /obj/structure/window/reinforced/spawner/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "dO" = ( /obj/structure/closet/syndicate/personal, @@ -1578,7 +1578,7 @@ /obj/machinery/status_display/evac{ pixel_y = -32 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/eva) "dQ" = ( /obj/structure/reagent_dispensers/fueltank, @@ -1589,7 +1589,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "dR" = ( /turf/closed/wall/r_wall/plastitanium/syndicate/nodiagonal, @@ -1617,20 +1617,20 @@ /obj/machinery/airalarm/directional/west, /obj/effect/mapping_helpers/airalarm/syndicate_access, /obj/effect/turf_decal/stripes/line, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "dU" = ( /obj/structure/table/optable, /obj/effect/turf_decal/bot, /obj/effect/decal/cleanable/blood/old, /obj/effect/turf_decal/stripes/line, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "dW" = ( /obj/effect/turf_decal/delivery, /obj/effect/turf_decal/stripes/line, /obj/machinery/recharge_station, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "dX" = ( /obj/effect/turf_decal/stripes/line, @@ -1670,7 +1670,7 @@ /obj/structure/sign/warning/no_smoking{ pixel_x = 28 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "ec" = ( /obj/machinery/door/airlock/hatch{ @@ -1686,7 +1686,7 @@ /obj/structure/cable, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, /obj/structure/sign/warning/secure_area/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/hallway) "ed" = ( /obj/effect/turf_decal/bot, @@ -1769,7 +1769,7 @@ /obj/structure/cable, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, /obj/structure/sign/warning/secure_area/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/hallway) "el" = ( /obj/effect/turf_decal/stripes/line, @@ -1820,7 +1820,7 @@ /obj/structure/extinguisher_cabinet/directional/east, /obj/machinery/airalarm/directional/north, /obj/effect/mapping_helpers/airalarm/syndicate_access, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "ep" = ( /obj/structure/mirror/directional/north, @@ -1870,7 +1870,7 @@ dir = 8 }, /obj/machinery/firealarm/directional/east, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "ev" = ( /turf/closed/wall/mineral/plastitanium/nodiagonal, @@ -1891,7 +1891,7 @@ /obj/effect/turf_decal/box, /obj/effect/decal/cleanable/dirt, /obj/machinery/light/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/eva) "kf" = ( /obj/structure/chair/comfy/shuttle{ @@ -1917,7 +1917,7 @@ pixel_x = 6 }, /obj/effect/turf_decal/stripes/line, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "tB" = ( /obj/effect/turf_decal/stripes/line{ @@ -2003,7 +2003,7 @@ pixel_x = 32 }, /obj/machinery/light/small/directional/east, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/armory) "KJ" = ( /obj/effect/turf_decal/stripes/line{ @@ -2056,12 +2056,12 @@ }, /obj/effect/decal/cleanable/dirt, /obj/item/healthanalyzer/advanced, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "TT" = ( /obj/structure/table/reinforced, /obj/item/storage/backpack/duffelbag/syndie/surgery, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/syndicate/medical) "WC" = ( /obj/effect/turf_decal/stripes/line{ diff --git a/_maps/shuttles/mining/mining_common_northstar.dmm b/_maps/shuttles/mining/mining_common_northstar.dmm index f746f90c0abb..9ee4fbc666aa 100644 --- a/_maps/shuttles/mining/mining_common_northstar.dmm +++ b/_maps/shuttles/mining/mining_common_northstar.dmm @@ -10,7 +10,7 @@ dir = 8 }, /obj/structure/sign/nanotrasen/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "e" = ( /obj/machinery/power/shuttle_engine/heater, @@ -21,7 +21,7 @@ /obj/effect/turf_decal/caution/stand_clear/red{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "l" = ( /obj/effect/spawner/structure/window/survival_pod, @@ -39,13 +39,13 @@ /obj/effect/turf_decal/trimline/neutral/line{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "t" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "v" = ( -/turf/closed/wall/mineral/titanium/survival, +/turf/closed/wall/mineral/plastitanium/survival, /area/shuttle/mining) "z" = ( /obj/structure/closet/crate, @@ -54,7 +54,7 @@ dir = 1 }, /obj/machinery/light/small/red/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/mining) "A" = ( /obj/structure/chair/comfy/shuttle{ @@ -63,7 +63,7 @@ /obj/effect/turf_decal/trimline/neutral/corner{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "C" = ( /obj/machinery/door/airlock/survival_pod/glass{ @@ -76,12 +76,12 @@ port_direction = 8; shuttle_id = "mining_common" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "D" = ( /obj/structure/table/reinforced, /obj/item/storage/toolbox/emergency, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "G" = ( /obj/structure/ore_box, @@ -89,28 +89,28 @@ /obj/effect/turf_decal/siding/dark{ dir = 5 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/mining) "J" = ( /obj/effect/turf_decal/trimline/neutral/line, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "P" = ( /obj/machinery/computer/shuttle/mining/common, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "T" = ( /obj/structure/chair/comfy/shuttle{ dir = 1 }, /obj/effect/turf_decal/trimline/neutral/line, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "U" = ( /obj/structure/table/reinforced, /obj/item/radio, /obj/machinery/light/small/red/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) (1,1,1) = {" diff --git a/_maps/shuttles/mining/mining_northstar.dmm b/_maps/shuttles/mining/mining_northstar.dmm index 4a557937d778..20c9948da8b4 100644 --- a/_maps/shuttles/mining/mining_northstar.dmm +++ b/_maps/shuttles/mining/mining_northstar.dmm @@ -10,14 +10,14 @@ port_direction = 8; shuttle_id = "mining" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "c" = ( /obj/structure/table/reinforced, /obj/item/storage/toolbox/emergency, /obj/structure/sign/warning/xeno_mining/directional/north, /obj/machinery/light/small/red/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "d" = ( /obj/item/clothing/suit/hazardvest{ @@ -103,7 +103,7 @@ }, /obj/effect/turf_decal/trimline/brown, /obj/machinery/light/small/red/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/mining) "j" = ( /obj/structure/chair/comfy/shuttle{ @@ -112,7 +112,7 @@ /obj/effect/turf_decal/trimline/brown/end{ dir = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "l" = ( /obj/structure/chair/comfy/shuttle{ @@ -126,24 +126,24 @@ }, /obj/structure/sign/poster/official/work_for_a_future/directional/north, /obj/machinery/light/small/red/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "m" = ( /obj/effect/turf_decal/caution/stand_clear/red{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "s" = ( /obj/machinery/computer/shuttle/mining, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "z" = ( /obj/effect/turf_decal/siding/brown{ dir = 5 }, /obj/effect/turf_decal/trimline/brown, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/mining) "A" = ( /obj/machinery/power/shuttle_engine/propulsion, @@ -155,7 +155,7 @@ }, /obj/effect/turf_decal/trimline/brown/end, /obj/machinery/light/small/red/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "F" = ( /obj/structure/table/reinforced, @@ -163,20 +163,20 @@ /obj/item/tank/internals/emergency_oxygen, /obj/item/crowbar/red, /obj/item/clothing/mask/gas, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "G" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "I" = ( -/turf/closed/wall/mineral/titanium/survival, +/turf/closed/wall/mineral/plastitanium/survival, /area/shuttle/mining) "K" = ( /obj/effect/turf_decal/trimline/purple/line{ dir = 1 }, /obj/effect/turf_decal/trimline/purple/line, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) "P" = ( /turf/template_noop, @@ -196,7 +196,7 @@ }, /obj/effect/turf_decal/trimline/brown, /obj/structure/ore_box, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/mining) "Z" = ( /obj/structure/chair/comfy/shuttle{ @@ -209,7 +209,7 @@ dir = 4 }, /obj/structure/sign/nanotrasen/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/mining) (1,1,1) = {" diff --git a/_maps/shuttles/pirate/pirate_default.dmm b/_maps/shuttles/pirate/pirate_default.dmm index 9b6f60d16a76..135be66b0410 100644 --- a/_maps/shuttles/pirate/pirate_default.dmm +++ b/_maps/shuttles/pirate/pirate_default.dmm @@ -8,7 +8,7 @@ /obj/effect/mapping_helpers/airalarm/all_access, /obj/machinery/atmospherics/components/unary/vent_pump/on, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "ab" = ( /obj/structure/table, @@ -75,7 +75,7 @@ }, /obj/effect/decal/cleanable/dirt, /obj/machinery/light/small/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "aj" = ( /turf/closed/wall/mineral/plastitanium/nodiagonal, @@ -140,7 +140,7 @@ /obj/effect/mapping_helpers/airalarm/all_access, /obj/machinery/atmospherics/components/unary/vent_pump/on, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "ap" = ( /obj/machinery/door/airlock/hatch{ @@ -149,7 +149,7 @@ /obj/structure/barricade/wooden/crude, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "aq" = ( /obj/structure/chair/stool/directional/south, @@ -210,7 +210,7 @@ /obj/structure/barricade/wooden/crude, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "aw" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -429,7 +429,7 @@ pixel_y = 3 }, /obj/machinery/recharger, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "bk" = ( /obj/effect/turf_decal/stripes/line{ @@ -451,11 +451,11 @@ /obj/structure/table, /obj/machinery/cell_charger, /obj/item/stock_parts/power_store/cell/high, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "bm" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "bo" = ( /obj/machinery/light/small/directional/east, @@ -499,7 +499,7 @@ pixel_x = 13; pixel_y = 6 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "bu" = ( /obj/machinery/firealarm/directional/north, @@ -528,13 +528,13 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, /obj/effect/decal/cleanable/dirt, /obj/machinery/piratepad, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "bA" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "bB" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, @@ -575,7 +575,7 @@ /obj/machinery/computer/piratepad_control{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "bJ" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -702,7 +702,7 @@ pixel_x = 5 }, /obj/structure/sign/poster/contraband/bountyhunters/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "ep" = ( /obj/structure/window/reinforced/spawner/directional/north{ @@ -760,7 +760,7 @@ anchored = 1 }, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "eE" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -776,14 +776,14 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "gY" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "km" = ( /obj/machinery/atmospherics/components/tank/air, @@ -805,7 +805,7 @@ }, /obj/effect/decal/cleanable/dirt, /obj/effect/mapping_helpers/airlock/cutaiwire, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "np" = ( /obj/structure/reagent_dispensers/fueltank, @@ -841,7 +841,7 @@ pixel_x = -2; pixel_y = 2 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "wf" = ( /obj/machinery/door/airlock{ @@ -870,7 +870,7 @@ }, /obj/effect/decal/cleanable/dirt, /obj/effect/mapping_helpers/airlock/cutaiwire, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "zw" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, @@ -916,7 +916,7 @@ pixel_x = 2; pixel_y = -1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "Ni" = ( /obj/effect/turf_decal/stripes/line, diff --git a/_maps/shuttles/pirate/pirate_grey.dmm b/_maps/shuttles/pirate/pirate_grey.dmm index 50594b83d9b5..9dc5377c9fdf 100644 --- a/_maps/shuttles/pirate/pirate_grey.dmm +++ b/_maps/shuttles/pirate/pirate_grey.dmm @@ -247,7 +247,7 @@ /obj/effect/turf_decal/caution/stand_clear{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "jS" = ( /obj/effect/spawner/random/trash/mess, @@ -340,7 +340,7 @@ }, /obj/effect/mapping_helpers/airlock/cutaiwire, /obj/structure/fans/tiny, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "os" = ( /obj/structure/rack, @@ -781,7 +781,7 @@ /obj/effect/turf_decal/caution/stand_clear{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "zQ" = ( /obj/structure/cable, @@ -826,7 +826,7 @@ }, /obj/machinery/light/small/directional/south, /obj/item/reagent_containers/cup/glass/coffee, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "AZ" = ( /obj/effect/decal/cleanable/dirt, @@ -858,7 +858,7 @@ dir = 8 }, /obj/structure/fans/tiny, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "Bu" = ( /obj/machinery/power/apc/auto_name/directional/north{ @@ -1338,7 +1338,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "TV" = ( /obj/effect/turf_decal/trimline/yellow/warning{ diff --git a/_maps/shuttles/pirate/pirate_irs.dmm b/_maps/shuttles/pirate/pirate_irs.dmm index 5ad360f2f48f..bde069eaa77b 100644 --- a/_maps/shuttles/pirate/pirate_irs.dmm +++ b/_maps/shuttles/pirate/pirate_irs.dmm @@ -4,7 +4,7 @@ /obj/effect/decal/cleanable/blood/old, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "az" = ( /obj/effect/turf_decal/trimline/dark_blue/filled/line{ @@ -110,7 +110,7 @@ "eg" = ( /obj/effect/turf_decal/bot, /obj/machinery/power/port_gen/pacman/pre_loaded, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "ev" = ( /obj/structure/chair/comfy/shuttle{ @@ -148,7 +148,7 @@ /obj/effect/turf_decal/caution/stand_clear/white{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "fO" = ( /obj/effect/turf_decal/tile/dark_blue/half{ @@ -197,7 +197,7 @@ "hW" = ( /obj/machinery/suit_storage_unit/standard_unit, /obj/effect/turf_decal/bot/left, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "ie" = ( /obj/effect/turf_decal/trimline/dark_blue/filled/line{ @@ -213,7 +213,7 @@ /obj/structure/table/reinforced, /obj/machinery/light/red/dim/directional/south, /obj/item/storage/bag/money/vault, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "ip" = ( /obj/structure/table, @@ -259,7 +259,7 @@ dir = 9 }, /obj/item/melee/chainofcommand, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "jp" = ( /obj/effect/turf_decal/stripes{ @@ -273,7 +273,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold/scrubbers/hidden/layer2, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "jX" = ( /obj/effect/turf_decal/tile/dark/full, @@ -286,7 +286,7 @@ pixel_x = 2 }, /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "kK" = ( /obj/structure/rack, @@ -316,14 +316,14 @@ id_tag = "pirateportexternal" }, /obj/effect/mapping_helpers/airlock/locked, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "lf" = ( /obj/structure/window/reinforced/survival_pod/spawner/directional/north, /obj/structure/window/reinforced/survival_pod{ dir = 5 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "lp" = ( /obj/effect/turf_decal/stripes, @@ -333,7 +333,7 @@ /obj/effect/turf_decal/tile/neutral/full, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold/scrubbers/hidden/layer2, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "lE" = ( /turf/open/floor/iron/grimy, @@ -359,7 +359,7 @@ /obj/item/stack/cable_coil/five{ pixel_y = -6 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "mA" = ( /turf/open/floor/iron, @@ -378,7 +378,7 @@ /area/shuttle/pirate) "mN" = ( /obj/machinery/loot_locator, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "nk" = ( /obj/effect/turf_decal/tile/dark_blue/half{ @@ -392,7 +392,7 @@ /area/shuttle/pirate) "nq" = ( /obj/effect/turf_decal/tile/dark/full, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "nw" = ( /turf/open/floor/iron/kitchen, @@ -410,7 +410,7 @@ id_tag = "piratestarboardexternal" }, /obj/effect/mapping_helpers/airlock/locked, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "nE" = ( /obj/effect/turf_decal/trimline/yellow/filled/line, @@ -425,13 +425,13 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "nP" = ( /obj/effect/turf_decal/tile/dark/full, /obj/effect/decal/cleanable/blood/splatter, /obj/machinery/firealarm/directional/north, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "od" = ( /obj/effect/turf_decal/tile/dark_blue/half{ @@ -523,7 +523,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold/scrubbers/hidden/layer2, /obj/machinery/door/firedoor, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "pO" = ( /obj/effect/turf_decal/trimline/yellow/filled/corner, @@ -551,10 +551,10 @@ /obj/structure/reagent_dispensers/fueltank/large, /obj/machinery/power/apc/auto_name/directional/north, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "qU" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "rf" = ( /obj/structure/chair/stool/directional/south, @@ -592,7 +592,7 @@ }, /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4, /obj/machinery/firealarm/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "sr" = ( /obj/structure/cable, @@ -652,7 +652,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold/scrubbers/hidden/layer2, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "tF" = ( /obj/effect/turf_decal/trimline/yellow/filled/line, @@ -697,7 +697,7 @@ }, /obj/effect/turf_decal/bot, /obj/machinery/light/small/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "uy" = ( /obj/machinery/chem_dispenser/drinks/fullupgrade{ @@ -713,11 +713,11 @@ "uC" = ( /obj/structure/table/reinforced, /obj/item/storage/backpack/satchel, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "uJ" = ( /obj/structure/tank_dispenser, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "vi" = ( /obj/effect/turf_decal/trimline/dark_blue/filled/corner{ @@ -753,19 +753,19 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold/scrubbers/hidden/layer2, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "vO" = ( /obj/effect/turf_decal/bot/right, /obj/structure/reagent_dispensers/watertank/high, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "wl" = ( /obj/structure/window/reinforced/survival_pod/spawner/directional/north, /obj/structure/window/reinforced/survival_pod{ dir = 9 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "ww" = ( /obj/item/documents, @@ -805,7 +805,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "yg" = ( /obj/structure/chair/stool/directional/east, @@ -864,7 +864,7 @@ dir = 1 }, /obj/effect/turf_decal/bot/right, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Ah" = ( /obj/structure/railing{ @@ -904,13 +904,13 @@ dir = 1 }, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Bl" = ( /obj/machinery/shuttle_scrambler{ name = "Wireless Tax Retrieval Machine" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Bq" = ( /obj/effect/turf_decal/trimline/dark_blue/filled/line{ @@ -1007,7 +1007,7 @@ /obj/effect/turf_decal/stripes/red/line{ dir = 6 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "CM" = ( /obj/effect/turf_decal/trimline/dark_blue/filled/line{ @@ -1054,13 +1054,13 @@ /obj/machinery/computer/piratepad_control{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Eq" = ( /obj/machinery/suit_storage_unit/standard_unit, /obj/effect/turf_decal/bot, /obj/machinery/light/small/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Et" = ( /obj/machinery/power/shuttle_engine/large, @@ -1070,7 +1070,7 @@ "EJ" = ( /obj/structure/table/reinforced, /obj/machinery/light/small/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Fn" = ( /obj/structure/chair/office{ @@ -1158,7 +1158,7 @@ name = "Fallujah firehose"; desc = "Someone's got a friend in Syria!" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "HO" = ( /obj/structure/cable, @@ -1200,7 +1200,7 @@ /obj/structure/railing/corner{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "IE" = ( /obj/effect/turf_decal/trimline/dark_blue/filled/line, @@ -1283,7 +1283,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "KA" = ( /obj/structure/chair/comfy/shuttle{ @@ -1316,7 +1316,7 @@ name = "Pirate Ship"; port_direction = 2 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "KI" = ( /obj/structure/chair/comfy/shuttle{ @@ -1369,7 +1369,7 @@ /obj/effect/mapping_helpers/apc/cut_AI_wire, /obj/machinery/atmospherics/pipe/smart/manifold/scrubbers/hidden/layer2, /obj/machinery/firealarm/directional/west, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "KZ" = ( /obj/machinery/door/window/brigdoor/right/directional/east{ @@ -1386,7 +1386,7 @@ /turf/open/floor/iron/white, /area/shuttle/pirate) "Lf" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Lo" = ( /obj/machinery/porta_turret/syndicate/irs{ @@ -1467,7 +1467,7 @@ }, /obj/effect/turf_decal/box/red, /obj/effect/decal/cleanable/blood/gibs/old, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "My" = ( /obj/effect/turf_decal/stripes{ @@ -1543,7 +1543,7 @@ id_tag = "pirateportexternal" }, /obj/effect/mapping_helpers/airlock/locked, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "Ot" = ( /obj/machinery/porta_turret/syndicate/irs{ @@ -1570,7 +1570,7 @@ pixel_x = 6; pixel_y = 2 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "PP" = ( /obj/effect/spawner/structure/window/reinforced, @@ -1590,12 +1590,12 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold/scrubbers/hidden/layer2, /obj/machinery/door/firedoor, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "Qc" = ( /obj/effect/turf_decal/bot/right, /obj/machinery/piratepad, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Qi" = ( /obj/structure/railing, @@ -1704,7 +1704,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "SG" = ( /obj/effect/turf_decal/siding/thinplating_new/corner{ @@ -1714,7 +1714,7 @@ dir = 1 }, /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "SM" = ( /obj/effect/turf_decal/trimline/blue/filled/line, @@ -1815,7 +1815,7 @@ /area/shuttle/pirate) "VS" = ( /obj/structure/window/reinforced/survival_pod/spawner/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "WA" = ( /obj/structure/cable, @@ -1829,7 +1829,7 @@ name = "swirlie-inator 3000" }, /obj/effect/turf_decal/bot_red, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "WR" = ( /turf/template_noop, @@ -1837,7 +1837,7 @@ "WU" = ( /obj/effect/turf_decal/tile/dark/full, /obj/machinery/light/red/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "Xr" = ( /obj/machinery/door/airlock/public/glass{ diff --git a/_maps/shuttles/pirate/pirate_silverscale.dmm b/_maps/shuttles/pirate/pirate_silverscale.dmm index e5780fed0cbd..a2fd2bb9625c 100644 --- a/_maps/shuttles/pirate/pirate_silverscale.dmm +++ b/_maps/shuttles/pirate/pirate_silverscale.dmm @@ -8,7 +8,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "aZ" = ( /obj/effect/turf_decal/bot_red, @@ -16,7 +16,7 @@ helmet_type = /obj/item/clothing/head/helmet/space/pirate/tophat; suit_type = /obj/item/clothing/suit/space/pirate/silverscale }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "bp" = ( /obj/machinery/computer/monitor{ @@ -30,7 +30,7 @@ /obj/machinery/door/airlock/silver, /obj/effect/mapping_helpers/airlock/cutaiwire, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "de" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -43,7 +43,7 @@ /obj/machinery/door/airlock/external/glass/ruin{ id_tag = "pirateportexternal" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "dI" = ( /obj/structure/dresser, @@ -55,7 +55,7 @@ }, /obj/structure/cable, /obj/effect/turf_decal/bot, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "dT" = ( /obj/structure/table, @@ -112,7 +112,7 @@ "fR" = ( /obj/machinery/airalarm/directional/east, /obj/effect/mapping_helpers/airalarm/all_access, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "fT" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -132,7 +132,7 @@ /obj/machinery/door/airlock/silver, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, /obj/effect/mapping_helpers/airlock/cutaiwire, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "hu" = ( /obj/structure/chair/stool/directional/west, @@ -154,7 +154,7 @@ dir = 8 }, /obj/effect/turf_decal/trimline/blue, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "je" = ( /turf/template_noop, @@ -173,7 +173,7 @@ /obj/effect/turf_decal/caution/stand_clear{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "ky" = ( /obj/structure/toilet{ @@ -185,7 +185,7 @@ dir = 8 }, /obj/machinery/light/small/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "kF" = ( /obj/effect/turf_decal/trimline/yellow/warning, @@ -259,7 +259,7 @@ /obj/machinery/door/airlock/external/glass/ruin{ id_tag = "pirateportexternal" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "pk" = ( /obj/machinery/porta_turret/syndicate/energy{ @@ -277,7 +277,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "rB" = ( /turf/closed/wall/mineral/titanium, @@ -331,7 +331,7 @@ /area/shuttle/pirate) "wa" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "wE" = ( /obj/machinery/porta_turret/syndicate/energy{ @@ -376,12 +376,12 @@ pixel_x = 17 }, /obj/effect/turf_decal/bot, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "yv" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, /obj/machinery/meter, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "zm" = ( /obj/structure/secure_safe/directional/north, @@ -406,7 +406,7 @@ /obj/effect/turf_decal/caution/stand_clear{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "zQ" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -419,7 +419,7 @@ /obj/machinery/door/airlock/external/glass/ruin{ id_tag = "piratestarboardexternal" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "Ae" = ( /obj/machinery/shuttle_scrambler, @@ -454,7 +454,7 @@ dir = 8 }, /obj/machinery/light/small/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Bl" = ( /obj/docking_port/stationary{ @@ -479,7 +479,7 @@ /obj/machinery/door/airlock/external/glass/ruin{ id_tag = "piratestarboardexternal" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "Ch" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, @@ -511,13 +511,13 @@ name = "External Bolt Control"; id = "piratestarboardexternal" }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Fl" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Gr" = ( /obj/effect/turf_decal/trimline/yellow/warning{ @@ -559,7 +559,7 @@ projectile_damage_multiplier = 1.30; desc = "A laser gun modified to cost 20 credits to fire. Someone on the station should have an ID that works with this thing." }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Ig" = ( /obj/structure/table/glass, @@ -573,7 +573,7 @@ dir = 4 }, /obj/machinery/light/directional/west, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Jc" = ( /obj/structure/table/glass, @@ -608,7 +608,7 @@ /obj/structure/reagent_dispensers/watertank, /obj/effect/turf_decal/bot/right, /obj/machinery/light/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "JV" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, @@ -622,14 +622,14 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 6 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Ln" = ( /obj/machinery/power/smes/ship, /obj/structure/cable, /obj/effect/turf_decal/bot, /obj/machinery/light/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "LE" = ( /obj/machinery/porta_turret/syndicate/energy{ @@ -704,7 +704,7 @@ dir = 4 }, /obj/effect/mapping_helpers/airlock/cutaiwire, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "Oo" = ( /obj/structure/table/glass, @@ -738,7 +738,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "OJ" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on{ @@ -750,17 +750,17 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Qm" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "QI" = ( /obj/machinery/atmospherics/components/tank/air{ dir = 1 }, /obj/effect/turf_decal/bot, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "QO" = ( /obj/machinery/newscaster/directional/north, @@ -781,7 +781,7 @@ /obj/item/multitool, /obj/effect/turf_decal/bot, /obj/effect/mapping_helpers/apc/cut_AI_wire, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "RE" = ( /obj/machinery/newscaster/directional/south, @@ -820,7 +820,7 @@ pixel_y = 4 }, /obj/item/gun/ballistic/rifle/boltaction/prime, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "RU" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -843,7 +843,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Sv" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -864,7 +864,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "TV" = ( /obj/machinery/porta_turret/syndicate/energy{ @@ -913,7 +913,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on, /obj/machinery/airalarm/directional/north, /obj/effect/mapping_helpers/airalarm/all_access, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Vw" = ( /obj/structure/table, @@ -930,7 +930,7 @@ normaldoorcontrol = 1; specialfunctions = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Ww" = ( /obj/machinery/power/shuttle_engine/heater{ @@ -941,7 +941,7 @@ "Wy" = ( /obj/structure/reagent_dispensers/fueltank, /obj/effect/turf_decal/bot/left, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "Xw" = ( /obj/structure/table/glass, @@ -984,7 +984,7 @@ /obj/item/storage/box/lethalshot, /obj/effect/turf_decal/bot, /obj/item/gun/ballistic/shotgun/doublebarrel/slugs, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/pirate) "YD" = ( /obj/structure/table, @@ -1002,7 +1002,7 @@ "ZD" = ( /obj/machinery/door/airlock/silver/glass, /obj/effect/mapping_helpers/airlock/cutaiwire, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/pirate) "ZY" = ( /obj/machinery/airalarm/directional/east, diff --git a/_maps/shuttles/ruin/ruin_syndicate_dropship.dmm b/_maps/shuttles/ruin/ruin_syndicate_dropship.dmm index 3bbec2623919..f2cb4fd0ef3c 100644 --- a/_maps/shuttles/ruin/ruin_syndicate_dropship.dmm +++ b/_maps/shuttles/ruin/ruin_syndicate_dropship.dmm @@ -181,7 +181,7 @@ }, /obj/effect/decal/cleanable/dirt, /obj/machinery/light/small/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/ruin/caravan/syndicate3) "rQ" = ( /obj/effect/turf_decal/stripes/line{ @@ -244,7 +244,7 @@ /obj/effect/mapping_helpers/apc/syndicate_access, /obj/effect/decal/cleanable/dirt, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/ruin/caravan/syndicate3) "zP" = ( /obj/machinery/computer/crew{ @@ -362,7 +362,7 @@ /obj/machinery/airalarm/directional/north, /obj/effect/mapping_helpers/airalarm/syndicate_access, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/ruin/caravan/syndicate3) "Mc" = ( /obj/effect/turf_decal/tile/red/half/contrasted, @@ -373,7 +373,7 @@ dir = 1 }, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/ruin/caravan/syndicate3) "Oe" = ( /obj/machinery/power/smes/super/full, @@ -469,7 +469,7 @@ "Vh" = ( /obj/structure/chair/comfy/shuttle, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/ruin/caravan/syndicate3) "Vp" = ( /obj/machinery/firealarm/directional/west, @@ -481,7 +481,7 @@ }, /obj/machinery/firealarm/directional/south, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/ruin/caravan/syndicate3) (1,1,1) = {" diff --git a/_maps/shuttles/whiteship/whiteship_cere.dmm b/_maps/shuttles/whiteship/whiteship_cere.dmm index 4696f2727a44..30125bc5cbf0 100644 --- a/_maps/shuttles/whiteship/whiteship_cere.dmm +++ b/_maps/shuttles/whiteship/whiteship_cere.dmm @@ -215,7 +215,7 @@ /area/shuttle/abandoned/crew) "gK" = ( /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "hj" = ( /obj/machinery/door/airlock/hatch{ @@ -315,12 +315,12 @@ }, /obj/item/storage/box/lights/mixed, /obj/item/storage/box/lights/bulbs, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "mV" = ( /obj/effect/decal/cleanable/dirt, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "nf" = ( /obj/machinery/door/airlock{ @@ -343,7 +343,7 @@ /obj/item/mining_scanner, /obj/machinery/airalarm/directional/south, /obj/effect/mapping_helpers/airalarm/unlocked, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "ns" = ( /obj/structure/cable, @@ -458,7 +458,7 @@ "rZ" = ( /obj/effect/decal/cleanable/dirt, /mob/living/basic/migo, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "su" = ( /obj/effect/spawner/structure/window/reinforced/shuttle, @@ -508,7 +508,7 @@ "wN" = ( /obj/effect/decal/cleanable/dirt, /obj/structure/reagent_dispensers/fueltank, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "yA" = ( /obj/structure/cable, @@ -548,7 +548,7 @@ req_access = list() }, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "Ah" = ( /obj/structure/cable, @@ -564,7 +564,7 @@ "Bq" = ( /obj/effect/decal/cleanable/dirt, /obj/structure/closet/firecloset/full, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "BN" = ( /obj/structure/cable, @@ -578,7 +578,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "CC" = ( /obj/machinery/vending/boozeomat, @@ -591,14 +591,14 @@ /obj/structure/window/reinforced/spawner/directional/east, /obj/machinery/suit_storage_unit/spaceruin, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "Dr" = ( /obj/effect/spawner/random/exotic/ripley, /obj/effect/decal/cleanable/dirt, /obj/machinery/airalarm/directional/north, /obj/effect/mapping_helpers/airalarm/unlocked, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "Dy" = ( /obj/structure/cable, @@ -673,14 +673,14 @@ /obj/machinery/firealarm/directional/south, /obj/structure/closet/crate, /obj/effect/spawner/random/maintenance/six, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "Ha" = ( /obj/machinery/computer/mech_bay_power_console{ dir = 4 }, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "Hw" = ( /obj/machinery/light/small/directional/east, @@ -713,7 +713,7 @@ /obj/structure/window/reinforced/spawner/directional/west, /obj/machinery/suit_storage_unit/spaceruin, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "HZ" = ( /obj/machinery/microwave, @@ -743,7 +743,7 @@ "Km" = ( /obj/effect/spawner/random/structure/crate, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "Ko" = ( /obj/machinery/power/shuttle_engine/propulsion/right{ @@ -771,7 +771,7 @@ /obj/item/gun/energy/laser/retro, /obj/structure/closet/crate/secure/weapon, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "Li" = ( /obj/structure/closet/secure_closet/freezer/meat/open, @@ -888,7 +888,7 @@ /obj/item/stack/sheet/mineral/plasma, /obj/structure/closet/crate, /obj/machinery/light/broken/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "Sx" = ( /obj/machinery/door/airlock/command{ @@ -942,38 +942,38 @@ "TD" = ( /obj/machinery/suit_storage_unit/spaceruin, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "TG" = ( /obj/structure/window/reinforced/spawner/directional/west, /obj/structure/tank_dispenser/oxygen, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "US" = ( /obj/effect/decal/cleanable/dirt, /obj/machinery/atmospherics/components/unary/vent_pump/on{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "Vx" = ( /obj/structure/window/reinforced/spawner/directional/east, /obj/structure/tank_dispenser/oxygen, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "VD" = ( /obj/effect/spawner/random/structure/crate, /obj/effect/decal/cleanable/dirt, /obj/machinery/light/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "WF" = ( /obj/machinery/light/broken/directional/north, /obj/machinery/mech_bay_recharge_port, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/cargo) "XT" = ( /obj/machinery/door/airlock/external/glass/ruin, diff --git a/_maps/shuttles/whiteship/whiteship_kilo.dmm b/_maps/shuttles/whiteship/whiteship_kilo.dmm index a7a1940b5c22..197c6384560b 100644 --- a/_maps/shuttles/whiteship/whiteship_kilo.dmm +++ b/_maps/shuttles/whiteship/whiteship_kilo.dmm @@ -160,7 +160,7 @@ /obj/effect/turf_decal/trimline/brown/warning{ dir = 10 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/engine) "ig" = ( /obj/effect/decal/cleanable/dirt, @@ -306,7 +306,7 @@ /obj/structure/cable, /obj/machinery/airalarm/directional/west, /obj/effect/mapping_helpers/airalarm/all_access, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned/crew) "mH" = ( /obj/effect/turf_decal/stripes{ @@ -388,7 +388,7 @@ pixel_y = 4 }, /obj/machinery/atmospherics/components/unary/vent_pump/on, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned/bridge) "qL" = ( /obj/effect/turf_decal/stripes/line{ @@ -483,7 +483,7 @@ /obj/effect/decal/cleanable/dirt, /obj/effect/decal/cleanable/cobweb/cobweb2, /obj/machinery/light/small/blacklight/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned/bridge) "uA" = ( /obj/machinery/porta_turret/centcom_shuttle/weak{ @@ -515,7 +515,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, /obj/effect/decal/cleanable/blood/footprints, /obj/structure/cable, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned/crew) "yG" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ @@ -556,7 +556,7 @@ /obj/item/stack/cable_coil/cut, /obj/machinery/airalarm/directional/south, /obj/effect/mapping_helpers/airalarm/all_access, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned/bridge) "zu" = ( /obj/machinery/cell_charger, @@ -681,7 +681,7 @@ pixel_x = 9; pixel_y = -2 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/engine) "HD" = ( /obj/effect/turf_decal/siding/thinplating_new/dark{ @@ -1043,7 +1043,7 @@ pixel_x = -24; pixel_y = -6 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned/bridge) "Tf" = ( /obj/structure/table, @@ -1061,7 +1061,7 @@ }, /obj/machinery/airalarm/directional/north, /obj/effect/mapping_helpers/airalarm/all_access, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned/engine) "Tg" = ( /obj/effect/decal/cleanable/dirt, diff --git a/_maps/shuttles/whiteship/whiteship_pubby.dmm b/_maps/shuttles/whiteship/whiteship_pubby.dmm index c351bbdd41b0..303794f46fb1 100644 --- a/_maps/shuttles/whiteship/whiteship_pubby.dmm +++ b/_maps/shuttles/whiteship/whiteship_pubby.dmm @@ -117,7 +117,7 @@ /obj/machinery/holopad/secure, /obj/effect/decal/cleanable/dirt, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned) "eU" = ( /obj/effect/turf_decal/trimline/purple/filled/line, @@ -131,7 +131,7 @@ /obj/structure/frame/computer, /obj/item/shard, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned) "gN" = ( /obj/effect/turf_decal/trimline/purple/filled/end, @@ -239,7 +239,7 @@ /obj/effect/decal/cleanable/dirt, /obj/machinery/light/small/broken/directional/east, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned) "lO" = ( /obj/effect/turf_decal/bot_white, @@ -265,7 +265,7 @@ pixel_y = 28 }, /obj/machinery/recharger, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned) "lV" = ( /obj/machinery/computer/camera_advanced/shuttle_docker/whiteship{ @@ -274,7 +274,7 @@ }, /obj/effect/decal/cleanable/cobweb/cobweb2, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned) "mh" = ( /obj/effect/turf_decal/trimline/purple/filled/corner{ @@ -329,7 +329,7 @@ /area/shuttle/abandoned) "oI" = ( /obj/structure/bookcase/manuals/research_and_development, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned) "oT" = ( /obj/effect/turf_decal/trimline/purple/filled/end{ @@ -668,7 +668,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/door_assembly/door_assembly_com, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned) "DQ" = ( /obj/effect/turf_decal/trimline/purple/filled/line{ @@ -882,7 +882,7 @@ "Lf" = ( /obj/machinery/computer/shuttle/white_ship/bridge, /obj/effect/decal/cleanable/dirt, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/abandoned) "Lg" = ( /obj/structure/sign/warning/vacuum/external/directional/east, @@ -902,7 +902,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ dir = 4 }, -/obj/item/stack/tile/pod/light{ +/obj/item/stack/tile/mineral/plastitanium/pod/light{ pixel_x = 6; pixel_y = -4 }, @@ -1215,7 +1215,7 @@ }, /obj/effect/decal/cleanable/dirt, /obj/machinery/light/small/empty/directional/west, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/abandoned) "ZT" = ( /obj/effect/turf_decal/trimline/purple/filled/line{ diff --git a/_maps/shuttles/whiteship/whiteship_tram.dmm b/_maps/shuttles/whiteship/whiteship_tram.dmm index 27391955c3fd..2ea5c4d104a6 100644 --- a/_maps/shuttles/whiteship/whiteship_tram.dmm +++ b/_maps/shuttles/whiteship/whiteship_tram.dmm @@ -129,7 +129,7 @@ /area/shuttle/abandoned/cargo) "aw" = ( /obj/structure/sign/directions/supply, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/shuttle/abandoned/cargo) "aB" = ( /obj/structure/chair/comfy/shuttle, @@ -141,7 +141,7 @@ /area/shuttle/abandoned/cargo) "aG" = ( /obj/effect/turf_decal/mining, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/shuttle/abandoned/cargo) "aH" = ( /obj/effect/turf_decal/bot_red, @@ -151,7 +151,7 @@ "aI" = ( /obj/effect/turf_decal/caution/white, /obj/effect/spawner/random/trash/grime, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "aJ" = ( /obj/item/stack/sheet/mineral/titanium, @@ -204,10 +204,10 @@ /area/shuttle/abandoned/cargo) "aS" = ( /obj/effect/spawner/random/trash/food_packaging, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "aT" = ( -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/shuttle/abandoned/cargo) "aU" = ( /obj/machinery/porta_turret/centcom_shuttle/weak{ @@ -226,7 +226,7 @@ /obj/effect/spawner/random/trash/hobo_squat, /obj/effect/mob_spawn/corpse/human/assistant, /obj/structure/sign/warning/secure_area/directional/west, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "aX" = ( /obj/machinery/power/shuttle_engine/propulsion{ @@ -239,7 +239,7 @@ /area/shuttle/abandoned/engine) "aY" = ( /obj/structure/sign/warning/chem_diamond, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/shuttle/abandoned/cargo) "aZ" = ( /turf/closed/wall/mineral/plastitanium, @@ -295,7 +295,7 @@ /obj/effect/turf_decal/caution/white{ dir = 4 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "bl" = ( /obj/effect/turf_decal/bot_red, @@ -307,7 +307,7 @@ dir = 8 }, /obj/effect/spawner/random/trash/food_packaging, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "bp" = ( /obj/effect/decal/cleanable/blood, @@ -373,7 +373,7 @@ /obj/structure/sign/directions/supply{ dir = 1 }, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/shuttle/abandoned/cargo) "bC" = ( /obj/machinery/porta_turret/centcom_shuttle/weak{ @@ -577,7 +577,7 @@ }, /area/shuttle/abandoned/cargo) "cA" = ( -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "cB" = ( /obj/structure/chair/comfy/shuttle{ @@ -602,7 +602,7 @@ dir = 1 }, /obj/effect/spawner/random/exotic/ripley, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "cH" = ( /obj/effect/turf_decal/bot_red, @@ -1003,11 +1003,11 @@ "sR" = ( /obj/machinery/door/window/survival_pod/left/directional/west, /obj/effect/turf_decal/delivery/red, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "ua" = ( /obj/effect/spawner/random/trash/botanical_waste, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "ue" = ( /obj/effect/decal/cleanable/blood/tracks{ @@ -1231,7 +1231,7 @@ /area/shuttle/abandoned/crew) "KQ" = ( /obj/structure/sign/warning/secure_area/directional/east, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "LM" = ( /obj/structure/cable, @@ -1282,7 +1282,7 @@ "PI" = ( /obj/machinery/door/window/survival_pod/left/directional/east, /obj/effect/turf_decal/delivery/red, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/shuttle/abandoned/cargo) "PU" = ( /obj/effect/decal/cleanable/blood/gibs, diff --git a/_maps/templates/battlecruiser_starfury.dmm b/_maps/templates/battlecruiser_starfury.dmm index 24c52bda672d..3588ed6f485f 100644 --- a/_maps/templates/battlecruiser_starfury.dmm +++ b/_maps/templates/battlecruiser_starfury.dmm @@ -550,7 +550,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "ce" = ( /obj/machinery/shieldgen{ @@ -731,7 +731,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "cP" = ( /obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, @@ -754,7 +754,7 @@ /obj/structure/disposalpipe/segment{ dir = 6 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "cU" = ( /obj/docking_port/stationary/starfury_fighter/fighter_two{ @@ -797,7 +797,7 @@ /obj/structure/disposalpipe/segment{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "de" = ( /obj/machinery/button/door{ @@ -1027,11 +1027,11 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/door/airlock/multi_tile/public/glass, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "dV" = ( /obj/machinery/door/firedoor/heavy, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "dW" = ( /obj/structure/table, @@ -1075,7 +1075,7 @@ /turf/open/floor/iron, /area/shuttle/sbc_starfury) "ec" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "ef" = ( /obj/machinery/iv_drip, @@ -1108,7 +1108,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "ei" = ( /obj/structure/chair/stool/bar/directional/south, @@ -1120,7 +1120,7 @@ /area/shuttle/sbc_starfury) "ep" = ( /obj/machinery/light/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "et" = ( /obj/structure/cable, @@ -1177,7 +1177,7 @@ /area/shuttle/sbc_starfury) "eB" = ( /obj/effect/turf_decal/stripes/line, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "eC" = ( /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, @@ -1317,7 +1317,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "eS" = ( /obj/structure/table, @@ -1351,7 +1351,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "fb" = ( /obj/structure/disposalpipe/segment{ @@ -1360,7 +1360,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "fd" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, @@ -1412,7 +1412,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "fj" = ( /obj/machinery/porta_turret/syndicate/energy{ @@ -1475,7 +1475,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "fr" = ( /obj/effect/turf_decal/stripes/line{ @@ -1568,7 +1568,7 @@ /obj/machinery/door/firedoor/heavy, /obj/effect/mapping_helpers/airlock/cutaiwire, /obj/machinery/door/airlock/multi_tile/public/glass, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "fE" = ( /obj/structure/table/reinforced, @@ -1584,7 +1584,7 @@ /obj/machinery/door/firedoor/heavy, /obj/effect/mapping_helpers/airlock/cutaiwire, /obj/machinery/door/airlock/multi_tile/public/glass, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "fG" = ( /obj/structure/disposalpipe/segment, @@ -1592,7 +1592,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/door/firedoor/heavy, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "fK" = ( /obj/effect/mapping_helpers/airlock/cutaiwire, @@ -1726,7 +1726,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/vehicle/sealed/mecha/marauder/mauler/loaded, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "gH" = ( /obj/machinery/vending/boozeomat/syndicate, @@ -1763,7 +1763,7 @@ "gR" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "gS" = ( /obj/machinery/firealarm/directional/east, @@ -1853,14 +1853,14 @@ }, /obj/item/storage/belt/military, /obj/effect/turf_decal/siding/red, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "hc" = ( /obj/effect/turf_decal/delivery, /obj/machinery/door/poddoor/shutters/syndicate{ id = "syndie_battlecruiser_mech" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "hd" = ( /obj/machinery/disposal/bin, @@ -2071,7 +2071,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "hC" = ( /obj/structure/closet/secure_closet{ @@ -2112,7 +2112,7 @@ /obj/effect/turf_decal/stripes/corner{ dir = 1 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "hD" = ( /obj/structure/chair/stool/bar/directional/south, @@ -2202,7 +2202,7 @@ pixel_x = 3; pixel_y = -3 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "ia" = ( /obj/structure/rack{ @@ -2217,7 +2217,7 @@ pixel_x = 3; pixel_y = -3 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "ib" = ( /obj/structure/closet/secure_closet{ @@ -2249,7 +2249,7 @@ pixel_x = 3 }, /obj/structure/extinguisher_cabinet/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "ie" = ( /obj/structure/table/reinforced/plastitaniumglass, @@ -2309,7 +2309,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/effect/turf_decal/stripes/line, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "il" = ( /obj/structure/table, @@ -2361,7 +2361,7 @@ pixel_x = -1; pixel_y = 2 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "iq" = ( /obj/machinery/microwave, @@ -2436,7 +2436,7 @@ /obj/item/storage/belt/military, /obj/item/ammo_box/magazine/m7mm, /obj/item/gun/ballistic/automatic/l6_saw, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "iy" = ( /obj/item/storage/box/donkpockets{ @@ -2553,7 +2553,7 @@ }, /obj/item/clothing/mask/gas/syndicate, /obj/machinery/firealarm/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "iO" = ( /obj/structure/table, @@ -2589,7 +2589,7 @@ pixel_x = 8; pixel_y = 8 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "iP" = ( /obj/effect/turf_decal/bot, @@ -3125,7 +3125,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/light/small/directional/west, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "kK" = ( /obj/effect/turf_decal/bot, @@ -3598,7 +3598,7 @@ /obj/machinery/syndicatebomb{ pixel_y = -2 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "mY" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ @@ -3651,7 +3651,7 @@ /obj/structure/disposalpipe/segment{ dir = 9 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "nN" = ( /obj/effect/turf_decal/siding/thinplating_new/dark{ @@ -3706,7 +3706,7 @@ "oO" = ( /obj/structure/tank_dispenser/oxygen, /obj/effect/turf_decal/delivery/red, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "oX" = ( /obj/structure/cable, @@ -3720,7 +3720,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ dir = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "pm" = ( /obj/machinery/computer/records/medical/syndie, @@ -3792,7 +3792,7 @@ "qb" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "qf" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ @@ -3858,7 +3858,7 @@ dir = 1 }, /obj/structure/sign/warning/secure_area/directional/west, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "rr" = ( /obj/structure/cable, @@ -3899,7 +3899,7 @@ specialfunctions = 4 }, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "sh" = ( /obj/structure/cable, @@ -3922,12 +3922,12 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "sY" = ( /obj/structure/disposalpipe/segment, /obj/effect/turf_decal/stripes/line, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "te" = ( /obj/machinery/porta_turret/syndicate/energy{ @@ -4154,7 +4154,7 @@ "wC" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/firealarm/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "wN" = ( /obj/machinery/porta_turret/syndicate/energy{ @@ -4393,7 +4393,7 @@ /obj/machinery/door/poddoor/shutters/syndicate{ id = "syndie_battlecruiser_mech" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "AF" = ( /obj/machinery/airalarm/directional/west, @@ -4415,14 +4415,14 @@ /obj/effect/mapping_helpers/airlock/cutaiwire, /obj/machinery/door/airlock/multi_tile/public/glass, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "AQ" = ( /obj/effect/turf_decal/stripes/line{ dir = 1 }, /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "Be" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ @@ -4522,7 +4522,7 @@ dir = 1 }, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "Dd" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, @@ -4568,7 +4568,7 @@ "DV" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "Ex" = ( /obj/effect/turf_decal/stripes/line{ @@ -4765,7 +4765,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 1 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "GO" = ( /obj/effect/turf_decal/siding/thinplating_new/dark{ @@ -4810,7 +4810,7 @@ /area/shuttle/sbc_starfury) "HL" = ( /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "HP" = ( /obj/structure/dresser, @@ -4819,7 +4819,7 @@ "HT" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "HU" = ( /obj/effect/mapping_helpers/airlock/cyclelink_helper_multi{ @@ -4887,7 +4887,7 @@ /obj/item/ammo_box/magazine/sniper_rounds, /obj/item/gun/ballistic/rifle/sniper_rifle/syndicate, /obj/effect/mapping_helpers/airalarm/syndicate_access, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "Jm" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, @@ -4901,7 +4901,7 @@ dir = 1 }, /obj/structure/disposalpipe/segment, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "JF" = ( /obj/machinery/power/turbine/core_rotor, @@ -4940,7 +4940,7 @@ pixel_x = 3; pixel_y = -3 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/shuttle/sbc_starfury) "JP" = ( /obj/effect/turf_decal/stripes/line{ @@ -5021,7 +5021,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ dir = 4 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "Mt" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, @@ -5046,7 +5046,7 @@ "ME" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/light/floor, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "MK" = ( /obj/structure/disposalpipe/segment, @@ -5147,7 +5147,7 @@ /obj/structure/cable, /obj/machinery/airalarm/directional/south, /obj/effect/mapping_helpers/airalarm/syndicate_access, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "OG" = ( /obj/effect/turf_decal/bot, @@ -5223,7 +5223,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "PF" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ @@ -5421,12 +5421,12 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "SW" = ( /obj/effect/turf_decal/stripes/line, /obj/effect/decal/cleanable/blood/oil, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "Ta" = ( /obj/effect/turf_decal/stripes/line{ @@ -5541,7 +5541,7 @@ /area/shuttle/sbc_starfury) "Vr" = ( /obj/machinery/light/floor, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "Vt" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, @@ -5762,7 +5762,7 @@ name = "Mech Storage Shutters"; req_access = list("syndicate_leader") }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "Yt" = ( /obj/machinery/space_heater, @@ -5772,7 +5772,7 @@ "Yw" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/structure/cable, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "Za" = ( /obj/structure/cable, @@ -5782,7 +5782,7 @@ dir = 9 }, /obj/machinery/light/small/directional/east, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/shuttle/sbc_starfury) "Zl" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ diff --git a/_maps/templates/shelter_1.dmm b/_maps/templates/shelter_1.dmm index 1e5d6502e03c..ff83de3a64af 100644 --- a/_maps/templates/shelter_1.dmm +++ b/_maps/templates/shelter_1.dmm @@ -1,72 +1,72 @@ //MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE "a" = ( -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "b" = ( /obj/effect/turf_decal/mining/survival{ dir = 1 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "c" = ( /obj/structure/fans, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "d" = ( /obj/machinery/smartfridge/survival_pod/preloaded, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "e" = ( /obj/item/gps/computer, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "f" = ( /obj/effect/turf_decal/mining/survival{ dir = 8 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "g" = ( /obj/machinery/stasis/survival_pod, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "h" = ( -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "i" = ( /obj/structure/bed/pod, /obj/item/bedsheet/black, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "j" = ( /obj/effect/turf_decal/mining/survival{ dir = 4 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "k" = ( /obj/structure/table/survival_pod, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "l" = ( /obj/structure/tubes, /obj/structure/chair/comfy/black{ dir = 8 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "m" = ( /obj/effect/turf_decal/mining/survival, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "n" = ( /obj/structure/fans/tiny, /obj/machinery/door/airlock/survival_pod/glass, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "o" = ( /obj/effect/turf_decal/mining, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) (1,1,1) = {" diff --git a/_maps/templates/shelter_2.dmm b/_maps/templates/shelter_2.dmm index 38a742b85fd5..fb349b2d8570 100644 --- a/_maps/templates/shelter_2.dmm +++ b/_maps/templates/shelter_2.dmm @@ -1,12 +1,12 @@ //MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE "a" = ( -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "b" = ( /obj/effect/turf_decal/mining/survival{ dir = 1 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "c" = ( /obj/machinery/shower/directional/south, @@ -17,49 +17,49 @@ icon_state = "bathroom-closed"; open = 0 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "d" = ( /obj/effect/turf_decal/mining/survival{ dir = 8 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "e" = ( /obj/structure/fans, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "f" = ( /obj/machinery/smartfridge/survival_pod/preloaded, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "g" = ( /obj/item/gps/computer, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "i" = ( /obj/structure/toilet/secret{ secret_type = /obj/item/knife/combat/survival }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "j" = ( /obj/effect/turf_decal/mining/survival{ dir = 4 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "k" = ( /obj/machinery/stasis/survival_pod, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "l" = ( -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "m" = ( /obj/structure/bed/pod, /obj/item/bedsheet/black, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "n" = ( /obj/structure/window/reinforced/survival_pod/spawner/directional/west, @@ -80,12 +80,12 @@ /obj/item/storage/medkit/brute{ pixel_x = 5 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "q" = ( /obj/structure/tubes, /obj/item/kirbyplants/random, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "r" = ( /obj/structure/window/reinforced/survival_pod/spawner/directional/west, @@ -164,16 +164,16 @@ /area/misc/survivalpod) "C" = ( /obj/effect/turf_decal/mining/survival, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "D" = ( /obj/structure/fans/tiny, /obj/machinery/door/airlock/survival_pod/glass, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "E" = ( /obj/effect/turf_decal/mining, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) (1,1,1) = {" diff --git a/_maps/templates/shelter_3.dmm b/_maps/templates/shelter_3.dmm index f474297cc1ed..0cafc1c45d42 100644 --- a/_maps/templates/shelter_3.dmm +++ b/_maps/templates/shelter_3.dmm @@ -1,55 +1,55 @@ //MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE "a" = ( -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "b" = ( /obj/effect/turf_decal/mining/survival{ dir = 1 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "c" = ( -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/misc/survivalpod) "d" = ( /obj/effect/turf_decal/mining/survival{ dir = 1 }, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/misc/survivalpod) "e" = ( /obj/effect/turf_decal/mining/survival{ dir = 8 }, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/misc/survivalpod) "f" = ( /obj/structure/table/wood/fancy/black, /obj/machinery/chem_dispenser/drinks, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "g" = ( /obj/structure/table/wood/fancy/black, /obj/machinery/chem_dispenser/drinks/beer, /obj/machinery/light/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "h" = ( /obj/machinery/vending/boozeomat, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "i" = ( /obj/item/book/manual/wiki/barman_recipes, /obj/item/reagent_containers/cup/glass/shaker, /obj/item/rag, /obj/structure/table/wood/fancy/black, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "j" = ( /obj/structure/table/wood/fancy/black, /obj/item/clipboard, /obj/item/toy/figure/bartender, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "k" = ( /obj/structure/table/wood/fancy/black, @@ -60,24 +60,24 @@ /obj/item/storage/fancy/cigarettes/cigars/havana{ pixel_y = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "l" = ( /obj/structure/table/wood/fancy/black, /obj/effect/spawner/random/food_or_drink/keg, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "m" = ( /obj/machinery/light/directional/north, /obj/structure/closet/secure_closet/bar/all_access, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "n" = ( /obj/structure/disposalpipe/trunk{ dir = 4 }, /obj/machinery/disposal/bin, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "o" = ( /obj/effect/turf_decal/mining/survival{ @@ -86,21 +86,21 @@ /obj/structure/disposalpipe/junction{ dir = 4 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "p" = ( /obj/machinery/door/airlock/survival_pod/glass, /obj/effect/mapping_helpers/airlock/access/any/service/bar, /obj/effect/mapping_helpers/airlock/access/any/supply/mining, /obj/structure/fans/tiny, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "q" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "r" = ( /obj/structure/disposalpipe/segment, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/misc/survivalpod) "s" = ( /obj/structure/table/reinforced, @@ -109,11 +109,11 @@ pixel_y = 4 }, /obj/item/lighter, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "t" = ( /obj/structure/table/reinforced, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "u" = ( /obj/structure/table/reinforced, @@ -121,20 +121,20 @@ pixel_x = -4; pixel_y = 8 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "v" = ( /obj/machinery/door/window/survival_pod/left/directional/south{ req_one_access = list("bar","mining") }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "w" = ( /obj/effect/turf_decal/mining/survival{ dir = 4 }, /obj/structure/disposalpipe/segment, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/misc/survivalpod) "x" = ( /obj/structure/chair/stool/bar/directional/south, @@ -161,7 +161,7 @@ /obj/structure/disposalpipe/segment{ dir = 9 }, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/misc/survivalpod) "C" = ( /obj/structure/window/reinforced/survival_pod/spawner/directional/west, @@ -169,7 +169,7 @@ /obj/structure/window/reinforced/survival_pod/spawner/directional/north, /obj/structure/window/reinforced/survival_pod/spawner/directional/south, /obj/structure/grille, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/misc/survivalpod) "D" = ( /obj/structure/chair/comfy/black, @@ -177,7 +177,7 @@ /area/misc/survivalpod) "E" = ( /obj/machinery/door/airlock/survival_pod, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/misc/survivalpod) "F" = ( /obj/structure/table/wood/fancy, @@ -195,25 +195,25 @@ /obj/structure/toilet{ dir = 4 }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/misc/survivalpod) "H" = ( /obj/structure/sink/directional/east, /obj/structure/mirror/directional/west, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/misc/survivalpod) "I" = ( /obj/structure/sink/directional/west, /obj/machinery/light/small/directional/east, /obj/structure/urinal/directional/north, /obj/structure/mirror/directional/east, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/misc/survivalpod) "J" = ( /obj/effect/turf_decal/mining/survival{ dir = 4 }, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/misc/survivalpod) "K" = ( /obj/structure/chair/comfy/black{ @@ -239,21 +239,21 @@ /obj/item/soap/deluxe, /obj/machinery/light/small/directional/east, /obj/machinery/shower/directional/south, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/misc/survivalpod) "O" = ( /obj/effect/turf_decal/mining/survival{ dir = 4 }, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "P" = ( /obj/effect/turf_decal/mining/survival, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "Q" = ( /obj/effect/turf_decal/mining/survival, -/turf/closed/wall/mineral/titanium/survival/nodiagonal, +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal, /area/misc/survivalpod) "R" = ( /obj/machinery/door/airlock/survival_pod/glass, diff --git a/_maps/templates/shelter_t.dmm b/_maps/templates/shelter_t.dmm index 1e8ff8bc590f..393db9ccb01f 100644 --- a/_maps/templates/shelter_t.dmm +++ b/_maps/templates/shelter_t.dmm @@ -1,10 +1,10 @@ //MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE "a" = ( -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "e" = ( /obj/structure/sign/warning/biohazard, -/turf/closed/wall/mineral/titanium/survival/pod, +/turf/closed/wall/mineral/plastitanium/survival/pod, /area/misc/survivalpod) "C" = ( /obj/machinery/door/airlock/survival_pod/glass, @@ -12,7 +12,7 @@ id = "podtoilet"; name = "Privacy Shutters" }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) "H" = ( /obj/structure/toilet, @@ -26,7 +26,7 @@ name = "Privacy Shutters"; pixel_y = 6 }, -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/misc/survivalpod) (1,1,1) = {" diff --git a/_maps/virtual_domains/beach_bar.dmm b/_maps/virtual_domains/beach_bar.dmm index 8a17361fcac0..387b651040b3 100644 --- a/_maps/virtual_domains/beach_bar.dmm +++ b/_maps/virtual_domains/beach_bar.dmm @@ -22,7 +22,7 @@ /turf/open/floor/wood, /area/virtual_domain/fullbright) "aE" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/fullbright) "aZ" = ( /obj/machinery/light/small/directional/east, @@ -137,7 +137,7 @@ "fr" = ( /obj/item/melee/skateboard/hoverboard, /obj/machinery/light/directional/west, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/fullbright) "gh" = ( /obj/structure/flora/bush/stalky/style_random, @@ -157,7 +157,7 @@ /area/virtual_domain/fullbright) "hk" = ( /obj/structure/reagent_dispensers/watertank, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/fullbright) "hE" = ( /obj/structure/sign/departments/restroom/directional/east, @@ -193,7 +193,7 @@ density = 1 }, /obj/effect/decal/fakelattice, -/turf/open/floor/pod/light{ +/turf/open/floor/mineral/plastitanium/pod/light{ density = 1 }, /area/virtual_domain/fullbright) @@ -228,7 +228,7 @@ /area/virtual_domain/fullbright) "lq" = ( /obj/item/melee/skateboard/hoverboard, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/fullbright) "lB" = ( /obj/item/toy/seashell, @@ -471,7 +471,7 @@ /turf/open/floor/wood, /area/virtual_domain/fullbright) "xw" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/fullbright) "xJ" = ( /obj/structure/closet/cabinet, @@ -553,7 +553,7 @@ /area/virtual_domain/fullbright) "zw" = ( /obj/structure/punching_bag, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/fullbright) "zI" = ( /obj/structure/marker_beacon/indigo, @@ -589,7 +589,7 @@ "AI" = ( /obj/structure/table/reinforced, /obj/machinery/reagentgrinder, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/fullbright) "AP" = ( /obj/machinery/chem_dispenser/drinks/beer/fullupgrade{ @@ -633,7 +633,7 @@ /area/virtual_domain/fullbright) "BQ" = ( /obj/machinery/seed_extractor, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/fullbright) "Cb" = ( /obj/machinery/light/directional/north, @@ -714,7 +714,7 @@ /obj/structure/table/reinforced, /obj/item/secateurs, /obj/item/reagent_containers/cup/bottle/nutrient/ez, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/fullbright) "FS" = ( /obj/effect/turf_decal/sand, @@ -894,7 +894,7 @@ desc = "A sink used for washing one's hands and face. It looks rusty and home-made"; name = "old sink" }, -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/fullbright) "OZ" = ( /obj/structure/marker_beacon/teal, @@ -994,7 +994,7 @@ /area/virtual_domain/fullbright) "Uq" = ( /obj/structure/weightmachine/weightlifter, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/fullbright) "UU" = ( /obj/structure/flora/bush/large/style_random, diff --git a/_maps/virtual_domains/island_brawl.dmm b/_maps/virtual_domains/island_brawl.dmm index 009ccc6c76b9..0d1ae401994a 100644 --- a/_maps/virtual_domains/island_brawl.dmm +++ b/_maps/virtual_domains/island_brawl.dmm @@ -518,7 +518,7 @@ /turf/open/misc/beach/sand, /area/virtual_domain/fullbright) "fI" = ( -/turf/open/floor/pod, +/turf/open/floor/mineral/plastitanium/pod, /area/virtual_domain/protected_space/fullbright) "fJ" = ( /obj/effect/turf_decal/siding/wood{ @@ -587,7 +587,7 @@ density = 1 }, /obj/effect/decal/fakelattice, -/turf/open/floor/pod/light{ +/turf/open/floor/mineral/plastitanium/pod/light{ density = 1 }, /area/virtual_domain/fullbright) @@ -2342,7 +2342,7 @@ /turf/open/floor/plating, /area/virtual_domain) "DK" = ( -/turf/open/floor/pod/light, +/turf/open/floor/mineral/plastitanium/pod/light, /area/virtual_domain/protected_space/fullbright) "DL" = ( /obj/structure/table, @@ -2582,7 +2582,7 @@ /area/virtual_domain) "GD" = ( /obj/effect/landmark/bitrunning/permanent_exit, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/protected_space/fullbright) "GI" = ( /turf/open/floor/iron/dark/textured_large, diff --git a/_maps/virtual_domains/syndicate_assault.dmm b/_maps/virtual_domains/syndicate_assault.dmm index e20438e1c412..9f54d8125e1c 100644 --- a/_maps/virtual_domains/syndicate_assault.dmm +++ b/_maps/virtual_domains/syndicate_assault.dmm @@ -25,7 +25,7 @@ }, /obj/item/stack/sheet/glass/fifty, /obj/item/stack/rods/fifty, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/protected_space) "aO" = ( /obj/machinery/recharge_station, @@ -43,7 +43,7 @@ /turf/open/floor/mineral/plastitanium, /area/virtual_domain) "bG" = ( -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/protected_space) "cc" = ( /obj/structure/closet/crate/secure/gear{ @@ -229,7 +229,7 @@ }, /obj/item/melee/energy/sword/saber/red, /obj/machinery/light/small/directional/north, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/protected_space) "iW" = ( /obj/structure/table/reinforced, @@ -512,7 +512,7 @@ /obj/item/stack/sheet/mineral/uranium{ amount = 15 }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/protected_space) "sL" = ( /obj/structure/chair/comfy, @@ -962,7 +962,7 @@ /obj/structure/closet/crate/secure/gear{ req_access = list("syndicate") }, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/protected_space) "Nr" = ( /obj/structure/closet/crate/secure/gear{ @@ -975,14 +975,14 @@ amount = 30 }, /obj/machinery/light/small/directional/south, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/protected_space) "Of" = ( /obj/structure/closet/crate/secure/gear{ req_access = list("syndicate") }, /obj/item/disk/surgery/forgottenship, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/protected_space) "Ox" = ( /obj/machinery/atmospherics/components/unary/vent_pump, @@ -1016,7 +1016,7 @@ name = "Syndicate Vault Airlock" }, /obj/effect/mapping_helpers/airlock/access/all/syndicate/general, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/protected_space) "Qg" = ( /obj/machinery/suit_storage_unit/syndicate{ @@ -1164,7 +1164,7 @@ }, /obj/item/clothing/head/helmet/space/syndicate/black/engie, /obj/item/clothing/suit/space/syndicate/black/engie, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain/protected_space) "Yi" = ( /obj/effect/landmark/bitrunning/cache_spawn, diff --git a/_maps/virtual_domains/vaporwave.dmm b/_maps/virtual_domains/vaporwave.dmm index 569a65a812ba..26cb10748f95 100644 --- a/_maps/virtual_domains/vaporwave.dmm +++ b/_maps/virtual_domains/vaporwave.dmm @@ -248,7 +248,7 @@ "XJ" = ( /obj/structure/fans/tiny, /obj/machinery/door/airlock/hatch, -/turf/open/floor/pod/dark, +/turf/open/floor/mineral/plastitanium/pod/dark, /area/virtual_domain) "Yo" = ( /obj/structure/statue/sandstone/venus{ diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm index 42e539ed6a81..2c57b4ccfdbb 100644 --- a/code/__DEFINES/icon_smoothing.dm +++ b/code/__DEFINES/icon_smoothing.dm @@ -141,7 +141,7 @@ DEFINE_BITFIELD(smoothing_junction, list( #define SMOOTH_GROUP_RED_ROCK_WALLS S_TURF(62) ///turf/closed/mineral/asteroid, /turf/closed/mineral/random/stationside/asteroid #define SMOOTH_GROUP_SHALE_WALLS S_TURF(63) ///turf/closed/mineral/random/volcanic/shale #define SMOOTH_GROUP_BOSS_WALLS S_TURF(64) ///turf/closed/indestructible/riveted/boss -#define SMOOTH_GROUP_SURVIVAL_TITANIUM_WALLS S_TURF(65) ///turf/closed/wall/mineral/titanium/survival +#define SMOOTH_GROUP_SURVIVAL_TITANIUM_WALLS S_TURF(65) ///turf/closed/wall/mineral/plastitanium/survival #define SMOOTH_GROUP_TURF_OPEN_CLIFF S_TURF(66) ///turf/open/cliff #define SMOOTH_GROUP_HIEROPHANT S_TURF(67) ///turf/closed/indestructible/riveted/hierophant #define SMOOTH_GROUP_PLASTINUM_WALLS S_TURF(68) ///turf/closed/indestructible/riveted/plastinum @@ -164,7 +164,7 @@ DEFINE_BITFIELD(smoothing_junction, list( #define SMOOTH_GROUP_ABDUCTOR_WALLS S_OBJ(11) ///turf/closed/wall/mineral/abductor, /obj/structure/falsewall/abductor #define SMOOTH_GROUP_TITANIUM_WALLS S_OBJ(12) ///turf/closed/wall/mineral/titanium, /obj/structure/falsewall/titanium #define SMOOTH_GROUP_PLASTITANIUM_WALLS S_OBJ(14) ///turf/closed/wall/mineral/plastitanium, /obj/structure/falsewall/plastitanium -#define SMOOTH_GROUP_SURVIVAL_TITANIUM_POD S_OBJ(15) ///turf/closed/wall/mineral/titanium/survival/pod, /obj/machinery/door/airlock/survival_pod, /obj/structure/window/reinforced/shuttle/survival_pod +#define SMOOTH_GROUP_SURVIVAL_TITANIUM_POD S_OBJ(15) ///turf/closed/wall/mineral/plastitanium/survival/pod, /obj/machinery/door/airlock/survival_pod, /obj/structure/window/reinforced/shuttle/survival_pod #define SMOOTH_GROUP_HIERO_WALL S_OBJ(16) ///obj/effect/temp_visual/elite_tumor_wall, /obj/effect/temp_visual/hierophant/wall #define SMOOTH_GROUP_BAMBOO_WALLS S_OBJ(17) //![/turf/closed/wall/mineral/bamboo, /obj/structure/falsewall/bamboo] diff --git a/code/_globalvars/lists/girder_wall_recipes.dm b/code/_globalvars/lists/girder_wall_recipes.dm index d4a015c6a4e4..75c9120d07fa 100644 --- a/code/_globalvars/lists/girder_wall_recipes.dm +++ b/code/_globalvars/lists/girder_wall_recipes.dm @@ -24,6 +24,10 @@ GLOBAL_LIST_INIT(material_girder_wall_recipes, init_material_girder_wall_recipes /turf/closed/wall/mineral/abductor, /turf/closed/wall/mineral/titanium, /turf/closed/wall/mineral/plastitanium, + /turf/closed/wall/mineral/plastitanium/wall_fill, + /turf/closed/wall/mineral/plastitanium/darkpod, + /turf/closed/wall/mineral/plastitanium/redpod, + /turf/closed/wall/mineral/plastitanium/survival, /turf/closed/wall/mineral/cult, /turf/closed/wall/mineral/bronze, /turf/closed/wall/material, @@ -44,6 +48,11 @@ GLOBAL_LIST_INIT(material_girder_wall_recipes, init_material_girder_wall_recipes /obj/structure/falsewall/abductor, /obj/structure/falsewall/titanium, /obj/structure/falsewall/plastitanium, + /obj/structure/falsewall/plastitanium, + /obj/structure/falsewall/plastitanium/wall_fill, + /obj/structure/falsewall/plastitanium/darkpod, + /obj/structure/falsewall/plastitanium/redpod, + /obj/structure/falsewall/plastitanium/survival, /obj/structure/falsewall/material, ), tram_types = list( diff --git a/code/game/objects/effects/spawners/random/decoration.dm b/code/game/objects/effects/spawners/random/decoration.dm index 8392e03e39ec..baa1abd9c2bb 100644 --- a/code/game/objects/effects/spawners/random/decoration.dm +++ b/code/game/objects/effects/spawners/random/decoration.dm @@ -12,9 +12,9 @@ /obj/item/stack/sheet/bronze/thirty = 20, /obj/item/stack/tile/noslip{amount = 20} = 10, /obj/item/stack/sheet/plastic{amount = 30} = 10, - /obj/item/stack/tile/pod{amount = 20} = 4, - /obj/item/stack/tile/pod/light{amount = 20} = 3, - /obj/item/stack/tile/pod/dark{amount = 20} = 3, + /obj/item/stack/tile/mineral/plastitanium/pod{amount = 20} = 4, + /obj/item/stack/tile/mineral/plastitanium/pod/light{amount = 20} = 3, + /obj/item/stack/tile/mineral/plastitanium/pod/dark{amount = 20} = 3, ) /obj/effect/spawner/random/decoration/carpet diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm index a140857b5710..6b6f893ed8fa 100644 --- a/code/game/objects/items/stacks/sheets/mineral.dm +++ b/code/game/objects/items/stacks/sheets/mineral.dm @@ -365,6 +365,7 @@ GLOBAL_LIST_INIT(titanium_recipes, list ( \ GLOBAL_LIST_INIT(plastitanium_recipes, list ( \ new/datum/stack_recipe("plastitanium tile", /obj/item/stack/tile/mineral/plastitanium, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ + new/datum/stack_recipe("plastitanium wall filler", /obj/item/stack/wall_filling/plastitanium/basic, 2, 1, 10, crafting_flags = NONE, category = CAT_STRUCTURE), \ new/datum/stack_recipe("plastitanium platform", /obj/structure/platform/plastitanium, 2, time = 3 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, trait_booster = TRAIT_QUICK_BUILD, trait_modifier = 0.75, category = CAT_STRUCTURE), \ )) diff --git a/code/game/objects/items/stacks/tiles/tile_mineral.dm b/code/game/objects/items/stacks/tiles/tile_mineral.dm index 6650528d012a..b5cf7f8ff57a 100644 --- a/code/game/objects/items/stacks/tiles/tile_mineral.dm +++ b/code/game/objects/items/stacks/tiles/tile_mineral.dm @@ -13,7 +13,7 @@ if(!tool.use_tool(src, user, 0, volume=40)) return ITEM_INTERACT_BLOCKING var/sheet_type = text2path("/obj/item/stack/sheet/mineral/[mineralType]") - var/obj/item/stack/sheet/mineral/new_item = new sheet_type(user.loc) + var/obj/item/stack/sheet/mineral/new_item = new sheet_type(user.drop_location()) user.visible_message(span_notice("[user] shaped [src] into [new_item] with [tool]."), \ span_notice("You shaped [src] into [new_item] with [tool]."), \ span_hear("You hear welding.")) @@ -210,6 +210,11 @@ tile_reskin_types = list( /obj/item/stack/tile/mineral/plastitanium, /obj/item/stack/tile/mineral/plastitanium/red, + /obj/item/stack/tile/mineral/plastitanium/pod, + /obj/item/stack/tile/mineral/plastitanium/pod/light, + /obj/item/stack/tile/mineral/plastitanium/pod/dark, + /obj/item/stack/tile/mineral/plastitanium/pod/red, + /obj/item/stack/tile/mineral/plastitanium/pod/redlight, ) /obj/item/stack/tile/mineral/plastitanium/red @@ -220,6 +225,47 @@ icon_state = "tile_plastitanium_red" merge_type = /obj/item/stack/tile/mineral/plastitanium/red +/obj/item/stack/tile/mineral/plastitanium/pod + name = "pod floor tile" + singular_name = "pod floor tile" + desc = "A grooved floor tile." + icon_state = "tile_pod" + inhand_icon_state = "tile-pod" + turf_type = /turf/open/floor/mineral/plastitanium/pod + merge_type = /obj/item/stack/tile/mineral/plastitanium/pod + +/obj/item/stack/tile/mineral/plastitanium/pod/light + name = "light pod floor tile" + singular_name = "light pod floor tile" + desc = "A lightly colored grooved floor tile." + icon_state = "tile_podlight" + turf_type = /turf/open/floor/mineral/plastitanium/pod/light + merge_type = /obj/item/stack/tile/mineral/plastitanium/pod/light + +/obj/item/stack/tile/mineral/plastitanium/pod/dark + name = "dark pod floor tile" + singular_name = "dark pod floor tile" + desc = "A darkly colored grooved floor tile." + icon_state = "tile_poddark" + turf_type = /turf/open/floor/mineral/plastitanium/pod/dark + merge_type = /obj/item/stack/tile/mineral/plastitanium/pod/dark + +/obj/item/stack/tile/mineral/plastitanium/pod/red + name = "red pod floor tile" + singular_name = "dark pod floor tile" + desc = "A red colored grooved floor tile." + icon_state = "tile_pod_red" + turf_type = /turf/open/floor/mineral/plastitanium/pod/red + merge_type = /obj/item/stack/tile/mineral/plastitanium/pod/red + +/obj/item/stack/tile/mineral/plastitanium/pod/redlight + name = "light red pod floor tile" + singular_name = "light red pod floor tile" + desc = "A light red colored grooved floor tile." + icon_state = "tile_podlight_red" + turf_type = /turf/open/floor/mineral/plastitanium/pod/redlight + merge_type = /obj/item/stack/tile/mineral/plastitanium/pod/redlight + /obj/item/stack/tile/mineral/snow name = "snow tile" singular_name = "snow tile" diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm index eb4ae72db327..5505bfa2f73c 100644 --- a/code/game/objects/items/stacks/tiles/tile_types.dm +++ b/code/game/objects/items/stacks/tiles/tile_types.dm @@ -1110,37 +1110,6 @@ turf_type = /turf/open/floor/circuit/red/anim merge_type = /obj/item/stack/tile/circuit/red/anim -//Pod floor -/obj/item/stack/tile/pod - name = "pod floor tile" - singular_name = "pod floor tile" - desc = "A grooved floor tile." - icon_state = "tile_pod" - inhand_icon_state = "tile-pod" - turf_type = /turf/open/floor/pod - merge_type = /obj/item/stack/tile/pod - tile_reskin_types = list( - /obj/item/stack/tile/pod, - /obj/item/stack/tile/pod/light, - /obj/item/stack/tile/pod/dark, - ) - -/obj/item/stack/tile/pod/light - name = "light pod floor tile" - singular_name = "light pod floor tile" - desc = "A lightly colored grooved floor tile." - icon_state = "tile_podlight" - turf_type = /turf/open/floor/pod/light - merge_type = /obj/item/stack/tile/pod/light - -/obj/item/stack/tile/pod/dark - name = "dark pod floor tile" - singular_name = "dark pod floor tile" - desc = "A darkly colored grooved floor tile." - icon_state = "tile_poddark" - turf_type = /turf/open/floor/pod/dark - merge_type = /obj/item/stack/tile/pod/dark - /obj/item/stack/tile/plastic name = "plastic tile" singular_name = "plastic floor tile" diff --git a/code/game/objects/items/stacks/wall_filling.dm b/code/game/objects/items/stacks/wall_filling.dm new file mode 100644 index 000000000000..e16e71e82fb0 --- /dev/null +++ b/code/game/objects/items/stacks/wall_filling.dm @@ -0,0 +1,134 @@ +// Wall-fillings; items you can re-skin in hand and place onto griders to create different wall types. + +/obj/item/stack/wall_filling + name = "wall filling" + singular_name = "wall filler" + desc = "An empty wall filling. This should not exist." + //wall-fillings will generaly use the inhands of the sheet they are made from. + lefthand_file = 'icons/mob/inhands/items/sheets_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/sheets_righthand.dmi' + icon = 'icons/obj/stack_objects.dmi' + w_class = WEIGHT_CLASS_NORMAL + force = 2 + throwforce = 2 + throw_speed = 2 + throw_range = 5 + max_amount = 60 + novariants = TRUE + material_flags = MATERIAL_EFFECTS + usable_for_construction = TRUE + merge_type = /obj/item/stack/wall_filling + /// the main thing it exists for, list of fillings to reskin into. + var/list/wall_reskin_types + /// what it should be broken down back into when using a welder. Provided as a path. + var/made_from + +/obj/item/stack/wall_filling/examine(mob/user) + . = ..() + . += span_notice("Use while in your hand to change what type of [src] you want.") + if(throwforce && !is_cyborg) //do not want to divide by zero or show the message to borgs who can't throw + var/damage_value + switch(ceil(MAX_LIVING_HEALTH / throwforce)) //throws to crit a human + if(1 to 3) + damage_value = "superb" + if(4 to 6) + damage_value = "great" + if(7 to 9) + damage_value = "good" + if(10 to 12) + damage_value = "fairly decent" + if(13 to 15) + damage_value = "mediocre" + if(!damage_value) + return + . += span_notice("Those could work as a [damage_value] throwing weapon.") + +/obj/item/stack/wall_filling/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if (tool.tool_behaviour != TOOL_WELDER) + return ..() + + if(!made_from) + to_chat(user, span_warning("You can not reform this!")) + stack_trace("A wall filling of type [type] doesn't have its made_from set.") + return + if(!tool.use_tool(src, user, 0, volume=40)) + return + var/obj/item/new_item = new made_from(user.drop_location(), 2) + user.visible_message(span_notice("[user] shaped [src] into [new_item] with [tool]."), \ + span_notice("You shaped [src] into [new_item] with [tool]."), \ + span_hear("You hear welding.")) + var/holding = user.is_holding(src) + use(1) + if(holding && QDELETED(src)) + user.put_in_hands(new_item) + return ITEM_INTERACT_SUCCESS + +GLOBAL_LIST_EMPTY(wall_reskin_lists) + +/** + * Yup, literally copied over from tile reskinning, except we don't need a dir as walls don't rotate, so, that much simpler. + */ +/obj/item/stack/wall_filling/proc/wall_reskin_list(list/values) + var/string_id = values.Join("-") + . = GLOB.wall_reskin_lists[string_id] + if(.) + return + for(var/path in values) + var/obj/item/stack/wall_filling/type_cast_path = path + values[path] = image(icon = initial(type_cast_path.icon), icon_state = initial(type_cast_path.icon_state)) + return GLOB.wall_reskin_lists[string_id] = values + +/obj/item/stack/wall_filling/attack_self(mob/user) + var/obj/item/stack/wall_filling/choice = show_radial_menu(user, src, wall_reskin_types, radius = 48, require_near = TRUE) + if(!choice || choice == type) + return + choice = new choice(user.drop_location(), amount) + moveToNullspace() + if(!QDELETED(choice)) + user.put_in_active_hand(choice) + qdel(src) + +/obj/item/stack/wall_filling/Initialize(mapload, new_amount, merge = TRUE, list/mat_override=null, mat_amt=1) + . = ..() + if(wall_reskin_types) + wall_reskin_types = wall_reskin_list(wall_reskin_types) + +/obj/item/stack/wall_filling/plastitanium + name = "plastitanium wall filling" + singular_name = "plastitanium wall filler" + desc = "A filling for a standard plastitanium wall." + icon_state = "plastitanium-wall-fill" + inhand_icon_state = "sheet-plastitanium" + mats_per_unit = list(/datum/material/alloy/plastitanium=SHEET_MATERIAL_AMOUNT*2) + merge_type = /obj/item/stack/wall_filling/plastitanium + made_from = /obj/item/stack/sheet/mineral/plastitanium + wall_reskin_types = list( + /obj/item/stack/wall_filling/plastitanium/basic, + /obj/item/stack/wall_filling/plastitanium/survivalpod, + /obj/item/stack/wall_filling/plastitanium/pod, + /obj/item/stack/wall_filling/plastitanium/redpod, + ) + +/obj/item/stack/wall_filling/plastitanium/basic + merge_type = /obj/item/stack/wall_filling/plastitanium/basic + +/obj/item/stack/wall_filling/plastitanium/survivalpod + name = "survival pod wall filling" + singular_name = "survival pod wall filler" + icon_state = "survivalpod-wall-fill" + desc = "A filling for a survival pod wall, often usen for mining in hostile enviroments." + merge_type = /obj/item/stack/wall_filling/plastitanium/survivalpod + +/obj/item/stack/wall_filling/plastitanium/pod + name = "dark shuttle wall filling" + singular_name = "dark shuttle wall filler" + icon_state = "pod-wall-fill" + desc = "A filling for a dark shuttle wall. Dark mode on." + merge_type = /obj/item/stack/wall_filling/plastitanium/pod + +/obj/item/stack/wall_filling/plastitanium/redpod + name = "red dark shuttle wall filling" + singular_name = "red dark shuttle wall filler" + icon_state = "redpod-wall-fill" + desc = "A filling for a red dark shuttle wall. Quite the menacing vibe." + merge_type = /obj/item/stack/wall_filling/plastitanium/redpod diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm index 6b66b2e5488a..989b55320955 100644 --- a/code/game/objects/structures/false_walls.dm +++ b/code/game/objects/structures/false_walls.dm @@ -386,6 +386,55 @@ smoothing_groups = SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_WALLS canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_PLASTITANIUM_WALLS +/obj/structure/falsewall/plastitanium + name = "wall" + desc = "An evil wall of plasma and titanium." + fake_icon = 'icons/turf/walls/plastitanium_wall.dmi' + icon_state = "plastitanium_wall-open" + base_icon_state = "plastitanium_wall" + mineral = /obj/item/stack/sheet/mineral/plastitanium + walltype = /turf/closed/wall/mineral/plastitanium + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_WALLS + canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_PLASTITANIUM_WALLS + +/obj/structure/falsewall/plastitanium/wall_fill + mineral = /obj/item/stack/wall_filling/plastitanium/basic + walltype = /turf/closed/wall/mineral/plastitanium/wall_fill + +/obj/structure/falsewall/plastitanium/darkpod + name = "dark pod wall" + desc = "An easily-compressible wall used for temporary shelter." + fake_icon = 'icons/turf/walls/dark_pod.dmi' + icon_state = "dark_pod_walls-open" + base_icon_state = "dark_pod_walls" + mineral = /obj/item/stack/wall_filling/plastitanium/pod + walltype = /turf/closed/wall/mineral/plastitanium/darkpod + smoothing_groups = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + +/obj/structure/falsewall/plastitanium/redpod + name = "red pod wall" + desc = "An easily-compressible wall used for temporary shelter." + fake_icon = 'icons/turf/walls/red_pod.dmi' + icon_state = "red_pod_walls-open" + base_icon_state = "red_pod_walls" + mineral = /obj/item/stack/wall_filling/plastitanium/redpod + walltype = /turf/closed/wall/mineral/plastitanium/redpod + smoothing_groups = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + +/obj/structure/falsewall/plastitanium/survival + name = "pod wall" + desc = "An easily-compressible wall used for temporary shelter." + fake_icon = 'icons/turf/walls/survival_pod_walls.dmi' + icon_state = "survival_pod_walls-open" + base_icon_state = "survival_pod_walls" + mineral = /obj/item/stack/wall_filling/plastitanium/survivalpod + walltype = /turf/closed/wall/mineral/plastitanium/survival + smoothing_groups = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + /obj/structure/falsewall/material name = "wall" desc = "A huge chunk of material used to separate rooms." diff --git a/code/game/turfs/closed/wall/mineral_walls.dm b/code/game/turfs/closed/wall/mineral_walls.dm index 23700a6c9495..81dace7b86ca 100644 --- a/code/game/turfs/closed/wall/mineral_walls.dm +++ b/code/game/turfs/closed/wall/mineral_walls.dm @@ -304,28 +304,6 @@ . = ..() copy_to_turf.transform = transform -/turf/closed/wall/mineral/titanium/survival - name = "pod wall" - desc = "An easily-compressible wall used for temporary shelter." - icon = 'icons/turf/walls/survival_pod_walls.dmi' - icon_state = "survival_pod_walls-0" - base_icon_state = "survival_pod_walls" - smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS - canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_TITANIUM_WALLS - rust_resistance = RUST_RESISTANCE_TITANIUM - -/turf/closed/wall/mineral/titanium/survival/nodiagonal - icon = 'icons/turf/walls/survival_pod_walls.dmi' - icon_state = "survival_pod_walls-0" - base_icon_state = "survival_pod_walls" - smoothing_flags = SMOOTH_BITMASK - rust_resistance = RUST_RESISTANCE_TITANIUM - -/turf/closed/wall/mineral/titanium/survival/pod - smoothing_groups = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS - canSmoothWith = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD - rust_resistance = RUST_RESISTANCE_TITANIUM - /turf/closed/wall/mineral/titanium/rust_turf(magic = FALSE) if(HAS_TRAIT(src, TRAIT_RUSTY)) ChangeTurf(/turf/closed/wall/rust) @@ -349,13 +327,61 @@ custom_materials = list(/datum/material/alloy/plastitanium = SHEET_MATERIAL_AMOUNT*2) rust_resistance = RUST_RESISTANCE_TITANIUM +/turf/closed/wall/mineral/plastitanium/wall_fill + sheet_type = /obj/item/stack/wall_filling/plastitanium/basic + +/turf/closed/wall/mineral/plastitanium/darkpod + name = "dark pod wall" + desc = "An easily-compressible wall used for temporary shelter." + sheet_type = /obj/item/stack/wall_filling/plastitanium/pod + icon = 'icons/turf/walls/dark_pod.dmi' + icon_state = "dark_pod_walls-0" + base_icon_state = "dark_pod_walls" + smoothing_groups = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + rust_resistance = RUST_RESISTANCE_TITANIUM + +/turf/closed/wall/mineral/plastitanium/redpod + name = "red pod wall" + desc = "An easily-compressible wall used for temporary shelter." + sheet_type = /obj/item/stack/wall_filling/plastitanium/redpod + icon = 'icons/turf/walls/red_pod.dmi' + icon_state = "red_pod_walls-0" + base_icon_state = "red_pod_walls" + smoothing_groups = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + rust_resistance = RUST_RESISTANCE_TITANIUM + +/turf/closed/wall/mineral/plastitanium/survival + name = "pod wall" + desc = "An easily-compressible wall used for temporary shelter." + icon = 'icons/turf/walls/survival_pod_walls.dmi' + icon_state = "survival_pod_walls-0" + base_icon_state = "survival_pod_walls" + smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS + smoothing_groups = SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_TITANIUM_WALLS + rust_resistance = RUST_RESISTANCE_TITANIUM + +/turf/closed/wall/mineral/plastitanium/survival/nodiagonal + icon = 'icons/turf/walls/survival_pod_walls.dmi' + icon_state = "survival_pod_walls-0" + base_icon_state = "survival_pod_walls" + smoothing_flags = SMOOTH_BITMASK + rust_resistance = RUST_RESISTANCE_TITANIUM + +/turf/closed/wall/mineral/plastitanium/survival/pod + sheet_type = /obj/item/stack/wall_filling/plastitanium/survivalpod + smoothing_groups = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + rust_resistance = RUST_RESISTANCE_TITANIUM + /turf/closed/wall/mineral/plastitanium/rust_turf(magic = FALSE) if(HAS_TRAIT(src, TRAIT_RUSTY)) ChangeTurf(/turf/closed/wall/rust) return TRUE return ..() - /turf/closed/wall/mineral/plastitanium/nodiagonal icon = MAP_SWITCH('icons/turf/walls/plastitanium_wall.dmi', 'icons/turf/walls/misc_wall.dmi') icon_state = MAP_SWITCH("plastitanium_wall-0", "plastitanium_nd") diff --git a/code/game/turfs/open/floor/mineral_floor.dm b/code/game/turfs/open/floor/mineral_floor.dm index 8456afc084b7..e6a6e91b529b 100644 --- a/code/game/turfs/open/floor/mineral_floor.dm +++ b/code/game/turfs/open/floor/mineral_floor.dm @@ -155,6 +155,8 @@ /turf/open/floor/mineral/titanium/tiled/purple/airless initial_gas_mix = AIRLESS_ATMOS + + //PLASTITANIUM (syndieshuttle) /turf/open/floor/mineral/plastitanium name = "shuttle floor" @@ -176,6 +178,27 @@ /turf/open/floor/mineral/plastitanium/red/airless initial_gas_mix = AIRLESS_ATMOS +/turf/open/floor/mineral/plastitanium/pod + name = "pod floor" + icon_state = "podfloor" + floor_tile = /obj/item/stack/tile/mineral/plastitanium/pod + +/turf/open/floor/mineral/plastitanium/pod/light + icon_state = "podfloor_light" + floor_tile = /obj/item/stack/tile/mineral/plastitanium/pod/light + +/turf/open/floor/mineral/plastitanium/pod/dark + icon_state = "podfloor_dark" + floor_tile = /obj/item/stack/tile/mineral/plastitanium/pod/dark + +/turf/open/floor/mineral/plastitanium/pod/redlight + icon_state = "podfloor_red_light" + floor_tile = /obj/item/stack/tile/mineral/plastitanium/pod/redlight + +/turf/open/floor/mineral/plastitanium/pod/red + icon_state = "podfloor_red" + floor_tile = /obj/item/stack/tile/mineral/plastitanium/pod/red + //Used in SnowCabin.dm /turf/open/floor/mineral/plastitanium/red/snow_cabin temperature = ICEBOX_MIN_TEMPERATURE diff --git a/code/game/turfs/open/floor/misc_floor.dm b/code/game/turfs/open/floor/misc_floor.dm index 9ce464c68c73..ba1c0a6e0344 100644 --- a/code/game/turfs/open/floor/misc_floor.dm +++ b/code/game/turfs/open/floor/misc_floor.dm @@ -136,20 +136,6 @@ /turf/open/floor/circuit/red/telecomms initial_gas_mix = TCOMMS_ATMOS -/turf/open/floor/pod - name = "pod floor" - icon_state = "podfloor" - floor_tile = /obj/item/stack/tile/pod - -/turf/open/floor/pod/light - icon_state = "podfloor_light" - floor_tile = /obj/item/stack/tile/pod/light - -/turf/open/floor/pod/dark - icon_state = "podfloor_dark" - floor_tile = /obj/item/stack/tile/pod/dark - - /turf/open/floor/noslip name = "high-traction floor" icon_state = "noslip" diff --git a/icons/obj/stack_objects.dmi b/icons/obj/stack_objects.dmi index 8504bbc469065700761df6e7fa6bfb4edf58f50f..16899b24b4c89944a7d5bf770aac4c1f0fd48eb3 100644 GIT binary patch delta 21579 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 delta 20348 zcmZs?1yoeu7e6{kmy}3J2nZ-CNC;9&iKKvZr?hm=ML23i@>6DZlni*n< zd6)0+|K57*t+$w&b?4qY=j^lNvp;*^L&0aPkPFNhRv_C)Q^!rl($(DMqqEybCr1dx zD?Klz({VwBG-UKl18;vyx94Ni!XrQ3T%CV^%$7T--&T{8ePfB(w%IRO4l>jAC_7^0 zz4w6li1>6kEVqAbYj5-rXuTfF1X{S3b;Q^oFIYsk|K+7>CC$t^4a)w`Jey;?@@dyo1riB2 zmcF=n^t@a^3vMc@2uJCDg}uFgMljP(E?I&iY|#J)5q<@x2`tg`ZX@kKQ%z6D5Y3>IuEj0TfS#3(8a$ap=jZngLq1m%{cBSv^xQ34Roj!?%KW7Y# zm`Y6t)Rf$tBbs-V+`{clb0Uqf9p7+YzqT(DY$3s=MRL#!l3R^(hU=NlC=LnDP3)Ss zriR1ngXEJ1Z%u+14;$=?Rvfaf?l(%-uAOtx&7Bf+9p&cNSl$X*1S(h8KUT1$%CSx$ z*WG3Dtrr>#JNqi6yDXl%UGbLo8MY24pEsW!z-P#(s_KNb;e++rPyR*5X;}U#!)q!_kW8QxBItYBuBzT;3;mubIvC>j}YogKYOu%oe)F?#@b@#%_%HaE|zwS{$L7 z%05wa)foYLa7`fWAhG)4`zo5@=rsJt<$sAkd`VY2-GQ{jP;nS<%E zC9wNsIzYZ+(($LO)$gU&p9koWZ8JJg@20MT_V(c-f#cm1gFgpD?T@gQR+)VYl8y$h z=2(iTK30^ocwU&eW)gGCaN?%@()&W-=AuQec)b0=FsANp4f!F*w7yN2vZ+ZFY<1_Y z(K9V_zBqisFr}RudCp9?G=ZHmvX438VXOd*@Q=(8w(X-$Gnn0a3s$Sed0rQZ3iktNXVD$T1a0Z;^@9)#|5EF$Lhm=m8a7K z!3@Q!7?tC!J>_}9Dyh}UbpPUyKE`Xr5u(=UP2Lc;{gyRJP)uRA`?W2MF=`lkT-yVP zX%Fo^RvkRXT=j83J{=@={k?#b@)TbPPcRZo-nn`&5SL-uQU-<3U~@k3%}<~9ZO3{~ zKCuGfvrC?)wSlHZhuvO!S<%{+B z383C{U`NuXVTSf#D3Q+FVl?qE=+IhgtC1>0ToF3bhV~n|0~0jnla`>^7E46Luo`DD$zpVZO0d=3sb%Rap_KTGkq@&NzNQc{Kw&ToA7Ih(7i zPra?G1xBsgmJd&ds$k}a^~|@H0ltRa?KSwjNC^t?QFF@OslV-z~2) zD=J5keoQNQy-JcNb5#n{56)B9nPgEXP(Tb7dazxSf7YO{->-@YSVRuxj5pB%jS zcF)n;f{$mfc#NdE);dbA&J|G$G@%TzCKgLO_Yp~_WrDvsh2Gg+kr)~yq>KGX@ z?kDq*g#DedqIuOSR=(_|mDDv3NcxF>ftf^P@k*4vY`nCI-y3j*N$@|e6wzURO=uU!M7 zeKUE!Nd(&i^N-|A5`T}vm&KpmY@jMz-l4jVsvS+P@8Tk7`3XzPwZLRVS z3l)sjGY_rolkqWrJZF|FO$^Ta*mQ+s6M`vlk)m_?xSiLSF>zuz*z1d45c!uBq7Ua; z(QaYiI*xw9;D^oHMO)Oyr9eL8#p9*`^3PtIvACbQ%Lp~iyCNV=`Ehjk9XjG5VrF_)JxO)Zo??iA?s_L2Q;&t&R;N^*E=UDq z?hDL`#Tp@Exw@Lzu$2+2|HwgDH^XA#$<*W|>y(M)#URr3GW97ywKDll|4CvOU(?va zs1rd+ei);>+1CBWq1hwX%x$O288#pgH`D*s2a?Xet|5%XJu6P71u$2Oqbm9SfQFvE zB_5+_QnRsOWEzy6{<21UlZB4!-tpkFM7Es5NGrnjD>@>{;hpC}^S%(xkw^A(F%-(J zzgbM;Bu^O%Ka~TdB<&~PM#@*AP(nw&0ZtU%9M4SaBjv72dEZaLD<{9HciQrQY4kZ0 z_mB3wooOe4q~do(WFGo$i7Pc7L$%RV1)`7`rDl7|to(cS-#pG9g~8s7&-s=eRl3Dd zlDxWV=$UEUJ`~67Nh5hU{P=V=^G68tDO=#wYie1cNxU8)Pdo0IR7S1$O@&k9{kOt5 zcR>{}{*-;9D17{!<(YZSsPuzP+A$9$8vgXcIBU3y%d?gDp{sRu9AG{4OEgxeYf3Vsz4$$s%CmwV%suI zfBvphuh;=B3Ho_1SyWuF)K%TTJ7~A3e2YA+Y~5}V6r@$B=$c6*GCB)e-7B(x8Sb`S zkICeykD9@fMvz>LJ(ZTR3>=4v`_}ENa~ti@MJA3#+mg-6S;-(`i9ASX7}~f|g`r^{ zKW(}T*nOtDRI9gsC*La&{asgLJQ;BDb%}#-Xj}qNsqc1*&St`rCOz=FKJMGVz{K%l zNO)dMY?>cD4BeGw4cUrR4!4wA*b%s}+`G&Zu5i!W*X-LCXWwVWGz-vm>nbw+>8W}$$IPwj}QrMA3MclFTeHzk0@IL1-K8z zWCUkdB+jL}AUk|(%MW|ysRjH|Zt?LPaYn>+LPO3O6zmVob&QRjE+nK$v7~be>WJ|G zxFIujLKfgBfztcICWYfhD(iK}rKml< zw?_OSKS-GEZm3wIRk{yvBNyf&9Al(QVJ`$dSNpfkZEOz|6 zezzmii7i;>_OG^?!&upb%D zlKPh);>V6ikh<;rfP&Tx^WGd0mrOd`iGC1{$i7$u{O*q1itV#kt#zb#BLZdXkn2QY z&>F&b6*XyG!afofvrH2k%$vB!C9dwpEKP=1*m)jq{cgF8Z$j@U6ey?RKx}MmVr~v> z$`Cg;`hQR|5x&|V(u}a9GeCbuA35o?x+VTWcM=LVZ+zW~FC4uQdVD7eH=h4PeEemg zQqc@22f3~S+T~ZCacXKapZDv!oc**B%xu+)WNu+P>^LVO(O%uF7b0(^S%Wm_$M*Rb zGWF?cY2u6?N%&DuN6<&B-PGbfZu4+!(ev`sMBbK*SAIM1D2SGU+uupS zX(X8=XFn7$lAGW>22@tfKev3M9`NIhMgMpe_04U>HQ`K{?k{t~}0_U-Rk58J4 zX;mxkQGgTX$Vc}AvT>n>Y>&ym;yuTZh6@C?oEU@layuNby=*!(w!66PV-&Vi3f%TM zLXApZl0#M^vR;P3570;4St#Dgy~Jwy$gcXs#R#gI(azzLx?=T!=FN~-=o@xWDXu5I zCKKNWkM;}Sl2T^j>%lsArM}bq44Ug$A6?jV=X}8~G-zyy!o#B5$r4Tuypz%2A+PE8Qf! zpMIw!vjNuc<1+;k;DsOX{@9wa3O}o_27aH~cCJ9Zb6kJ_`M8$Zk(s}Gj7P;v7-`F< z+0M4+@?RLh#m0_(0{P#5w>*Qyk0i_DAP$k%7P{Z{&KMz(NBal8@CwJy5{-h@wyT5g z`C7Z5C2gN+D7cJTr~_tiw%~M-m9l)*PXK5VFn&5}K2GpC%)f9)gi_6pIe{luyF)*L z*St#J7Ezg#eY042Skp#kO*H)6&ui)uQxVGy9SR6cSyhfKzw-}aT*fqBhCqZX=D#Vw z~Oi#Ryf420eea<{$rJi!CR_Awv$B-uh^J@Or&Y2$(>GPoPLT zIs%gao_;aiGU9^}C56)tz-L<3(s#ogsoKR0M2(=lT+jGG!{jF^b^rBTeqwED`4}{f z=dyp&t5%Ctlo25O*xq<_XyCPxBTspQE!$GV!NSTp`df)rSILc}_eriBxE3%tYfL}q zCDE5r_)E-pOt;#c!~nUWJve1fJ?!ja#EeGnw*f-;Oc5p*lo-Dm1PK^{Mf(fVOK}jv zC_jRp6cZ=hTiG!l9yJ{)!RTNDchqyy9e-mMG;N*kzDAwz=C)Q!ajcaNEVd{x+^8q5 z_9x3EtNhAT`adCa1UKUNA87hI4o7bL_9V+<14bF5#COgG(NXNGZo+FfxoRqD!w~bB zHXTlNA7A?wzPfc=QBoYslUn-dO(+Rt?;F*Fj{W6!H4U$wCn|`!({MJD&xMOE`ZuAO zi_&DIB#3Bwu@LRsqlX^$zRO3CQNB9bS}YEFE;Nw5?pBKtpO(f;73I~uB#|-eQDJVN zQEB^dIr8?2>onnWtF$`qB(#J**6x+un(=0Q-~Pnv@tnvj2|5kYba^TJ=)hGLmX|qw zvI+WYnVt-epBC|&|8OLm_74x&Ufavd%a5a8Sz#xSN}Sfr<70LJdmkYXH)BBxr%8KR zUwK^6dheQen78k#3EX@#5BHk`Js2uCY>tVG3-Z4@TOAS@ePMv!9?RKn zTE6HN{yts*tfS>NgszjyWC`>}offa>)1|uX`?I?2J=9o+5L(17{A=c%n_vc=K%o z#XO8_Zm1QB06}}MkZFR1e1~!GCBUrA4;`(1o)UT{A8RS+3l$ExQP zvBPKJ?~}JbyR~~Y=KH#I5c4i@{l;x>pm5a+I0L11TRty_$kAz}asx2C4WtzKpzjNzG#N?W+X>qfni! z&LIA!`T6JPJ{7#cI3abkl4=+zulOAeF8HzRL5|K~nF0+^dX6Lt}PPav?!efnf^m_6(RwopYppZkdyFw@>HgOb$UYM1!2&A1ns1+1FXGC_x^?Gmz|cY6?o$Vy3w zkCn)Y4P|aciwwI3@{?JZ5#WT;M9Vy-R!4E&>oBU&tya+pTK!KH(^}HfGYnGwqd8+F zhRu84QXHB1OA{|kzI2;3n$I)Fx5mRhJNYC%wvTo8yE}P)V#98f0j0X^QMxP;5p9VT z_<>E9u|-Da%8Wpa0LfMq&xw3`ekvJGdxSWN88g4S$Q0RMp6dP9vw6GMb%$8~eN9%w zqQCdV$Io%6@i7M|XQe+;P@LKMLm5bg)OAId;&~J(;t5ct6by0ni-*xO=V|U*g5$sv z<#!T~k4R&EPnzc{cM@f6^m;I(hXI7FJ2yK0m4isJ z3(s$W{2CGfa&3%&CTM|tj(K{-y<-h|x8{ht^FiZVen(%BI|@No=$0=+c$W!dMK9cc zO)VpI-B=BWakFBI+a=ri-kn!&D^zXfoTNNC={TP2fTcZmZmAtaT~(5aq>JGJ{{Hvg z^Qp);AI<6l<8#ZNiwYf=95HGf|HAJ>vi1q|U`!|hc(HA+=aM%r$oTx9ZP$?oFY}or zR&f=4ZlixwRE<&Z$j+L%{Jdkp_F?5(@jVeDm}_n|gxv?Ixb(W1?tKo@3XrRE+V}@< zAA(u>#8Rzb(4e&(< z%+l|#x!qVe^Aq%_f(-CZEWE&edyp;~mdQ&^rImC8xjR@WvFMOJgYU|Py&qtx=_ep}bzSh*FlgU6!h^%57y6tmaK@T$h|Ae9=Dlo&1 zrx}6#cs?Ei()?Vogc^)oFdJ=k)B$O^*}&Pu5_IVN)5tbD>2Y@dyVqemfXc@SZpR-6 zSF_Qk!26ANr0?#qWPptp8bTR7!l4t7NFu9gqr9f7U=L$_O&Y(?otTGuh^&n;n;Z34 z#lN{szwI5GsQ8EI&V_lm1LGtgB#B2P@k3WXNL0&!MTRYrG`ue(B&0G?5dvX?yp@sG z+#%uJeI7VHqfu86=omF!7#)#7c8xh*zmF&&x$(4;uQ`s}H;tC+wrM^+8!f82uEC=N7?}1gKG7H7;A2WyHh+@IV&}{*5t%EI%I*lH+ zD7>!V%Cp?W)gnR!6P`gke>IX*L9!Y^=fcMN+UbCsV4Ps$ddqH8lIYRirNu|rgXu2a z&f(h}!A1b6Xd*6Z3q8MG*$2VyCtPctO1S9M&NR}Bq0bTo6v6nhyVb;riT95B0`hX9 zBGt&PSm+PbXY{T3~Gf8%MMfcPjzNO9<()YE_8c@&U&IChiL#o8{}@BM{r zt)>VZG*(bqtNdN+H_imR+SAg3-{X+40b`qnSG-@4oX_BD_<3wB`$%Fa+6a#O>3Rpw zt!B(-oi3s6^x0W+x!^RK#Xq#K-4rVrfSDpT!;{{D_3`A$GEr>4RQ&CkLZjbyP2a|P z25+NBuqvW0g5AfsmjuT!&bgJ5s7a23*!1F)?1{|bV6o{*pc>Ly_vIxA2d)MEKk{4O ziXa32`%+J5F*t3P*AZLwkOYa{(2&6_wcTy)5hv*{M(tJiPkKK|H9Xi0eyj-G$=t&G z>rQ6UC6W85U3rBFo#pLw#u|y04CtS}_RJ2Xzr?MEvhn1xQ)=Ll8RF zes|qhp9^Vg*`jjlN{d%|8o+~WB>(m=#9Wr-o~1bNeTWL|OSWKl=c{Rwf+z{XU)sCK zt$Gf)_aBU5!X)POF4Pd#5Mh$fIL0X|q#gYOId=?tC23##d#8>U?-iB{){S!*J1+Z_ zjC+oqY@`6I2VCFpuYN)*rqV!!2tTeSxl|T;G!6~*b_7cEcn&A$Z}JG<>&m8kg6aF` zHa56>y;}Vu?@^21)nhhQH3CyF|5(?6WLb&^)O|tPV;QF*ugX+X33`n>?L`rXW)!oa z?&pLvclv-&sd+ycC9_wh)VC3HBz`$6u$v26IQ$gIz<`&~r>nGopIPQm0gNQat>Gc! zll9j(xMd{ADCtM&X(Ss4{L1;-4uL6x)|pECDtfET!c@lSnO)RKC_oUO)IvKjH?7z&x8ZQv83o+CJ zz_Q_@;hJP?K%SByqfT@t@eU|tG0P^4OK6ZWe<;(z%(_?$Scmunm2`FfU$ zh<(PSK`?ij1Ue||H%wW>z#{RUpqV=QP?JOWaIT%+!cG+HpW9JHiCPAIU&QNklhPl*p%&GJ1b8EH3)q*zlR{R3q zwTpKmB>lPodz5L1n79Z$*dwh4-zNAy);!ftZAsb#FsYvW!=JnBrEMb#2jk~sdmS8X zGwvES<8yD5??Y1Yg64cR0C)Ve>osMi8!awmHqFb^@szi@53=Jz&@MjSEmk@2W9lEZUXZE4okQ-6Vx;3A} zUqOOpE&&pS7MT?P7j*MkxpHsbC6oZ4ctuy@Cuc|;h$m19qFxL3P~W- zIOUV6ZqZjcw4_dOLxpyw55%GB#NQzPgS0XKBa7Hv`O;frz*&m&f|)B2Jp1{tMLAd- zvf1Q3f^IK78S61ab*YWL)a8GKC2jGr7x@r6*&HLG1Xq^XVQ-=1Ax+y3SB*ZZEX0=d zOB{}}4Nc=U;`zB+zt;#ueYiIHBZwbbTW-*-S8cTQ&RX&IMhYL4DLPfRmb|&ks$1I5 zivE0mQ4xiM5;Q{+vj9wf#|0X9Byo1@uN-sWv~^|Jn~_?1%U1jpRLMJ{nJ1vidhP(A z7+X!?3j%Yhf~?(P@BxuzvBgB)Mpe>Iq~@eGbxOG23)CFef7FtbNP~|n!8ACdQQ|@~ zOn1I#WGiU=e-uq0jQzX0E?x@Wg?G_Y6A6|N2$-<`@_lLeU0WP$l^);v^;D_-ry2d6 zny~G+o&Sg3y(UVeh$Z_UdHh^v+VI~IDRW%r4qcqvFfGtGCS&1q$M7c7aX+1F{~vzG z?%#Cfh3=`vc)Fm^1*Tth<51y;gV0+%g?639U2_z_g5)})aT)fg=aARr=0|k9(hLX| zj6WnEW+);VL;yddn}5A`F*!3n^pmJhQt^jwYm8L05s5R z%O>bbJ3xZOcdB$35cO~SWYqA$H{cu{I(1A{1MTPo{-Abzu5rs4{~cS;m-9cFf3FYEkZXcBt8{9D z1Vq-79%NLGru6?J*8`lN&f*Vp3ftxytLugy^?pjG;*rhkLk(cW>Y=2LP%ghkAbBS2 z5j9}MW?wG}Xi8E>gBdkQ;x=e{H20JQ?gww!f_~>gl>${Cm_YR@(pAs{2UvMvcO{%$aVjq7gDHt84@R2>l>EBy~2*sf^4Dp2_kdP}{|TCMW=K5_G7^UFKR(B@C+ zR^CrMj|Q90)2n!27-Vj}26J*Z!!D=fktjruxJaT7{dwn21hmkn;gMK>2Ap&C&xj&yu71)9eNuXI@FXJb@PK!Ef5@Ml85ECnKyf9d|i1)J% z_mR3If7p5v5(HBCtsqfp%FJ+tslax=Xvi2}#-_nJ(d3^{O40OcMsQACiJi3FQPm&D zy11;zSK$*g^gt&3fAeg?9!TE4ny$|-EmhjoAZg(q=jG+(jFSb@0^BB4lLN=L77z)2 zAG$kb{xBkCL(mG#t!-iVUTCB4g}ynH_mAzCe%sD?dpn^ZN#>42BDFvh*Yf}YSW4AQJn8ry@4WPX7r z>s#4)VZ-TeZULq4^*{u76x#bMf)mapos#{R(7)sQf-3Lu>>86pJw}m3BiA?233)|T zc8Iv3$YQ)cc3OH?%>nVPc(9ZwHU4&4n>*jVAAsc+ka0rWS#aAy4cY&!%U(S z5l4SNZRNUq{g?A%PhWd2w5r>*46%QGD+}*~0j0<|#wa?(u~Ve_)}Wq&zCNe-++{&p zTNXwUm??kW|3Yu_YESU0pxUpXFr98kAKu5d@YRssD{u7v(d2ljit(QFtMM&F4o-J0 z^gGaPN`b!B(3hNBt-LV=KN~{_a zkU*MK-#z?Z-`Mv{kIDz;(Dl5UHlEipXcvBHF@MD^&nM8XnGYC$BXQ zJQzt|VVoP+_K{d&KVA+W`SUR#Xf&fA)28e{y31XPY^^wVE;yWWZixqt7zGb)(`^mP zSnc2mF`pN|ZJ~|gd@Y%>+Tall2#S;cXrt=>1V%J@9q*5w(Fx_{COk^Lf>DytQ?oD) zW>DFBY`dr3W7p$b6-@8n!T^V;uvIUbawnJT(+cIcE+Tv;&|8cafbo9G%(<_KWATpl zzeCv3gTV(%!=-v(4OgrD^puA`nKs_aQ17S?INzk|<{FFTsh^mO@a?2AqE$jLar0pI zK9jo_qnaRS0us9vOXhIL2CP`y;Nn3G=>4{f$39DWp$d}4qu&ULL&JP3M!(SwAB-rX zKHgpts=-JxX-KnyKf4XKqdQJ=3tl9VmAdO71Rl|U3;3@UvMOfrf1lxJ!ivq`E4lys+L$ap z;jg8ges*o78%3?9vAcp537La4!47W0Fo3vh&xQH0riVo^g_&^BF?_d+o>90qh7^h$ znd{kTmx;{D&EdQfDKr;BRtal9}ps1aoyHRoken}*TELrca(QUdizi|_v~#Q8(}-2u+B z^8fz%gp%Q^4;r@^{!7s}@t@!GVZ~w%`8}%mlw>_a0nV~#@%`M9VV>7!ro5xv2ca#sr&#aA z20aJrR$wll0XGQx7HwrV*ukAVM@86q;8jD(NCZ50p&e17;0 z+YUgNpQIYn|0K2FKc?M*xtOy{fGXj<5jl-Dy<)VM@SBbnU<^bC^|+Jm-pwS>Og0P; zW$@`2mQUD0rF;`RRWH7@uZksI@|%1MY{@76eKLa~#le~+`bT7IBBnIc@1le!aYE30 zIMnK0&Q@R3dy~Og?my{6y^iX)2mkATsd9hkc`fep4=?J`%RmLzKqh94lycbHo*|j1 zA)&8bm<5>m70H%yeyE5V|E)vKte#0wzh{}E9*$KjY#lHijF>JNtoOurL*D@Bpz^_2 z0a2G+C)Qtl>!voUk0F}lsupYr)Ccu%{z60Hx^#+``+dpyMe(_Xmz7R2USK0ffkRV; zdhe&lzTU>a&T?q>sP#cKaWQ9E_(s6zS;{|`r}610i9hv~LgAoE!0~av{xNNk_UuY2 zhP~bFj!LPd$aAJ8Uabdy*I`233n!AJEwZ`y6m8!9{Rj!@V+3A#oN#C|2wcU-ku;00 zRY04df4_ua6A!pZJjfZuxD&YMHc`_0>=rloFj(KwkfU0jf8mq8l+KtXD|u%ui~QQs zmY6B8=$g{Z%=!!N)ugsUkJ)$LHb@HDr;91k7yCO4CYCXKP=FB(x7}rM^G;u@r0nK+hwS5gULAu_)tUhP# zyg~0f50(*bd_NueHD~@!&1I=kp*MJz=e1?VQ_hwbyM8p+zS-Xd1Pqt){4G|zlq3HY zU_76o`Y54jsSE5+VywsGE+3z&6V#R7cQ17PC_$|##;UAe^D#+h!3=oPC-@k`=O1xG zr{4}0&o^(XI$|7vArH&7Gq<~o?YE8W?8jc!TI!K!_$f$sBMSgQ9shZ>6tc`@GY5Xb z-mow4dUSLsT*CHmOkPN2DL+S@&r177?$JvZtQ@&W7hs-Yy#8x{EHyJS!84B0JzLeA zIt72nY?iTN9p8<-~6a}pL=tmLZ#nMveJwa&yg(j1o?Is3uLS0~?^-ZxW<_A%*D#!w@hFEvC`kNLLBUJg(W*KE7?)@(0-2qF&fjUHu0 zcPl0!-oj>_hWBpJA2{{UF9pse$^QNo@94zrNCuc8-)LUC9R*Y^VrTv(w3;kb=iuNt zvOT3KIh2hI!t-yvRGW4e-u(xfEq5;{B_%ACu}9SJ11`CPy1}Qy3EC8$VEK?XdY=fp z*YVaW?IgEQ5>CY)s*qRSv&D3EKRV|Gvht+CWlSqU$HD+$P_9F+@))2qIr zXmtz@k&O4@=j2(@N81ZoAJab8{Q7(TI5bC9?=$1|2JWXfM-ZFS*2R-=iW{-#enyK| zTN$1VCns*F1Tj#(JBa)Yzxx1o&^y2UuEF=X>g(nEL&24y`llEo5do~b!Sa!`8z28Y zjzjK8^c*eu^}sRRkNBB2o(<9p#1`Udl?DCu8hv(;jB(ZUO1t>$&z|WqdHtBlb287q z!Ph@-nmmx-^1;txUwiMRQ*WIYUVIOU86q+>8Hk|%d^qhA)Sj8H*1+Oa3}x;zfD9#C zqG-5{>GZuffj$_^xd7j}u;4bsOsH*j55sSJc?E)yL$B6`6ZV^)kNCX9p(#)<LsEXymgXR5g?e5eS3>}!z(IA@*J zK8iAYPWkYeb;{`oAf|fbYdi9kg{E@B|9?=3`xT@dyN5wtl-oJIm4Y-lCNJ-tzm8dN(6d*$Ozv;7@vRP0m8@mKBC zceA0B9y0_fFqhF&2H%ve9@W!0TOeNO7!gRQx4>hgf!8yps_8d;v3fa`DU&BDU zW*<>Jcj*lQ8ChSE#>T+*Xc!Tr*eU$lUcpH!TSL4R?m)*avjQ$ou|2({b z2{!Pt;5c0>$l~88-_e1jbWa{Ip#<)&TK~AcXaRmXGUdP8;4Z^b7e*gM&TclgpxMKPOJS)Ci9>}X`xuzv38U} zBN!BY8py+2w>AsJ zJbo1LP0gv%J!^=;BQZSK9m_iU>FeL#TgCa^_Kj5YdyqZR+poozb&K!(Jp!uz%K>yn z=63U()7Wxonm^SPPV>Ccjbu}^uk-c_h8sN^3o=R3l(up6c0ozf_kzZjJxLMubv{7_ z)pEH4tiStEPFf5LtHa#qZ$3GrYpq*$z}ExXZ@14U(bZ#i>|lmUv4=pm*dY~&{Y;1q zT=eDf*IS9j^lO>g&Zs(VbaVl1p6h2+fH*j#0r#Qmyw8nHKD*70%)X+$znB#t&5g_} zl}?`vQ5*ZE@OXQ0IM!S_=Efz~=+FbTklX<94Nf49<_{W?T`#3qq}t zYg>GEh1S)(f85`=0uNlh*zckjZwmHjUc*k-gdp z=&}bEs|}{fKLiFIadRrL3fh$3pSQxN)Geriz@z!lNCqRYfq3n=7$kd~+;V+n`ejWlptgZka9R1lQb!(AoW^dUJ5V zAh^*5J-xOonB`}JZuk%L?P^{Gjo4vAI*z33^$rr+GAIJ5fF_Ugh491fowjiMAbJO0 zG^2EWN1&2FlKrOW*RTEo+9IRNtD-|o(P<^g*qxJS^g|6`V4MV3*&`d-nT2X^90@D> z-t$5MDrVwjZcK7FtN-fsQ{K-SgLHH5<58fu33C7F<`tpca~vu_4lEXD)%iy9b9~_tyfi*TyLw#w|#J% zGi0w*)TesGsH4uo22PEEY zUQbuBF`HKcknU*SWu=LVI;^}y&6)5stho@TS`7!bN%6?Y-7Bu)e=|y{@52Bt2;|}5 z-J}29T&Hqaq2Y}>tTIG$^x~sLvJg{m0P-E?y&ezRqfzX%kEp4{ABc=ypD|E2(s-fa zU`13)v8qlt%p8Jdk$k|jPw-|N(2HWn@CDBl=%+Z7{PwV-42Y4s2s2%^50=t0C>w?+oLTmVTTp$rnCJ2 zz$Fn;6NU`>-(6^2!{r(0q&(YL!Jdiz8=Rhd^%23;9raDdg4n5Oz<~A z>{1NI*e9?UvJ}fGZ4+?6dP#=Lt{JYgw2%PNIQ!CoIcF`GSO&e3Uz{J(x2cl(&`k{7 z)Z%YIM=EIxxmJ7#czXmJ=n3AcY{( ziTEnTmI#slemoCZ)SQ}xK%$2;fL9J10|~o7Yu$FYmStu?tfn$5G;WukK+F0YW*SoQthlGzEL7Fw8&T)v96UOq;_FB~qr!AZ$L01qgDp;}9XM4lv+Hw6hnJg-r6y=g+4gn(VQxhuP>uiXVA zBtf#>sBLq%f72R) zQpR&kr!oV4Na_ryHd^k+C=S3NR2SaP5l25AxW^XZ-jWh~v<)?OlbgGmYk zES=if;Xq#W4|p`)@zTNHi(T!u!0T&2tqGgPZ6Q7#Ycas{V6ox6xVRW{u|S49e73)^ z(dfEmHdkfYM$KpSYI7um;@n7UYBNHB*_ZaL-*s!G6Vq-6Rkv(;br8mRQIUaIfL>-S zLY2Ocrgz>}Rrqa3MVIeD1wOEtIW+qh=LZ++a7|l^G%XQQx5i z%ohL{?idhoNjUF}0eYuTFnCm(Q6ozDr{!Pq?cH6MMI6qd^4`{#U6n6*9e1nq+ZdPs zbJiCL2`HCz>=D#9E~CZF*HD?%%h`!a`P;d*UtT+3Bv(EIwRTz20qJ6<RR*8 zZ#8v=QAuSvbLBxovz^5PXS>B>Q^Xyv!00beaUsaz{!-*S;8y<;kT*UJxQuCPYY(pX zMuH%JzrLwS&nn;I*EdSY1D1On`R3q}*LtMzpYidvk5|_(M2z;s$?oj1pRxa+{1wAj zL}H6&`t<{VVY8|rGfAmoN$PzogL$z9@sGx&IPARaqZ9sXPZvBxOKGFWyDliR51;Te zgT{-|_?J2VQPYOw0y77zyT`Avg#KQdi_oZ?=WyytXVJh_L0O#&< zqhupC+EwP|Q{u*xzJ`?%bU}WU%+X2^iDIk*meVx~xZhImTVHHfbKUy4`AdPZ&X%Kl z?Py{oo$&p^MD90QT9lw&!tY!XRE18RJ&lp?zTm>B|yOT}xmvwwV1Q(6YNa z+pqdN+Wh4sitNT@bKN?eDlL^n&PavfcD{u~ODj!_r7IijtHfZnJN$$G^73?G%a^n&4 zUgUR;GTQmXdx4BJHVYa{n>~+2Xr$%QwF}FSfGNIQ`}hdeppt0%LOvv8)ZO!Z1#BYp z-IzooFie&lJZ*1x>(@60JkDVb01KxbD@eaysDJma|NFI>idV|j)phX}YSE&jJ-D!F1B-`?aL%Rfdd0gEiKdD z2%7OK%fZRGtT>e>P^(5i&9FgGuqg2%x4H*$Pz;Oi@Y0H2(?*!Xr^`+nDcFj%FTG!S z=fkV!W5A$E7Ii4D0oB@AT|D?nGw_{5|A#Lbeu`mJFt-oxkF7Q9QVC9q`6es2Lwo&X zavs%I8+}_SsM@u&bO-1i4VfNNjxXJLVsBVtg42btk zS1qnuEZ+-V>q?x2ijtvXjuPu~fU0@U=8d2oW5g8~M2VY6rI`3b>J+(P(3+T)4@Rz<5TL5rIR zH3cI7v~LOV^Vv;veFRak7=Nrl3qeNH`cOc^Ln%TZ;edD@?%XcFf*0Zs&bF|Nh~!OA+JV)`7S1v4NG=}UQE_z6ZNw7 zycOk$#s4V?5QG@gVE!38G||FbK`73Yg=~BLy=Z;OPg(+B`NY2*!Z3ZwhEjR|iJ2cN z=1WV5(E~`zZFYn4)=*Sjw0icXxt-M}p+(L{_QKKZ;_r!vTslnXQ{i?R zZIZ6{%tki)A8EN&g@~*DuRKzw94a4RhAZmiJ=!Y;*0JiunS{D<`;Qh#c|Y2|Ck&d(#S}8iN`V>9IkFkCBr!q>R6W;9JbG4kB zdG?enZ2{u;l%9oa{ukoHCL#s@lZ6z-p}_Ba4I5IE<<*=~#zD-x@XKjo*pE&{@UH6H z>-{Hksl<%AHf{0M7Be>aE;^SDuU_cE8WK9UT?hQoeB&+Bsk~&A{Zr?;{0oAmAm9w5#7!yACI5?Uo~8LIMm%2 z|4zee&sw&!Od=Fxmo3{MSyK@)wuG|2*QHr>B%;#P5^BrCxhsG2<-gEgnwdlywSW`6dGXhMQZVv}VOw&?(hNFN2^)H<6Ha3*{QslUn7y zbH)_%oRUY!N||6ZPjQ=T;m^kj+E_(V#x{nT`jyhjD}-t*O zZAR8SnDLD-3wC|c`0`b*XH%$;yJs72j-c=Tvg8(}~UDHI;#3mx7M@|gZ~&x%FdPnhBnG(W?~TFnCH6l6i^GE{i? z%QO~jx+foYrm9D?Ctm8%h@htGb{|T<@$BxpcbxlQ__xoJCa;3GZs%l|zv3>%ZU(nOj^VCS#)YR0! zvugeb8P~P_sma@;$tpTT<)!?t?WdjKw7_>Wri;Uac42BeVC*yT2(P+ZXZfTxU<{u^d z1uT~bPz{ZV=Uhb>yk#+o-?_iG7Py#NA6o$|__FreF;pB9<4F=zQ{x9Fm-_np$|3N~ zYin!Bo1gjE1(0u z{UXJtdyU#56X&pjOME0VY+jKNhf#RAX6J>2h)3!H`fmDL8!g9C z!>|9*2P~fwVfRqq9~eFH;h>zucfD|B`}@djuLzcc9?Y}i02fO`q=b*IVLlF(a=guk zWY%-Hw4C^3VL`X6hnU}){Y)QNPWP_umq$tO->aJCDWW{F+_WN8%Ixm$E@iGG4rx)S z5no%YV^F!)%#WrhihqA+10U$^ow1)dO6aOJuXUfYTB9=8?6gz!GQ1v-lLCi6N6@d!_4vzwF))+%8!X0H zQlj3TP=Att%E-w{#Nv@U3=)ckpiC)CqksSfo%2DgkTRa7rk4M&jE+G0}PkwogJ_>DlrW4KYS@_vhVYMPc-)Su!X=WA)4Gc0BIz$IaX*i;KGdAEL zU*)S{EYl2Fi$2dI1P@Pd?*_6`x=+y^O$O;rZ%uBA>?}t#Gc`e zH!D)oCl{TOGYWgz!VqLU{-bU}IE}dQzu%YX%^`?pe@FOq)^_(y&|Z8&btVwm&d$#h zU%uo(Q&;r+1_z17#SH%x{WVLKx;m3R-~fY|fV4Ooo+ZplPzXh)q1bG5&R$34EgrnA z5=5{$=WTZ{OKt67v+lNQVy%fM<%7Tfv*k2d{la#pu@keT2yOi&WZ83hT+tEYbD*8B zF`rX}$cao(R79`PTU7A(if^zQ;RZMOeZPuyaLvunmlbM3sRF9(2ih-$s0aJLTJ~Ga z!9Pa@`Qx`{`=lROBYQ=`-t>I_8kZ7B#!OWGEtS9_oPY&>;X8KBWwI_hALDo4pC0l8 zNM{IuOW&0TP8Ybt_bWv4eq(7#=QAN_qVweK+`#Kvi{4rx8)|bPPNXMlchqusEZux= zQ=X6Gd;O|lXP01SXl{LsRk>?36$?HxIhNr%&){lw1ic6VM0c?m=C>sOZL<(d z(uPi-%p?$S85ul5*C`vfkBLHtP{OjUP(p=g4`SnT zc2H@@Lns+0EhE#A#4F|hQKg)c2vM%J`CA4Tk$x984}TOI{;9qr|)hw}O=F%Kr4c>^l= zOm0q{+S>aaQYM|=mMK+s%6%OlWyqLYz7>SMVU3p@hCJDv!Pyow5X-bW$HEKMkv$rj zJxGt1D&OJ~kJveI%xzI^BQNcD@)Kr=ddb@WKQbb>>7RgQFeq|+m=L#n)Xqh^3vAij zPL{w>^CmcC5$+WHFpyq90nkiQ6w}M+)Kw5GKSd)#QLg%SdyHk#SSV_G+SPo|00FPN z8`b260|#k;D1heXilG2v*Jh(E+@s~7X3aqn;v;OHhnl0H=~SkfA*_Qe;XuCPOw0A& z1UOP;v0*&KTL(JKIO^eVaF}eodow|UBMJF8aG5)7O5ghm$?qvIGy}?h-G^1i2y*wd z#Lm+nf<=e_S_~k;6Hx}$9}3gFgpYsp1u8GVk({{#l{Ba>c-VLxS%b?6gVC z!(eJO5G@yBl9%o?;5U(9!KT#>h21H2RZa2w{l0f~%6f9=AkgnWV|wH{nBBmiZM@;% z;5XW`4so?5d@-2LfnB;v{dJZxIOjXR_5UixB3kCBQ3X z(X?1Xctvuu8=fhJD@8AK53p4&PHn$sdSZAdBS~XnBmCaI6|mCESVnS06Ol@C1TmAi zUNQl)nwou};%z=`fQIbRXq;OQw``QF(bDp=`TQQ&(}}RzL~pHiFaDlGt|guv=?Ldx zh#V*^E{d$yQ=w;k>vjKXV*K-sh91ZuGb03cFszcyyXBe}QHMOtBIg3MfkTq4!>-3Zg;+NH0>QL!^WB4nhb5B1%U(NUZ~Oetd*1J#@A}Rk=em-;XJ^ms%w99=UTfX6=9R%CjT_fku7Nqb@GEwFNyX2F(%6j*mrD*+GT(jBd#0`?N9hB}WqWszL&Up+2q7Yf zjjb1corou+bPoedA$as*#NBvNI8coEari8=T4I>9#b9;Epgpu6b#KC&z5M)Q+0bom z{XD)P&PCItepks!pAaX!So-|d)H9<1vQWN5sWSQ}s^rQT_=<>rOc{mH}^Rz??u)~z{v!%mwf z)Tl-BOdH;T6<{t#6ZcEzvXqJ=eW=w5K1#C;I6_FG-+1ks`?^<}VcTS7deO80|iJn#mH9lwNbY zn+3w{50zPMxqrl&ee)}`VU9MeHlUT?%9mc$t}S4_Ru^eCRF{(ZORU!MxZmM@tYUVT zZ`q@X)x!v*0NYlM#FZ^w*0V{>6~W98rPv3}#O!$eWk0AECUuo>XPI23+UW5(?CD5+H86h_H}}<%bD-@UU$^ zAr<$HBpueN@yyfCAGh99P`F;9oF}$x`RzucUP88MWZ3Y@y3z~vTDXHa1>~eE#?D7v zyQsM5_2i!&#Fd|>$j+aSgJB2fP?C{&-p^7M3pJ&*~iqZfQ6;QdzDE6LskpQ9(|EA8R@BUCYF z&`J8c{3~{spM3T7KW>~cktXd&QgVn(aZ&v}@NPkq;+S*#tI_B0W zK6Z|F+wpreQ&Y~0sX_XGHzwzzi29#>?3;LQ;Yde*1YEG!jcw}8L#3tPSC@z)_JhM( z4!xUp@|1DEFdFLW1vV}pL`yC<=e)hVQvDU1iheU}zll(PUHMkiR-&YxVp~)FM)ln% zQ~SXKd0tS~ zEi^Y@`impSaHdIu@LJqLpy>L046`?MGFU5QlZ{*j=nEhyMs^78_3CqfFx~Q`e+Xjd-%bc=KiLDh?BJ5fWA?@rrAMZK3e5hzvo=9yOlxybnb zp@JYK20ea4e&#z$Ka-IbVSHtqoGL*-eb=t;Jld{UKZVO#W?uF=sW2P?n*OLi%b-mY zV?LR~>F_c4VbAI(+@$v|3@o)+>lHayY=Mlzki7m@N%^8Vyn1)@BE~Cm7Ddek=U59@ zx*)h;d`^HMiu`QNWr}9vv=`Y#Nb&gX(0vTv4GmHopIRP%3_)S84-S0l9M9@XT!$!m zqMeP51$ACYYP}h_m9Nld7IZjT(ZP&XMkRzDbQBffu(5*F2H)_Tlg<4*@p0frWKC+> z&afH^s?U*6fnFJWm`9CsUc11L-L*(2onf_`>|%P}M{dAjTZpO3hr$jKg~9 zx9A%z$F7+|QQ<$K>#Mg+Q3>-AaA!v-MIRLlzc$U+TDpl>cc4+`6pl#pY)b7nTpP6Y zx2YwK`Le0e!)F8`>XdAEN6$#Hb?)s!3CrKS^n4qXKm{5}x$bba2wWRf5eHE&Qc#Y{ z9yOO!@_hZ!sI@1swK(D(7e%3~o{vDTV*PgX$4jWaxZMnBYC|SYGf-2b5S_9W@{EYK z_+I4Qd%1zb)2nvmV3Iuxv45OP2rA)3H;7U$b;>DYCPbMA{J-~!q5O#Ezx50vFgaG) z8DL?fyzyJ~G(1wK7&SX95T=lyj(@JCWTS7^;knopTH$?lJARHDOgdgIGnp}k&)ao* zOWX0^xRPhH%?Dxhz7R3=C>sOnf3)>g7IHm5zQ7g*rI?NG=}UF`@s?IR6u+yA17F z_v2=}$(p$?{pK?vq#ixy0#EKIOYVPj>MpZ*g01vk51C}Fs7VeV8B*Jo$%mg=G&DBa zEjD?E?A7*9{ztzD&Iq0@Fxyo?fvK+623WAG$Kqdf@I`UOT_KU_RN{(kYLZj9*sFih z@!_phA_%~kH%%C42UBsDDlCbCo+K)@z!X{XmW$%+HDkQF)}m#7tFzPKU$02DW8}lwmJ!$54 zX6B4|)tdf+EXticNktB7jhh4rz$gv-^q>lyS!#M%P$Ak)>ZjMRHK&$cxAl*5i zi54jGfn4S)b;|`!gY*c26VE1R+?e9Iif!ZQcceaCNh?q@Ytv;N7cyVxq_d##Ky)4b z7?qGC$vOKiW9&lW&cZotvES(w%bJ1y3OpxYpOx~p5(1$<=aMOyO=(QFHb}-`r_yrA zy$;7SGPoBZjiEgGNt|>FWJWLLhaFtWUZ*^<4l^J!*qnT_ZUW^4rU z`VD=Kg3>YQqO+bUOqK6XV-5UvS@P5tF600)yGcT7Fgo&@(y}V+#{3%LRU4vS;cl0L zehOnfdegXb{9HdY#dx*{+#OET-ZU)k5CPff8CHj~ZVcn;{}*ustepMiKFsJN_Hz>c7!2ZC z4)?6VdwJV#MmwYszE0hZKJDFNdo8y$y==!&ssB2Gpv_ zLm9xAx*W;{>08efI6@RSz@#KSS!0{g!|N*YY;xi2C&Q29Gtd%Xys@A@58KTWv8r@H z@$31w(c5H^i~D`02!`$_l1^^L;gH?D+geEK4d{d5163{>O$$rmySzmtboJ4F8KO6` zOoulsI_A?(dslq!Q~lp7pUro{EaFv}E>ToIH^)QIDpx0;V#8$3XfXS^SZ+b*!Rc-#*Ud2Y`J&S+KkJ{W4PhS?BC~I=O-0ShoF%G zwD~h9x{kWh@@M_@f2N=p1`7%jO3c^1GX?vi=doG{yPZ64ZB7H8a^MAxGi%yAIF)b; z1L1%i{@XOWM7i=^dpVU_$HsidXoB((j=F8t^kEcTZ{(#8fNqM^*e-zF$6wLV@7X2C z3x{Wxtu(x|9W}BJ`7bRvC~6=EKQ5Zzdz*g0uFtU$K29#JVSc^@*XTZV;UQfc?4h^g zPzZHJD{~I8*{uUM`va7oR_O4&lbJbBscNmoX^6fd?PV%uy@IOfx&koYg{fNw73E!4Dg>+$mk~@F&Zd8Eb z?p#aHrgdf5*qd&)%$LBwfLseGOo+1HI)tv1i2MBW3@mMhsmk2cLGmcQ23;@Hr{$93 z6+5MHY}&E9J(dMgi)0}A#|ZILBeE^j%k<^@jb{qpEzB(YG`m3l&phe> zp~L+ZJcUiWrpRf0A1^Ni#mJLI6Llb`5!Ekk2Z|Lq0huixGo_J_Oe zA`97{)QR6sKLNbE-j9o3vBT@y3&fp@8vAe$aGqD^!wsoIy=D~m19rIpo2_pTsTCb9 zh-KF}f8vM7eowXG+*#HI&B6?=V4en+(SqjzE3byYnbQkK@$T>tx$MSKT+e_b#1vCO4(e?AKs!W6=3I`m;q3J zd86HK>hpxdWM*bwrM`E;KixhzSbo=OMFV&Qqw-45a}Vp-j<43oZOv)uqn(J6)X8oCk@wvM(C7C96yTxFRs_(bQN0>;Y z{|^MA(+>W=s9+qnrvrX5JvS`T9%H=Ue+-O(z&;S^$c(Ox3pH^tccG)D^EuO{{`FNzw7+3Zs&0%?58Zp0%%e0ZND-x3*jmmr=wVxW!mnmI*3)Lq_YCcDq#DIQ zlGY*It(5Yg7m&zR+1<<|5F$=b&qzTFi;EK|RA<2{BhU6oa;iIDwiKyj+0AVps*3d% z`=E$={u+ow^-lbg@ag2u#8nrN_xUmzx2B1WItf zNS}ksD=MTUB_nflS*UK_EO*LDLb~I$>Frw$lZ@9Jx5*cd(lV~mO$V+$%JvM6%+Zx(M3c#* zLD$&4>+V~esKA%05e;T4a7x*p*;uD!4Ms*8XMZ|ZZ6o@C?PgD% z>z&uSPW?&jGBacHyq6zk6Hne$w3k-ZTm^Y3k}5RjkB;ZvH$I%@?J{nZb1IsBDL5g7 z&VKS=5uyC57-(;yk+nlxV#zDG)%dR;_zVvtoXnZGdOQo-%RjJn6xp2lOB~58!}Nn| zZ1s~3;4+G55w4g}-A_PlO3>^t+h*5?-pKp#R-AkI4|WXkA~}4`4%9VY??M6+HLEtM zAYN{E{%R#&aE2>&9FmrF%=2m$_Phtm=?5d4eivNsdrJITSmcOA zh*ch(XMQ>Qd)?Wn2v|huqOZY*RQF3xeD-A*5>BHxT46NwJ@9RJJ_42ag)p`3HvAtq z)&fN}xj6^>5>(YzTE}K%vuOxJfr8bfM3%%8uJ%wQJ$heJ0UeDfCM-0%JITeq3|--yr|Huw8q@~I`DZeq?oCj-EF0uZ78>EVn> zX7&1fr9->MU{n|9d|m>V@XHzenGpQoK1i39T?Vob!4s^HO>qV=RM!aAty{O4*dJR$ zNV{o3j$-q z1MxHk_~<3{l7qL0paL@@gyOandY_@gnv0A}+h&*igps?hoom2JA7?rXSVauxhCKXN z!om<;+fv?4gp9O$q&5_$H+hCG)m(6fM|fOZ(VH&W7knFkd6L!nXnSrdZ}i69rL zJCKw~o z>$xb$or@zVI!CEvPV6G6UX!}$GyIJK7+%~m>ioJN6=ujDl@%t%LRF!cZ+doaA&*ce zyYqflT<;vM)$sw+$-vhENtutdK$>;nI58h&fd9xT=omBBOm|7GcNr7NSt)#|@n*<* z=u$*PWE!GWvF^`~&sutMa8Nk}SZ5VUHP22|$`+o?2tK;SzKE+!Rk6hAnMJCNPLh+R4ZU5CFQ zmfrNzQnlX^&GWMb^g!8FI_moJGKHS{bf6ORPJWp+xc)K9!{&~9wC=Ssq?-+2_k`A4 zi0@V4)R^-o@$Al`_mwG%qd`UX!4^cT^LLk6Pk=oPoo{q+cB=+-U84Dsz^QXxC|c1X z13fvl>^^wO^e;hizcL)sb_wXT_Iyd==G*x>IalLls)=0444Ss@0S)#u`l+LV#D9~HU*ztqw9OJDYSx7)>sQ;+$&D~nC* zKgoju$Q4}2F8lP{j)h0$424>>R^Gh33VcU1VJjoOs0Ai{YgnL+w&f@;D<4FA?wlm^Jy+V8VcAgaH zX(JP}jwH>%?gjD@;^TXzmp$FG*G&F!F%_NYhDJH}TXl<#Nf_sId2=jy43L4-IZ5*F zq8tXncfC6_LO7qu;R~>P^fO6k;1oLS`ol%Bu`x0Il$R<1kFpUO00RnBX#rq%KktruIqd(v_sgN_MI^oW?MEvcy36d0^ud1}x7lwoGDlH^-WrYsJG{KUfayOux>n{5FhQ{XaP})t7r;1^D$n~fg>;b$%eBO)14EZeNc0J)cY}nq zPas8e zBnawpiEv)2VfQ>%SmxHdPg+tB)q`$<~z^Pe1HyxzjBnzfvKtOP(Sz!6c#dO zki71M@0}?lV_PgE)e0p~dH^*D8mGG#W&9oE`igDP9657u-I^LCPxxgPv%4!RopHQVgFm@04f;lo zv#YU4_@jjA!&N4=J4=C9exMK@k5Gt0C^6>`5$UTQ(syJKBx7Y}D*PtK)O2>Jrzh*P zApgi?)Z?&dwvN-6S1J+7z5nV=qOp&L4@b(bn^Rwdmi$Z_S#bA zO*;izmZwW}hi|!OfsD>x%_dWy_5`(%&%nc^Rz6>ood}-hkNg0ye8l#e`TNTYiPKH$ zwe!O<0ZZ#J-SPPpOf3(FqJv)B4nkM`NQ6A_H{}!WB<+ zld^Wz-PI*%6DQcH;R;E~t?b zr5&1n7nl0}Zgn3ktxxsu%`sXd8N){HXMWU8)V%5_l8Zhu*!tTi7}=Ck zABnx#EL3#pZA~VT%HB|zsL<~fqftQDrv_L%)3+aoS_aQ1UToO6|aw_7E zLZlxkQW#%@{7Zlb{Jrquen`8rlmvR!q6e%$y#@)BikSb3tX4cY?1G zh(P@L8pJ5gK=OT`!bk27i4nc)!T-lo|8`4?*8Feh4`IMj@DTRozmAzYJOKdNU8wr! zYXo8YyJut4IE9b60BL*zX!95T?{N)({lAV~ep)f&4G@62gZ=m~>ClmEoK%>uN!UT) z1fU(NGmgUN5vnutzm8&Na6?+IWQfFBZm*`8WqEEvgj<7mm+vjQ_{?kZpzEQ;7lh0I zngpH2^IU%T=pc>nylD6q`NB`_O1`hVwWK>6#TAm4Vb$+CW7ctb6C;a20B4DGlv1 zKH>Y?iug0^FoQ%X)9HtYoMsyM%W_gYqXa34g@uLYl`7Y;^!o{+ud9-DufRY7a2D=( zV?5}s=$?*OHr`KNM=s7X7eMb2&Yc22@=-t|X_-?1l&M*n-46>B}1SXJhG(x%Mc{f>C z#)D4sf9dWARgh##pttRQm33yzwwAEU>X$=wph(Jq_YsYCjy4ou{!cDIXN5bRIB3q3 zgAAm~kkot#~HAEiX3)->0s=RFgdS>2NSh;K`S_k<6X|OrBbit zI}0;`FqiOWMA@?saD1}$b&&am8ER!#BJ9F*S~jz&*<{@Rtk9~P15 zy!lvoo#y}$p;wFiKC-Ftcc`vr4V3w+v`un{bwfvvtvK%E)$A(lCkK&=@S%~ltU8Ew zc7&Hqi+;VU;VnRvF;UOo4u>9L!+~`}fO)%@g*NIaz2=bXYL>jXuP({7HY@eMw3H$c z)4R2ktGnRcU;~3C;BfZScc@80SMQe_nxwDHq5^DE`}PkXyK3uvW$6qHwORqpuY@? z{L$=%rr_!XeeaECG#Goy**cOV`wYB^i*PE88?j8<|evgtyV{7tl_uS__7egev zDrs+3FpYh|=g2$uew8}QciHhIUts&-f;;-Jr{`E;MBcft*bS{fBny6-LM-Cg(+($` zYV(hdn;Ua80D!c>yG1>OGC$@Ot6z`RH(3eT)U-X|1hHi#Z9DOWTCL$_>ZG8i2p{XX zhQf}CCR2&U-BC5@o4@_PG5)%CYd0!k_hYP_4j&?;``FG&= zvPix=Wr4&DGv9fzYW+Ji8Si083c^w@7*wRb5s79D#LRN_}(p=J{d<+BvJ^8Ws zg{W*8!xV8U@8$72(&G-qHED8Y=0#XX3fs2S%|lV-(=(HVl$@d_jvA7Hz`)gmrZvsv z+KZX*ADs1_lNO$};nMhuRi7pFN_4!N-Wwb-w9@~EXFT8JAo)l3vH$MyO@2}n+$`J2 zRzOP}eCd5!{lRLzJO?O6r}9XD=4pz0qBSSaa-;cjscTDX*2ihlr@x5L*0;&tGPtTH zcwB(hcGiDi$`On|^siTqZf-O3ja(8=H|*YSQLO5Fb39srB3)fwHKjVAjVfhVS8Fwn z>3>14`pAFAU^YrPDSB|14Hs(LI%(Ru-Sce|g5U7!e9#5_FOV**dH2|(u zPP0$~g~!T*nJ9t3Dm-)9&^iLv4^AqOIk_pGnXl%u#_k1I7l%jk)J~Gin_+Im}Bo~2F*jy2(l!!Jmry#;6IpSL>1w5bP z$c6-0@dgLd5d+u^&Xqx_2z-3aaNJFe)zW~jmc29<>~-< z5~#T>PozT=JY|~Pl@y2wMJ3w5RgIlo+P-^2W~+X5D#$SPV4^UA5RRkgzFZ{GRhrk5 zf<6JsN3RJ7?Mt|vlgnqVVRk?`89nN3te41~H@nvGabWzw-!Fn>-a~F?I=j|Q?-7z% zg6elylFQSRm38*8d^45H^YDv)3}jAdyzvs}!Qcu=%}BPBaI(k;^0;$McUnXZj3}ff}MjB^aF+AcOSVW!p zz2Z~2JwMAkGv8gZ7l&jGlbe~<`UVU*^sBw-50I{Vu|eZH?*3Yn1=-lX92plQBhJ%M zgu2$T2dtGG>{wKz4!`pCxp7CRpk>@Vy3}a`nJ|prWO0+lyc;x#7RK2sN zUZDKx8^al}MZFN~m1{!6wE40RRSuBK@W}cCYRN3sfK+(obw%3zZX@evYKqp=8Pd8PUp^BhX588MP$XbFC}2p+27OxDlK zJCkxzccZ29q%V!Ts6og0C4nx$TQz5GMH#4_88N0cc)^rY6&V)iAuOm_W5Hv}z>JX(KIHe5Lg5M%n@jIDCy# zN$=IT1SVCe22yb9yN;t%ZrWt5lEC3kv(+P8R3Gp3lwMJdRKTGri~950;ce_dE}ukY znNyS0y&RTxH~N*v(RU!DtNlmnO6s@;aEPbBU}So?6n1=0pvyh)0m%odB-+BUDe@5U zZ#A7>=u5<=QA1!iXR}?O+x?K z9zXS{z^Q*>UlQ=SZ>(?v=M|1ckcOp21Z9I@jDSPrXz8^JcoK^UG40 z?r=V_+a$B@jA$Q8h`g~YAub*(Hms$;kmAIA9K!ZWmGsSD`hrc3N$cn0}cQ-ibnjzir?Gkf8Oc2nyK zM^=|m$)LR-++vUkLk2t))$OnO9}?Vn?F9GCe-th=?HTauiF_b;^4AvZZD{dhTIF z(Xu%&eX}ilf6bL};z%$);TDJOW@fyNg?oi=ELQhuw<1F5f;ZZ2SpQ0LQE$6|XUm+$ zs8dwN+(X-jX>v-s@KIaM6fP|~9Ev&SZHk>lox}X3Q(XEBTB=weKSYx76Uc@6C*H2q zJ7yRh3An9uSlJl8NcJQoIaRWHN7;e&}}LHh(AP{GHKSLbT*xvelOmka8H-_puJ zK3LDiMExizvcEIHu&NpVV3-7id{#-;)xdp{yK;N6d6@MlG2$%?hq{gvKcwZEY<>t@ zuffEWG_rMc{E!nGoHPvXBwiUn1i4R4H%E}tfh?SfpcK9V5T9mBVL0t@N_Y4U>SvsP z&J$11Dr*yy^l(yM`t)@Mmoz8lR84SnbhHYU-2<*>tI}ZE6Njip5-ifvQg;`Qn-;di z#fylpYbRfSq+zIS!N>COiS#x7T3%1A57&rO6lWVpT@4!(Gg#e_}Dv5!Y~DlVv&?u88f?b7tBxx2rYKAiME z%eY|qyg&4=V;l9dy!@ZHEccUG+w(|3-G7*VQs#p}cW&LiKEc6C#lRSg z6_UNB7USibs(SZxM&E;_d*Yr|*p}rXi9*a=)z{X*FQ4!aMnf)j7;AxHI4nE)WAA6sEddoP>Vp0 zNY>5Y#7&5dTm~U_G&H}_r_fWbYee!X*UcAz?d3FsSda9P--49>af{N0`WWYxMO+B` z;(FDBC;Z-Rm_L z-6%!L(sk$Fvb8q68{?OySG6gc{QJi?T|lC$!J$7X$gBN+Ck`03LfL+6jNT0zCDV=m z9qJ^Pz&PRJpQ1-eFsbH>r8&L~x5S$1uqy(Whm=!^s=Ve6t#mgvXfN4+m%26;?Omg0 zV`NXEmoB#|2Z0{C#2N+`Q)g6!Gd*~aG>uxDulN0}K680GvHTY=etn~CtTuetoaDLM zakUD&O?kB^_96pk*Yk10L(9c$VSrlGJ84OXoAX&%tAWK;YYsAzVScJak_@M=ftVPz zbvf%Zvd8OPoh$m&ne_S9fx-%4wf|$-w%0YnsaDfE3PK^>3U)h-icMMTci5kDGGOfekOH-Q13nG5wq=}m=szO zmr&ksWeH&&UadGIEmG=z@2C3GUMT2+;j-lwm#Uu2HG!=QOm^+#5KE(>23jsq&;7PN z6oOpNEBqGuxfxlu*GhL5(#vrt(Up@pYS(huAsg zP+4TIh-Ps0Bn|noa*@OjfwVFWAK$;#NO{ER2^J(VpBRs{>cF$_J@x!LiXz-Jot_gl zq={#cr7arnod}ldbubRD1*B!rtRjV6H>**B?}}7@cD_~ZYLkd_H?^}E&M*&}$%yNx zH#b6DISa?#Yv{2=u6oQO_5SonF|p@$ZDmjP`T^4FI%R~)sClr8%57f$GmhSOX`Xk^D*hBF&9&QUQJp zS>iXJc*sWv+wOO5t;nn6G`O|szVAbm53aXw9X>(@=iFF;bY2`1$gQ{Vv=M~e2Sz)t zyYRK>Fyo!Iq2zqeJ_0_tbmM#hLJe$;vBw8iOwSD(+qN~073Hq{dut#*r^QKHgH}_H zGEML6K+KI4KI^kZkOfejWZ2=%0x>h?rf>^7QY8**f(!_^d`ajOIH0NnfHF&rku(B2D13GC2mcNv87D@DL$>i2= zchzNzrfTwW_4h7?_0sFF008Ybi9$_A9SG*r__4o;!5r37q`)U?%uUIN<_2(**TQvv zL2oH$E064Z>sUYrji;Zs>fg7Sc*efH4(xq68i@(rl+lNaek%3)O5`Z7n0q44@pWaz zSGVtBX|-)pE^=Ut8nmYtlr9&t;v)}GW;EActW*b+)Fs5cRa$P;p-EJyP41_?_QGJn z;9B$I4KqrjgwK=Kv1GLf!AEZm&bN0#T^f+jj786lg#$y(o4puks}?gHBX(!^zmDEL z>;<4UO*ducmKrxN6r?$jMu*K<@J1S6EcZcK~U1@+|O3L$V%`xWI z>eegrDL~TqHVoWKaI?~8*D^DU!j>8i^En2tB8(9;^Mt2~|X zot&KH>7Nhvn@$#_W1bSI<7P=LuTfdyy#7n&Jdy=#v&9h!==81OrzZZVnO|o73=oNcM6=Xz6tyiWyd~&^Kjf^-47DS0}0iCJ|d8WFvHhFy|&;lD&}Vdeu|fZv~7I6 zY!nWIILprpk4`&fK&YW>pssH(xPH|xJwP;m>J}55SzT)J|3Qep7@wX7gIu3J{d&f^ z^wNLx`)ZYg)yG`L8YN?6`g1O(n!6C$ai{ya4jY^2i@rVx=IT~~`%WR}zXw)j3u4P@Qy=&!1u4E~wN}G=)%|4}VjJ=+H5-kcY3!Q6=k?o{?s0IGMCMQ6&x?J=SDm zBFoGqN$~`^Fl0ME@qmlaO&jm|2^(^PEYgqlxb+jIGx#XwSPhK!B74N`LbbqiHM%R! z-ise|?MjR#Ri8A^e?FZ*R#1H}71$M@KWNUD>PqFSOj|>{08xrXh*5aAJP>BFj~zIR z--%ufRMloVvX3#${c0}%+>b=6tD2YY0EWCIZYKskARrTc0~>aUkW#8xt#&UoI*+BG zAhYf5y;}-N<>lLPb>9qQySsmvV{4tqcXnhc)Jkm5r^kJlww6Y!pmI!+h$n38iN&A3 z>}4O3OFi?_Wxuk}an3H*(aN$uJe-=HF69?wvL$2y3R!th?gN;inbXrdva*GZjj|5x z91JqoD@Jp@ny-_eXW5}`e*b7Lq02kWjX*&$<{AsiGA;IU3o`HSy=K00pZ%DhF72e3 zN2|0l1w>gYQm_2zo55oeK0fAn5xyTlG@lx04@7udttk)Y z+`~#@;txs3Id5NmCSbTz*CQ|($M4ASh3#vAr}5RyijKr7kf6+EoYTF?p65P&)=H_L zHuSE>Uji^Wtt^SA<&B>Cc%Nym_N?d|kWmIKmb>}Zbe9$Wo|==nwJn9duGar1_hDoI zD=xZ3rKp%+_JUkGalH;e>>wm68vQn9HQ+wLhua^^zupUyXS>NTA0LxIdL>-}nz>V; z<{qFPsLH}UjCKG=+gS(dF^keZ!*&>~`Te)ytj#pc2>RY&^*l+yDF;^$HRc zzE1f^kWWm6EVl2t_FbP)2J^KmvC&GQ{7)FSBIzQpGm7)w@S%L!Q(9A_|N5#8KR?9_ zqnCUByNe!EWS)-vJW2eHst(ev?{BI)NU8p&{6;@?M2v|PFnF@?$8Tm+_SY|-FK+p; zj|$2Yu6cJYKJ>p={)gnd2H;5mdiiH4HJ#IyfyC18(HhCmt>w4rJD)0p9vane)-Bv% zVpB-64||m>nl1bs!umARv@qZW`J)p0)qE}ae->9Q%%WToY;syE_}KNh-uFyl-lV;l z-lK+A+O2EcY#YJEMAo~%aE&mXg%UkK@25|tbz@vSh~02&Ttp{3^jT0#?n;CYLN{Q zQ1HVx3Q#gPW4@5uP)~})m%ayGp+ENI653>v%!QuEiC!CY*FcPca7j1qKOh*MS98UZ zUsC*Yg(et{IObFcG0^iI|0w?nGdnx`;r>FM&6mzNK$d zuL;|k7M|(wo5_;&A0rQxXWJ3w{6w1+Nk>^nfO_skcf2wIclbRCvA^`kn?cgM(R6=4@mJ5=>B?2Ms4tOIi<)6n7USqI zrYF+^*65KOR~?xrT*1dWev?Au9lxW6L`0(Ap@nIvJgrlOhJENVzZUa$jyN z9|m+eznSh5;mD%Z|I~4hnekCR30L)@uj{U9IBvS^?Ws2;@LHo)^R<>Fm9HHG7S)_q zE^Z{2GSk)Z(p;cF_*Y&%LdNlDEJ?7Vc>1w=z)kB4juLz`qL17u&`TA<*mB>wCWa36 z>Fs+K;ywhpL?*M(4BJ1_rO~Iv(|!Kc_srz>arb^;_iDhC_1mH=;=w()?J3f4I(w;W z9V`fp&(4}AT>Yl8Is&$k5tM03yh~laA(*Qo*gb6Ew-?(_qBKaX*V0xM(lVw+>IpY% zb*)R)B8*19*&^_PfpG}?rzRh!woNoN^Pvb1Mxq<*-!-;$2mf^orKjXay@znBcE+(D zTStwt?fR{VO|e$gcGrtHF5`hGRILlB-d^Lwug-7AgA3_lp@e$SA0(xvD}Fu(LQKqI zXT?%-0jWzENJ*}BzBqLXvrX&9!x7y#AbU&Yn~;Ir=MJy!YanTD z_)u{9Vuga2hs71H!N?K?5{pcOLx+z&$g3nEAbFLEg~bSY%JVSvSAy8^6g;AiDsQJFv%os}Z1EYxjtzQJ+`HU^_&In&8e|m9} z&~Z7%95tYzpd?lJoAL5+D+Q&qC%c;Abg7@|;GZT6Zy(5N zk=d#$g7awOHE8v8zpjh)rNy!gXevDtdbhM%E2J%|7^lul0gfsaGP+;#yB7TH=Pv#G z8qbBwqlT5u&JMD36kJ4@nkI5#?;gvammi)>D@@Pa8((C7tVh#jQtwiKf5`v7{t?w| zmGO?NOY{mmxW_qafd7^XMOw@-o-sv}ulN^xP-DzNwe-^0Z!RO%2Apo@0Op`_J6PC##@Iz7w#ZsSIGt zmOld01?;qD!2d{}-@aJT*4Cz>y88Gxz3eTz-%{6$tWKCiC&D9Vb41a{w=5Aq8&c`P zQp7^$Rm%Ti4)FhPHTx#}`|!BS-_lOF)C%bJ24dG|qg?52iN2N+!PJ8Y>#LC&I_}eO zm$0jovo4tLXH-f*+uvCchaQhggZ_xXcREs6QyVZA?WwD@ZDAiBzVD&{&5|zA{WP=z z#9{lN1GecvPqrtE<5YP7wi3vkTlHZA2uq;w@+SLG9-%jWD5mYkjT@mePjaVg6Lu9cRaH5xl%2nBWRVua~e1n7k6S0fHDP^+!6g@uLbVVsi$8qFR@ZO!wN}EKFtSah*wOQDgQ9Ch&n zaRn5PpXJ{nc(+fy42I!^ZI^InA!}uw-5KX_>)Q^P4vaddZNwSEFJ~pAB6mIPppzWr zGj-Rr{DL|hEq1d#lmhk>PkV`9hW5v@-Ys>i{P|qMmL%c-B|+4?YtU4+vcMh(E+D#g>F!R}PQiq^ah!!(Tc>*t00BZJs4w5-z+{H8jX`^YZF# z3H>_Wxw%hL=hY<94zc~Z-^2Qne?*UwJb;uP%+1b6Jh|u7hum!^GkD)pgK7n%o)t;B z5$C*ZqBLx^e_mynx|n5!YdH@_|39sLcQjnl_wOA;21)dq5P}3D2+?~F5hX;A5+r(! z&KRP1B1&|kM(-_1^d7zUPLxpx!@TSJTkoy+$9rqN_1?3rbMHC#&dj~1?Y%!|@6Rb6 z2}*7*CxJD)^I2ftENX{HyV5DnfHw#q4pf3S{9HWgcXikg;bhvg^VnzZtI;(jG*VE zv;d9fr+~?~8XEpG3=qMXR#PS{aJ;PwQayTf2LR(}vKkt6y<2pu_8eKo1rk{+SfZ1j z5|$qdh`~4U(a0#DcDfng#g;Q6ntgoqP4y%)Gx;a-Br0yNP{Q(}>p3| z+V1g#_0a=imj_^j#W(rjF)(wOu+YeQyW<7^QPbcjW1M;@4`xz|!`}9a4lHM z>aX24WPF3=i*h^c1vl2G4RL0Fc?k|Yd(TGL)cb`0`mQkK6$Cyv*&a`SH*kPV=6IS! zQ*y)pR9Q4rb9~Yrog1b&nlF(^ymLRs#s?g;g%{pnsxXktU?i8%FH_@vJ1RF^!SpGw zUMPY(bAf2u<#x~wlx`;-IPO+*>a;8pw)k9jg)bfib*-uyii(PEZEs6*QOT&N5YiC9 zfSh_||7J_L$LQ-yucZS&a6P-KrUpy(UP)yoPfin<^f2jo8;~Fc03&N_GG8O2-$q2- zapL;P1G!D0r46v3eT9$-hQ>y2%36I~E+g^gmL7Xx?Cf0q;K|Y3wP$Vh=435ydo=rv z4{%`|j1Oo!<|hRX`>wz2)Tmv6*JL?vtfneI5tcbIm$^A;_KA9)*;@(INSIM0RH?w4 zZ*%?D!UiHA2&9Vumj9o8cJ;_jhnQ~xY;4q!WZ?Bg@X7wv>N1Jw&Egsf^QvR&mI(R8DTvmyf8zVDux@(av zID7*&8vkq>ZR%waIYiDzOdE!-+DI%Y<$72o^O^l<^t(xJbAxpm$#8kMid7^dIKz!N z3w<+G1?vNhQz*m}q9HgrImtiulkBjcv*q{uO%F=arO!h3j9j2!$!JheCAWBui1*Y} zb}VeNFIF}-vY$RNgTC)?d#9T_JLRfD&Hk*wGn#AO@G%525I@KMOd&cWF}|+ z_i7;i@+fL?TY_CAZTlp?ZnvcIFx9LNaRUcI1$NE@ON-@+kHkyz39!S;5J$7TK8ptQ zR@UQtQPQulyR+)ML;9xdRY(_X4y4NXBcCO#lvo#V5;AbGW1Y;rj&b7;{FYquXT1-) zB|Z~G(s+ake}1}t!}b(g2JveD*c0r%ZnkS$qiPwskdpkQ=z}Wcs?^R^$vOPW3J%$Gn3{?02EZc*kH!CX(%h}l(^o6!>oZmq< z-2rb57@w@K;A^R<;4W$&d@=?kmA})_F#7zN4D|l7OmfltalyKIChd2`DE)9qk#F{- z&o;<3`9@uHS0PqN^(iF{L6D^*xZuS4#jtAkRs_(|X=VFNaCSq(03B0)KEA-17-MI> zma+JuJlw-il%k5J{NrO6Mm$oB{IVOvv2`k&pG!&MLZL?@Wip|5MCxoE30z{9^^<;^B z3@xHj4Ijz8_~~SSgTwdo$ZfOMnzSC7ECh;+_sSNk;x{g2Lyne(YO=@fD z0{&84^7kcst=4s&9~`IaZwMTSjrd~HA3*T`L1jYN3exT5^C`rWwPUMphSz;9zOp;u zMFI29E+^OWbe9YNI(n1ivwPPOUH|#C^m0D<{l=Vun}u&vF837fHBH|=;C;tZ-ndjo z`SZ26yL?c!n8av$H>lhMorn7fjW37O4PgW>62_0kIn>YSJQQutPD+m)3H=}lP@SHf+7` z@Kj0A1AxMLF|VYJJ2;%d57bpZdC$X~`&N?aqgR7a#lRgVk{xr^H27chm)~E`RF;$| z(*jT>OzW_m-@j|hVZv?(37d2}0KHP(hdRuCieBM0o6ScLUQGmt0fcm#{WhD*eSMCst*3Zeg?CoS`ls%BDqpnPrh4byfXb;KrMk&LWS ziT*a3kf0=ss83!pW%uJ97%00y++=$PcQQkO;jdp04b%g+6wEx44Nq9t2QUTH803C( z?t}Q5;17gidH39gPcB}d)vpxw(iHT9f+%Zm-VLtvi;Q#@W7g;Cdg{?FEf~+aOCR`Q zaY|oHOQ;|TBQR}#JBHR4!F{!XHwppy zZzmE6mxCy+Ak!gH90$pKJ;a1Jwh?u>WmfFiz$o#;*offxrt8*9eVk(qvC+ZwF~CK; z{N}kR2Y*@qog0*@qPtUNHSXV+_g9AR>mfm7;3p^M8Rw(xFl3yNwYX=<4z%g0Icl|| z*ymf7i0intg7e_}(`en?VrIYl{I;zLu0Bi_+b+yL$olz5)k=CQaX0C7UThW?KZbg% zPThv#?jW5-byQT6(aQ&KdWlVA=ns})zu>mS8QLQ`yi~HD)>ZkqQ}RS#NnbJL8n92l z|744&qq*C@=CAfG#b}t$il7(_LB3pW`>qJdzeZRm!zduS+hDT{O^QiWPpj985eIJ+ z6aH09PjA*Y$2QL#QZcmU#p<_4?1`(&J6Xy*SDcQ-e~R5P@2jEH9!!OGlPp)?S}y`9 z@tu~B*83Eq_)M6lvWGH6o^lwgGLzxo z7V2Nlq=;X`&jhWqC2e}K4+x2Sp-O{L|GokOw~PKG#+H9Z^p@{uDXvy~oaCS$qlf2Y<`zWJTM8ZZR;phkD z{i4X5=}eI_T%uQeVr)@Tq>wPc*L1b=h~=f)rCgNY?Fq4x^0U<5-v#paG6XZ*WjF>*$^}e;uQ4oHNP!Q-EDSLU z-O(|x+Q-Gjsky;ScvJ)fMbvWpm~_8M`5aw9XLv-C4rtaS3-tB&j)@sxuMTOa)7z1< ztV5ojL*N^|1#~=O>jhkavtUK@5vnep^h`{%sF(?s6daf3;>BSor&?U^080E0AGrmJ zi9=V~=~S>5QzCb`sZ%C!19q&w3-H{Y$r7b|L^ACjn8c)>)n^nbVVYEpgh-Z0wcy!% z=+K9GgtNIHy^UICpN&14Els`er$wil`?BF|#>(u>$jW9kZCFggogNXKL=WT?9RHgp z;HCZZv6Ah}mwR%x{?d{jholvOpEdUVz3@)P;snwuBpf zQcF2DO+R(yJupCZ^x4Si_zG-FH$%mXQE`v=+SsV0_pYqjLVCJIoOvI=EcwbJ3{{$P z2JawUOV)JgxNX9MfhdeY()|yIXeNkV>QB|-ri(1IWo3-bCUwz8VOdQtncVR(n^Yjyx4uit5?MC#g zt*LIjO7YM}q`aapwAznj!V|yV7I*pu+Z;;Q3aI80FUwVaUexn7iGBd~>KJmgv+4Q& zFJ@kf>E(Ou7gp1C@L&Im3lydg=%^6CeqaMcgaH}Ft8}l@p6xEhFrN5;9A45&Ei|@P z`p<&0U)0gacDr17d?GwgPzf1jl>hM6o`Rfl){KQ@X~J+G4H2Zco0*>v0!S*Z=s>*| zki~3bsEPpiADn7@wqOMQM`y8@e01rc3)`gcc8Jp|*wxDug;=dzU0nw+A5NAg*lq1f z$IM4+i=-VN5x$PMPg+tpyxvXO{4#qfej|K-rE+;!&u6~DS#Xz5)FbZXO*a^4Zxiv_ zY&Mk5!N!S4NJyx{VBt=;$W!f=lkUX}FC)vERS*K<^d4e7PF&jAAne9mitlX+Pl)mk zo4E7^-R#0lACdkqC||%rTibYVgHlTuAGcj1+bMgfHDJopl4i>Cb+{J*gJ=AA7cua0 zU=>+6H#wP5+1zIv_7=#s!J#rND{L ztne{cL80)0kDbjg&4V|bRe$O4cjx|ZQ-QuzB{JQ*>-Wpc7B}< z=jcjAMLcg`aC4%xu;B{6>p6IlDr{I#XFL0fv2--chLXx>J(2=w-KlrBeE6RNa!k-q ztwO`IDxb|6PDBbe1}3$D8Dv%gfD#|KqM6R!D)U*{_HuuDJ~v)J%s;uv zY&iy(vLU2@6P1+wlve%(>#U;j#~eb!V^R+3#1J#oJj*_-?=-M*KQ^klqkup?f~?qp z7juq8&aZ9T78uiixP@H`q!6SWK)(kTmLmx{xO4*7>=8ZO6ddto?$9r5_8G`{RsE|k zwY7hiKTn0y;or~lR$=3#DQOEVu4Q~V2c4W?@DKE~|1=A8v%omk)le8aoHo>*<&{%p zszwx}+}+)~`}(966hhWrgw0bI1_@n__fsA&#imRl7YKmXres@eDs_A znmtu)8ziW0VN;yU!O=yrF=czdt8rGW)gNyaq5%NPwa!c3G1->sc%+>spFvlBwW@)V z3xUH;C<~iY21)2vs>L<7SbC95lFPu$YP**{#=_`c+UHZXSN96pnhLirbgzadmbI=l zwA|D*zfv-Xp24s1udlDCCcW-u|706$;K1DeMt&EVQQaAM!xHK;eX)jTjX5E+>{HYj zw%Xh4axko&$*U>C<_&PilO5*RohXI^`p)C;5`~gaX=5KO&54@Xxw)kf$YfKw=!*wg2Az>ymo(L-wNMv8V6ognW zw2rOH?9Id1y@c!Z1@pfPQkU`$b*$_-(3#ShRwQaZ03Gz$CGJ#9^b7Nd|_wPO|yPiJ>A(CZ#p>HG02!DFEyQ zHc_ZT4#rxmOB#6Zgx_mk{;^n=kSG{F^;}EE}M@=HrOTBy)VFb$md4g$j-f&sld8}l@u|6rIrOYKF3oGRR@Cps^)(+ z(MqeIg?vl67JT}@(6)4|`9D8ml$}d$TSkm7w|X8l2hkqvsp@Bs81+FYA|9AcUx;3I zGwwKK$-exU&)yS8D*pU*v=d52O+x-0UT3S9f|C8!%y|_DYusSoGYJ}Z+FfZ^>8o_r zhXQK*s!ZT8bXdtVQ_5LUt-B(94xbbk;ZsIed97dd9?0zy$YwFaf!I((Yw@33A#{iz zE6h6wsw!xJiCjF}Cn%7H-WBfsMSAPsSwYtvbYMcMgo|)^HMzDB&Z;7Zqg+|BrhhTiB6I+9kHgT0v?NlLvK ziV6w0lSRvP)-u2uR(1u&l^fa#5hcf~KVGAOEg|v9cSxv`KZj|S`O zY(^plU{td=I`#+v!7<>qkX1Z|CWZ0Y%=mXiMA!r7O*zE<5NDJVyvG&(7tx78CTniy^JHkbMW&&swurm7b_y}A>0c_8Yx6nH?edi}}KH|bz$Bj7G_(A9LV!mXSeyO)a3dJ3UO!P%MXx&Yzh z%~-yQ9eFAN6qnqfk)$g5kz9P7f7w4ZTsf?D4|g+A1!%0ef@k@%4{N4)>A?B7|5WzX z6S!DftEF*N6ECtgU) zaSy4${;r(ObhlI`SUxvdOprWd1}Yu`?ngg7L_YR|ZZ&SI)iTv5_&LxE<1u zht-;A*FrshM?g(Wi`kUiJcM$po^?1S9qP*Iz#u*uu;~+3=i)$6_D*Ln=^Zv;SRdV?GcEYV2ET+X($ZsG8o`*~evS6M)VvgJbpAsT9M$Bzb#>*$58?m( znOL(9f-U&N77r*Z@Z*^K^&Srs#SLA(P#}{h=+jEifEkTK>K2oi;1NA^{((1dZ zP_Oq6E_2Jsqq!azBc*rxzl31sqY$jW1IMqw!RkCtE%nOl5xo8BggxLqgC{_X=l>?Q zY>`ZtT5vzyW5kA;{?(e zam_{)L9ZYJW~-&!s~V zG5}<+X}j;VF%`OZ>JcCyQ979{q>b-Z2sKhdSGDCZfO0>PM4XpXl}Kb9!u1qBv;3C$ zPy^PQh{Agak8D+^1x!}*XgR817mA3dF?TO}%Wgn+1}=~yl3>IoH=(NCVALj_qnlx- zJ3{*E%*bu#qz;GEi&*L3pG0sHVI@4|DZ_tBhVRQN+#nHY?NnntD%bSOSQJ!L!e7jd zOyTx>!FKtrTRYz*-_i=txvfSRg+C^%sKtFYZ8>z{bwaa&u6t~JwY%VnY((Y-Y(R90 zTSjeY|7bqeB^Osi76+%7RvT)2v4I#M9T5dMzg3@wx??^bp{rIBN?ziMI*f)IHjL1p zw7+44OO4Q@o~fq&gUd>rjQN2aDA?VDUdc*eW zP>Sc;!S|E-q~gtrFy-O0{EOiMcPWe{rJ?i?M}huYsY-J3q)N|&`GoT0wvD<=tzYL^ zrE8u5g7;ieO4IiU+ZAl|&n$7wb;(36P#-aRb`ceOenvP$kkkcBAT^RykgS({fn>$m@-vyC*k`} z;KJu`sttWtQ870kW?-q)Zt$J!mu0^$Q);J@#z)KDhvp45Mk}J?Pd*>)N%V-1K>-n! zgGly;?>4)<_8e6x&^5h-Gyd*#c)7kgGAy9|ppqGm`28*eRMyq|HsUG9-m8UeWYX_2 zxO(UR{Mm*COXeHMm>Ku7XpAJq4wv?dS-N2*&9q=Tcn)#8xf9bU79KZq=I7-=aA;8^ zJKo<@*X&`lW=r_IZZlGAxOr1`4W+8z82m0HX%K1}{+PXQXN3}|b2;0L!+G(TwJ`nc zMax6~U_puO){jt=|LzO$Q&dZYu2WBTv6UDttXQJS5$u{y^`)4vRthS|QPaawGILqL z^Kt8}8mpy@NpHP?_NeHYv(-)sF-f|icI&!>t9O?5O=)7kq3JkS2&w9qYq4+jA3r{bJzaDknK$VDtc>F^8PE|8nSQ z{A8{Oh1z~g9^|9UA^*?~%u1|iE_h1$V&E$Niwu|Gpi&C}dfq&5j&z-xdNGZ1TO|m8 z#ws)Z&4g%woA-SUooK+&VmbD5nnCwX+PmkpzCJHV(!&hTmp@jKyT_>O2CB5R)hI^M zp>J4Vq1cBUN)dGG>4G8HN~l0AtXC;@_4vYmXACb?1^K4(5;F${5`~sS>uP3OsviJc zE|h9iH^ z+vSC0jtxt2fbf!%5UWP-Z7>+-M~Jh-6UsNw(_hm4*{+$0d%>w0&#vMlPIx$P7a;5fjn?0{R$lRR11KYFMRjH@80S;PueB zQnTr5sn>><+J`e_CvD;36B5C}sqNYi|4#IfJ1UwVG*|WC^bu55olr%#VAAfaRYRV{ ztnRlB6dr;!qp1ad4U2J&&nG^{DW&|F@WD3P*&o~VIc{DDzfR%!@jDbr2+gOFb|Izj z+Cf@|KD6pj-$Ys-J3HqPq+n|f#lrCtPV8zecJ1tXVxRm2y>kvdejQ$2tF42eagzZj z!6T8xviXB^la2l)>!CE!hSJA5Q0s+7YyejfQJ|iO1$_VheJNIc=6_hX!Uyly38QGS zfibd;%$tKQ0C2&~DpZ}F{oE_JIrS00(}>yo5slJcYCg%~G@h$-01zf!k;~t-4Aejq z8H~mX0<)mrw;w3V3q@`57|4i2P0 zZR%iSNxF&b*BzZ~k6H?PoS2VIPMVzW&kH#3_?Mvu27;o~4`dC~4QrLOygGsOgCgDd zZxL_%R=c7K&D<|oGLbFA9=I?ml1P}@N!@z^F)Ep;*?=q~B2ioZP(MjDHRC!j?&`zh zphNuu(@Z2}$xZdnQY$heT=)FdMVDltZY>!rZOuxTP%GHAb;+9-i@aI4-Ca3@W-#OF zMH;)sLak8@1YAFjW1o4g^4q>~zZvelvo86C!7pW+dFF*VJ-i6o1a{_kC_dZixFhsz z0!PQs(w34(cO4LPki^s_2W*PrR|+Jq7z>aBSYs|cj=kPCs-5ddDmKJ0i!WxSOW`Rz zWJV^GjyVbS#FCU1p5nif@Z-%7l*KSbQ1^jWiGa?0i4?LAgn>&j05?Wl%`i(@coGqlx zXDv_0AV)t3P!W3X)W%HCI*SEn@{7?e z6qy8EJNNq|OFxDe{K@mSPEcj}5js5`2|_+gtrESC8nOW%2G8E&XQZJZJsq4i>F-Y3 z2_@bl!}dOriz1=X8t4yKIXl?7M3yRZ&pLD=4_$lI-(L7R_SmR z93na2xl1fz+3kx(gc=sl`a=0mw042|tE+*Tgu|25gNV=1oV&iMBs9Z{ei7fk5rX?O z{*)79?)nL-_$PPXKdHm3S#%M_Z)J&slb|n&3~xUiM&4rRnj#x1|Hp_^8ce0)jAV0E z$R#DHg>bM1B~AxIJPS2IPWbU*h=j7Ot>PxX09VGNM*V{8_q`Yr(ao;m(Dc_7pjrJn zm5%xS3QS`1!eQjee`PaKcubs}i*YC%EXhm)mj9ftZ+ZrFfvS~Qx;9K;phGFdFlx4V zup=bYP7X~5GZ($-{R9taOjx8|U19i)&4^o2{HN7BMotzIB;Tnp4>~ zepV7CG~OjeW8_?g+ZjqLJ~7cHBqdA`9Yzf}*YR{QX6|}X<=`kcA!q1Rb(h+W5Sdll zAhBGng#)6JcoXZ1f?^*H7+CxqoZa8S%uV-IW{37%^1_YHiTi(VeAz*sC}1+8XSq3?lB79z{@GP+_QZT|Pu|MZA^fboCY>eCV}})NLGT}tpeG6@5&Vx$ zS>(Bl;|3glx=+vG81q>9)Zs_e)D~l+Ga~E7E+A_%0f|qDB6+Y_1jYU%wVLdVsJ7k3 zO0gy&0;T|Ue>-m((+0oWcYUJX`}aD;uALa`0nEaifa`4cbJ6PS&op;kV_i0ZdMM35 zGnVJ}g$z^9Vho`%F8jZ#2}aInmi{qCF$R-~MUHOM=$s028FfP}uc#H~6<865n8x** znZ2X2+$-zq*_`4xKJ!k6E{$lh4iSSw7q$QC0Yf{(QRFyZ5cfeg?En&SiA9KFeeBAm zZAh@PDS>M5x2#BCVwgFFC=53|_z`8})T(wJ^kpbDtoyYn%$)L<223;ED_>YPRA#3x zHtz4V1!bo>WmEeZ4?{02&fkxZ40ebsi4HKu@D*1KBf1VQr`u>hS0krz^Rb;CHD7Th}g-Yit2kEzZ#zTI+AeER=bbe!Y9|FP&|3W(g@FD%p2)riS{;dT^3$!OCzFQ~~Shr^8AZc9_* z8JE3%k?!zxHDvO}hxpZ9!Pb2huih_jYlSGO^oEuR?05XxlBX z$9?$?x|XFytA2C_4%LPRIi&~KD*;@V%BsLOojJ@8Mfv*=Ly{J>ua#^OO73Z_EMVB)B_Ch>}U4Qr$cVNj5PSii?wxg_}RxAeJ3(zT?DCgcqU1_9tRP-MxZ9;x-0QD2{pKTcigq89b2GA1Sy#pFcT$)KNh ztf_=#H0e(-cLbwyj3sGGB&mG(Sm3?rlqhI>GPVg&~z7^d=R}< zf5fElK*8rPe!2uP4Gm4WqYE1Ls+sg2J-B4PizbP%lo1q{w(FxwqG$mXMSMch6V=Xk zyZO7EOKg4dW=d!;F*=ung^0^b52X}=p0SUA#nKOW$kV&>i)o`keevB^UmtlFv%;&_ z=uOL~w;Bf5{l_#2=%{|?t29G_iW#183qcYB|2PsOw~~()C=>@COsbXADK0jQ*3}Ea zXvt&!6STS|I4r%gt>8skj{gKCjAU9t_1W0XYKJ^k7dcu2RIdWn4EeAF<{5~kmWEt$ zNeZocr&!JJ!^5(DzXXjOB3Sn^8dzHTjRI{eIM;nagv8FRl@&=NL14HQ%Q6%dW+3DU z2zq6{&H^zLSYioHYq#wUyQ3?#Ex3V}utRG_e!m0&@Pa57qgXlDQR>Y4#LOQw&4nEC zLGe182K8t7@Pw2xYa-esN(byGu3um50PL*} z`Nt)r>;9gL?_1Lh*JZAA6 zD{rik7dG-l*v4V}XQv8=09ATprS5kMaX{Vhg7BsHwojL!z|TZ-aS3wvPMm(r0(TAu z$jNVziKU&Z+K9EU0Mq1Xl})-=^O|QxUSD8~Qi=#W22p_XI$%9E8b$nnZQByR-B+nK z?IF1Xgueh^8$>)o7g+T?We5PqLlEO)w^B)SQMLd5OJu48lIFFSyl*te2>;=(_1Wf} zu6NpGPdC)p57#ohNL_D{eAH4`H+52v77<2j73+3*czUX7Ynxx{!xswQ{bcVd{h9n{ zW>zJu&@z16>B9Z_^K|Vv~c8{?ua4DdR>p$C>WhZ?dYNym|^%y4sYc-x!f=+so3ZFKGAY$K_w zogL>b+YD&_=?M9p?u^&0)YhiA#})8ICnsB7&JFX9qqbCpL0b{gb#tL7Fgi~N z(72^i*a!pYV-xXm@>xA?Y#+O*;^V#O?V%Rq-wb?a*7uvp$?;}gz3U!MzSm1H8(18M zuxq#f3tkGiUFHQhB|O^O`endLMOv98WYT}4MXwO6jE@H=Zf}`$UkQ3grFWN%Vr{wp zQ(OZzkK9bP^RLY-WIJIdejaUTSXfMIYDDd%ofP;kWgr=O+-84@AgSKsd}LtqhK^&T zF8!ewv?F$yH{)W+2tuwdGK*t;wwWf&1>; zI_r>y_-KgJyr`m2hwqff{o5shq4%!KNM@f<*uG&>1!gGi+I7xp@;z$Y&c}+)mVM7A zHSkLFUEWQ~J<%cA!wXNgXIxyCGvZGy!AKnos#h+L4xuUL)#S-tgpuD}m~chtW;HP# z7K^k%Go9YSo&8fR((rcIE#5$>$M5Fmf36ouY3M}^;zq;ICyR8x zfV&ml4WLGKOm<(D7P8AbgotjE_N}VikcoR(=VYP?=P=G9H)|>ZotrCN0nguA$gxL6 z7Yr@#QwpeU!hAR9TYt^)@!Q)0D0r3!N4(t1E`A2+qTg{3aCCHfQJ5uFi=7dg+xG+= zcx-YX{o`wRXTrTho$#R!J5b3EROIl>T3Y$^vY9x~JdoFFf+})IR+S08Vs&!;(BYX} z0eg>n$B;RiPW0*7T_7V^&r>fi^R3&zd+-~|ySl_%1t5GeX}{GsF|1dRNU6O>?d@|l5y8)*I8eH`qc zcyUJw$=Ynry$#^u{4ySrIhFfME|E7{5K0Y56R6H|va$93+kJ7*#QGjaL63-G^{`%~ zl0agOCgU3_t6qQgcswl$j!6QIhjwNB*sbG;d!C(x{FHpzkTU2ssU>Y z6fc6Ucj*z^1}BbcU-xA>DM^!D05Z%`q>rGG1fX#IZ^YRkYqPupf6N?RCDi(vL19GU z=IexnjNG!q-bY$MT!kzl8=8$`)Q==M{Gjj!C@RL{B{6g*HwV?J;&&N1I5-mvf|Bjg z7BC4UXE6#~*%BlDDbaGt?_+i`w=$yWTgr5-fA}r~RDx<~jOdBDt~}mmy+@N=F^Wvd zQehmwaDD_03>i+po+{{Vb?gmrU*GVo19|U7?>gBv5$CM0=k_9wq(qoJ|t| z47`vXaY)Q%Ddqv2W|4$XOWjb$wNhTeP|I~V73E7?{pWz0XC-VO)3A&(Y_>{@M(J`3 z!V(LHFdrC(%kpZ%L5KFH27F!-k)qJ){80Q+PhWf&M3TW_>@ey!EkKE6riRf2^RZ#+ z-7;>?waoII%`Tj(YKOlh1oe$bzs>xTkaZ~0nOIxP!AIn4~&d)uc z(S51%&GWX~wnQ$=7zzFUa3SG^#eJNN+FDBBaOn$S7P#8j7oX$82ReHcqL9bMBMWen zY^%cYumZJDnP+yC$98QU{rQ!8E*N|oK}U^O*2+&QYW44>x3pwSPfrO!SPwes5wFmA z``D$ANyL|}cpiwa*IwSL3Isvl28l^QbpfYSdrX-#`~?_%)?E<}Y6t#Ok3sF=Jzw7m z9`f%sunkAJP*i~Hey`7=KdvC)7JUD|=+nUO`SCUG{B`fibs{${W`mWys4$CWk2aM{ zulZOZXb;6N(ww9tdVYPtUPX&Rdcp_sch9n$egLAIS24+7BYb~9CL>?Vti1Bl@GfmJ z6kWLL;sy~IlbTiWvuxR-mu@-RC{Ofz?W8kmEphK> zbaI|X8RjyNRl8Emzi~ei0H&JX6YIh6DYc&My{F=7pgP|8R@kM&&eo;n1ALUnwsZ3W zH`M&=tX-dt1C)8#rv8_26vPO^$0N*8w{f#rrXDo=;-e-<=-p!o;9fs{SeLp}Uh~Lk z2f1e9>^636a<)1Ur5lO)OSo^m;HOM>r@w2+>>9N_^W@*}bvxZiaX8vd6Vz0sN-t3G z7CI2=wFZI3co4EhQCcGh3?WodlivFU)L|2hcXH?Qb+@_R%6;DpUK-b$@0Zh3)<`1- z=zSm8KT-s!vYOxVkVvk4kkbNpN7qyHweqwR{hGfwqfm?WD`e=ZY^7Kclyv7i3`k6c zOTE{J17u~njn9QvpZt?TI#1`l{6*v9Hd}OIsAG{1-JeiBeeKHTy8r_N%zAMl=PayP z{W3`Pof?7Hi199O(cJK@yU-;S!lNjs=qYC=%gplFvWxzUa(oHT-sVTHC+V>s`yY3a zR-r<}GwP8?b7S5ntH(~DlZpPlY}X%A5q2B;@2R*7{aOg|!h3pz<$lP=GNtToRnZ9_ zRn@!Ki!{mIbB<=ZcXB^CM1{zrJ#&&uxXPSDZ8Lq!6a8=MrNapmv@yUY+n66lMCJ=6 z30nj`R!G-xNKVUp3)(_aIDU_-m9Mm4GZ2#nd_%biy6&f3@|*HCpXn2)h|LFzVd!=B zRI(4y;6ZO$TpTf)r(%-fqiu2UP-=I3)JSP4FX@Z8ilHH0V35?|Rd!!{^hwDFD?6JzfQDY`zc|mI^I(a8VKV=vNx~`DP zYb`y$V{$J5=ZTR~?~6DS0OVqTQmJZa7t&&pf5EPsp9X*^-H#BX@2fO#6tdIWb9_sn zO;fL)I)Y}ngacx>*422S=`E`9YxrsB(!qX5lY7oo^RL(W&?*KvUDFjz?}dDzME0_tx!3A=ZAOGfHI-;l`za z#qG9j%?jcSY z78*bHAe*i`L`lc1%2B^p2O-cev)^zOKpG{pFOm+y;jE|ZN8j;QKFDhUdpFQ95pht? z82k!Gc@fxOopGoWL^sM26CBQIZb+;V83ep4k+Or3-imvrwYVS=)5)_e-S*8J{MXsND_PtE zv;+c74qt@lkg`J;X3~Bx`*x-rs?;^)6lwG$a=n;XQ+JnbAvk@)OvrNtE7L-`}1NuBw7%Rb%DlaKF92$vm1O4y$K5cvDMa<;nAh&Wi8TwfO zWRLV1a{clmcknv({2X-8GaDq7P$?xO=<#iCHmK9L6-ZH@Er*!h5JnT>5&I;?2G`Dzf_z3D}q;8IEnNl9ZzEJK&T#`g**GZtZKcB``MQCaRaz0E65j=XiY)A z({c-Fe_0VN%Z8=9Ok9rJ*)3vFfuejy$fe_@Mxg3Pmb9yW6Mwg<1Kglo7AL2-aLA7Y zxYr@OuIQ~+&mi_k?(vgX(Mv9$AgUXeOu!?plfu93Dka>^TRjm>Yhj@(MBA&em*3+< z-eu76I3Yp?Yrl{qkE_pS4RG+8())N~oe~D!C58okLjF2A6s&{|LjQ?^9o`)yXjZ*> z!Es6j_IDxQVD&gA1-EB1Tp+&k6%xuAKK2^Gf;>UKFGye^^{Zp5`+>~`s%rJgJgY#x z71=Vtvy2vSp-BlQ!xcVu+wO%n5p6!r$}htPw^(=g!CCmwt)z1w9|#D@F{&sU#Cb4N z-hutdm5&|_QYri?2C17DG_p30jGp*Slz^3>#LyJ8<~ppb>R{iSmVUEufWG`x`g?Ky zuAM@rt&T{8wo|h+iXmPpn2w7(eA^+}hmN5}YE>V%HUW2krKp{<2LB~@{FG<&!v!=$ z%6Yo9u_PYYvf24%WTL;`vpi$(s9)e9qx_bgHyPyT_o8Oi(h$PbF|n-7 z^uVtxH9~^x?VZ3S zb?4CJ|E+mR5yhc-PC#BxdT&!IjE<`UH}`|wk=Wg9p)de|yi<^o)Ci$Sahu}+eP^ny%iNcgZC-%!iURk6$~uB=h}@fX8)!)xC}5GsTBXu5|Q zT=xv?LB1D?&9g~FOw2xx3FCQSIQ2{wNd4p;ZKjhn3mtp=v7$mzJ(l4o?+XSmtRQ;W z<;6b7TP0kZ)E_jm4=ixw>c}##Ai4CT=g>IY3(vj;lA4=E|2e|4e}yWl8qt)l;v#zv zLI*JIo~PdoroqN^9v)N*oITT~F1p&}ihE!FHE=yb$ zX{Exf7kCc?Pt>#)RPTPtm`g6C8)Wm3*T~VM!U@~Uefr>yHqn|6{cb4FT}G*?9;-$+ z1}!(R@Uj&wbR`q3o~9>MaZa2s5vP?kh%1fW*M(aUwwx_#)}<^J4xCy~W{bHGMcoo8 z{*L{_^A~-y1ozJ1+oyxa{C*aros>;}W`6@SQ*yLj)#LAkI}F}hAdh;W%SW`rlgDi4 zBH#lnUE)ysft;;3fYcpZWNM)wKL>GU0>U z81{?TbJS{j3^I;|q1C}g+f1)S^ZMS=nh|si8h>F0+1TwXyg^v`S#It_?*UJCylt zZDd=f&3BH*Yzb8p&M9|_HVjzk_5Gvbrv7`AJKoAz`-uWaV?$Jk{GIv&t7i)Yv4Wv& zucH<;v74`+%vzM>lz!TOP6?61X2i`<^!`Yk_ulZ0MH}PD*R~w#GNbL=L|=O7REtu) zAe2Xwbh=bBWiF90EKSc;aQfpXWbt$cUO9l zf3dHc>1szM_$PMmt0w8~r44Ic!yF#c;_&7Ma=Wn7?^m7;TgYJ93G?wx^Cj}qYq`q z5C5-TQ41eaj_wH9Y*aN5$>#ApVvdh%+j1+eQqwOyS65wT#Ujr7_DM)cl-kGDpXyw| zYF3w)SRDdg0x!9zIMI!i&mOt_|B)4o~gnfBQu8fIWkAatK-E^{hH?MYHg{uKPQXZH;&C86C5**ruQy)Nn{ z;_JTgu*$yhii{6Ac$fR{S0uxojccCxcf5i$z!W#pIH4n3iUQQ5Z9!f8@GNAt`$*Zj z^~Q2NuTm1bdQvehivnqDSf(Wx<+r=@HGS;^I@^~?_%C%{%3#|T3n+wVxD%0(Y)qJ` zkvDy{w<$4yYAPe}e{&)O-Hd~|!+hLb`| z)m>F}i*<5e(9mLfqospi8|n^7aLS6q|^ zmMB+rG_$?b+}-0g%6-FHS^2~ak%Hqim^a#-38vYF@E^K>!vuuo-o2vu^yosDwDp?- z#j7?WJb%|c1%3{0@-)BK7b_|LKE}iF$}Sq=KU=56m6uq>;}M@oUaf|>GNn(#-HpcY zINLYU>N{pDspLcDWNJFvk=@CDJ)hIO^;B;DcoXYm3&sU?{tQBvn16mMBJ>o}x^oF1>ZPz| zl=gphs<0Yk4#l)`Z0P3)uxU|KvrYU3hI>bvyJk<}duW&#avaB2K)B&$xbgn;o;`T> z$YrT?$JJi8;LjRZ%qU(@hC-1tfuguEt7PJ~8tI*|(Nkbr!IAvC^vk9#tk2Q7Ue>{x zD@a9#K4G1Kbr8Ox|6xiLKTQ>>)xZ&RHArT6`JrwR1D=_(F_4Je~_5zgszn|88Y zK`=aVLzJtlYyHmyokb>m+!=(?mRYk~PBY3rq00{~&kP($vOSephPDPT(yV7^o7q~n zQt_qV0VbAbBNj_Uocw)FNTyP868yE~Yib7Mb>sCQ!ks}&n0~Xl-rYB!CEt$PVAHvG zYU}u3Q~4g{%-`J1)66WppaV#&tg}j^IRpz1;;vKQ) zc#ZyJ56^?4+?wg!nx)p~?LTjwK3^UFQ_OE{$%M>Ejh=IGS?yu~Tl1sMn}^YVGcjc# zdQR+;SEw_@>^wbPE(3WhLScrls^>UCZZ%z*6t|AqYMTllcVcet-NDah_+?Uk|6}am zhkF`7=CWg-x4vrJ#*-K6F;@#Wh2(mIsL7*+uzBF(Jl6l@=D&f*qpUqHla|m)v|~|+ z%i-z_@YP;edQ(?`rjv#yHCnl5AVY&<<{~T$K|qeHU>gA%L?9cy5obosN=(eKO5wcC z<}fZoI|J-dy!45>DkR)KgLch@B^4=XKa3kYn$C>>QZ{+`in=uh_p9w9=d1otJ2;Pp zk=Wb=z9i-%7eJRuz}D;*McxjCsP$O`vMC~F-x{n3RvL?Y0|!O>!Xp$~@5`}HK*ts+aLE#R4k6pN^^w~} z;rbszeH^tHrJKR~5Zn8QcVy(q&7+WAA-U2WW7%SGqtzH>zPoDmDki8m8INo46xR4W zd6tTdAb8(w^~ui0<2Kpc%RGdCC}8w2hqqJYXboXKTIGe^6@XD}_WizUoq=7T4H+$h z5mle$(vL#AhI|>BBQi7ZQ8b#u2<_+je_g|snV11hvKM@#@!&WX*gPnta10ji1IJ?~ ziI03V6=@d`@)&r}j88V&B;EZc(TZ;rwsU><6NNgJBq0?L?J-vk&fG?RIK+8?|I%9M z=LvviLoxbvvlJR4gSxaq&=}wTHJZtx_h@l@BJATU)O}_MF+>=%(BUt9gDzuQ z6KT~G2R^+AK5V4;)LlWcOrLZ(6$=v9d9p35O}~5!VcE%(e2rb8$K~i)5+u8y{YWEE zm+1yDRIk|ZZ%1fo^4$2piv}u40JG5Q#fcpzKQ;BFmB#UILI%TQK1;WjMpIc!#8_G} z|82E`UJu8wL0x-bEtW*htHJH-Q%?Spg8&?0aDdVBRi(j9APo<8qglw~sQ>?O>laW= z=l?mgK?HlG`C9Qq_eqtU8Cz}8K7R^6?<$HCi_gQ}88^)x{gymz%Pgcx)e1Rh$wl_8 z3vqfM%jtaT`(&>x1gC)&}hgV>Irq zKYygFw)i*-G|d1L`p@alw4ng&oZ|$XloV?7{C>_ zO;=sG!w;(&t51;Caumwn$ixX5G)C50LM*`kv;OGt_ZkM{i!J+N5qZGzw8LVllL{tt zIo;?_Thq}oKlPTnZd<9!@l<;BMBWb(cC`Y%2WjxM@H?5DrJ|HVH}|h0im86e5YvjFR_mCS!}mlpD^ndZFv@ zhlEAo7+loVox(LkI8XpL2|pCPTDA#X?|09r|McG}Z-b%neUho&2lMxUq}z1@cg*ve=trUJa6zB0o3$N5%#((EMl89ShZRtE(aX zit+OHmH*TZdoL|NUH))%JHqU=z_=QjF*=9{(zD;ZymplBS}2F=tzf|QRSk8=47~9F zg-;$KGYodK6;M|wk-M=czxEz*k6iL0zJ2L{2dT}?gKO#-Gej0*bv`~IZiA|%+Zra+ zow^R|A@i1bisTviyAs%W-Mxn2xE}z4w7x=X)mB(0RM%EjX}dW)3qKYIz=`mW^Z9p0 zj-7$Ln;sQinVw%i6>ShNR_@~Rodo#2B?FhLPtBVJ*@7(e`i8Yz1|MT}U`j3f!PD9R|@i7CYTIB+^#TF$D1>N0{C8KmHcz z+1Qf}p3buB%Ew$)-!=jl|2*@j*Q{_I>YJ67j#JGXh&)4?#R0kkRZdKdauP9(Tj2t_1h0g72sux)wg zXhAkt+`$&~`OmxQ#v}e3-oZB*(zE6B+7%`SnM7#FiAY`V@skq16_VU8VnTBPYar)u zMKJlTg~cE+u;f)EW858zA!#Y*jSo(A0fBgbz9$j7v{DX~@-*CeJgpiUvx>RCc&Ayp z)ln=7T}V13czIpxzAB4Ad^?40%naH1ffgV?hdH1#?X5g;&En2B7O-ew#Dmn-^gUil zx{&AGRIzhcz=K~wg^TYT@HW4UtC2D9Qs64E>px%D5Xgo<3?fLJYY^b4`ml0EEuF}$ z5?bv3M1XX2G1nbcxF5~2ifa8_Q$w;WBLjpcbLtk>)VM3`@+%u;GO_(s7XW*2z+j+4 zZ9LKdF3SJGw4NVPOwrCxaQk))RO=sbuN;7f_lbyzw_x%e z88^-0UCr1{Q;Np$8wyn53X-SF4g2I9v_#D{ccqG<&DBC1WJqdW z`zsCGz?B$3(HP6-neV}H7p2>~TTV&>PBt}+o<4n`Y*lGog$*oupiL1!ZV2@$%JKB- zjlbUqE|58SY+>bYV#toaFFw5S23}58tZ79rj*#Xc`^8ki{fV#+?2y#GjFt zpG3g3t`!3?mI1pDt`2>nBRd&KycXdY)Fy_!5ED6&{-<@tZzDFQCbKq9lzdZAE>xIA zVBX)O;YA3`CfFzt>dR^v)`ap_G3)ErF|ou&g@vqktz^HgXIM{wg;t?S2a<{VNqDca z92}B3Gn~=paF)+4a`y>73(N7Kp2P57^#^5;@aJ4BW{@OqZ)7i{PSI#Q`<|d6wHoeiJN; zRlB7yS?;QMzjy#xjgYN<`0Da*WivCNnI0>*tcjcM-%ySV(#1_M=IbSJz_OaJUOr9@ zT=#4>$Wa7|;LQF_CsYomb{Fc}+XE7+Kbb&VTUB&?LqM_^AS0uQ7(4rMSuhJ1?AhMF zk4!!&{p>4^WOqsOY-{1&NQYkCl|>HWVF3=X+*<&zWtVx?^@!#5yZ!5r}^_~L$h_jJGJd4sBj zk;OakK4q28p(BR4@gpA+t?+viHQxA~m;9)iv23u9b+He3yjxrUEn=_jiTNJA-i70Q z?9Kth488l+{*QX)lam6{nf6?Qzx>eMZ!lhLHHblMD}z*yz|aMdyYWi6?b<$0oOO9e;Y5)E*tvH4wYWKz!4VqlV*&G;d;arwuTE7KhnL& zV7fKQkLKQ+U%Cb0$Hr$YW-*cJ@d3*+Lt3wTcM!?(f>h)eSS33hRwFMOJ=+(>u7>ey3ye zg3_C?iw9e|oOxPiU&NpnKcymvoyQ(!pR~rEYw~5EAdB7Ya?bknOy)zU2ViAYygef_ zGzVhf(ph$P`t*T|+#TI-qWh;dKVgoAvaWLL=9}}-YgYqk?uv_B#~(Jw5tSklIiP}I zTatWcuhi!9{9rGjZE#sga>J}ENMbx!CR_A`!ZjE$^3t?>W54Ca(7@kn9zp<9MP~}- zz5Q}@LTPPRIfXltB_YGd31l2H(PRtAKpiQ{f-k32$tw|evhUz=r-M z0Ip}=XO^B+ZEZpi^W0H)dV=gDuGIx16Lq$Z7B@UAI2`Pt_omLVvP7&;(y|4UEvsAQ zXAs$-B;d|*iCbkJkDtbG=_bBo$FcKUI$qubTc$Ie6kn%oKQw6%bjWm9IaHvxho&_B zQ_IYhQ_pHN5eG+a%f#&44@0$W#9T_t)2Y!`O?%l@(fzLyG-MIeRnhF?LLWfeGK&k9 zi+a^h=GtFYS5*@+EDc!fJu=nYf*C%2cmyL<51xoImF45oR9>?I|5f}5Grd=Li9r=`u-P+uO z{bYPXJ0>w9;p%3;C5?-N^&6?X0ZZRnJGg3I{XEu}U4t@?2T6^sMaZt*wE3X2r#ui>J$KM^#b9U`iZSzj_GuPoQ zcQ(>K1OcI9A@iU)+>voDjjheqZ)P&b_yiX&)bNNz;01dI&*`)nTULqa=CAANEu^=% zWmaNhO9~j{`5|Y-q(Z7|Y`WsGMO?8Vg^Qv&dwRt3xraYObS}z-jq?=zLtmc$qq3wT zAy*qrN@iwe!yNWS?<1hd?fpH}T$NetQ3yxUQk9}Uvq~6ps@e0}d?ip;Ev()l5L+N< z+o26@kME9saKK<>c^YOVP}Wt?4Z8*EGYfOSX+SA7UEMwz=`TGb_j`00E@?kz-s*B& zSQ4WsBv<4t(B5Uf4>O|RqMIE;Exd?!5?XYBhb)??Fyw65&z6rJIaTXA#rGT(|4|y?%LP<-#^O1Sc8ub`g z7;UtFs=c~%)jisY34rkIcc2p0Bb_OCwhHtOW1q`0Q z%1R#uMTN-&i-@;3IuSD{4VfXRzFmUC3_$STZRGrSQZlo+hLq(LC*zg!oL3!IcTMmq z4;cTd=bdTDg`g^#P;@ka={Gfx3XezN$JCsIx6ZxlTLArKpIGGnzQ|PSpS>C+%1a3N zj=Hp}F^zc^V!4R=64&V@2(0a_20Q7jhIGkcGXG-od{q7VC#>v+&C*S{k|Wd7O-7WI z(pxwmj2f>#hZNDboo!>!K{bhA1ibFq3pA1Cz0}17_3W91>}VjTq;x%ZnN#vRB3x%`yuB&zOO_6MJDQN1reiovAd;Yn^AZbEJR><0zGUZe*G zd3_bObd6q6j&_bGxG!oO^hF)g-JXxgS=oDAb>#Bw2WlnNeDU`PKOH*lI8mYoHX5Iq z6YSVn^YDbxCB2<&|0w(PBF`O+7hU4(PA(jrLW#C3I=t&2NI^n!5q*iV!Gj^(H7M4h zy=x`-X$61MZ&}Q=dDa(h9y+w)Tj$eiSahdTos&!Ej)}^OVYY%ls6)UnPj$WBD!sp4 zq0L0)d2~L-n!%0pbreH&1T<=K25m5gs`%8w|;2(KPScja8}lkFt}14=SMO!EG#{?ZgVD^?=CGNj3?Ka zcvkt30F}^<+%kAaC3GPftl;KLOF{v@hnrgH_a%`j|2ZNwca!<1E2S%@fZByG5LaD;NQm)4l+ql z>egco3YNnd!JACgUg3&UQG}_ehI;+EzB!&;-}?8_YJpu5Zk9*&BocP?@6q`HG$5by zoTLln$%f#Hryp4=*}&l&84PuH(c(MX(D^rdi4MPWyW z>A1Zyp2=TnWv#cHU%K>-StaCzUVO~>_{vnyP*>Ka+%F$yEfnT)7t6`XVuCPePzv}T zD$5xOK3&DS(timu;hRv@SWU~e&2Y0UAnmbF>pxleu^64sPg1d8K8!(Z6)mA|c9G7J zNt}`NjEu(fhN;k#6AL%=_IBDAxrp;~!zGmODx{eRsORmqZ%{~L|8d}B-r+|EOiWCK zdWaJ}Pb2%F3`#4c@dIGNV`rkkztX*70RWQN9}Qkwa-m2p_J>hNM`G@H8mqXP!rw^l z5@(xC7`W4(T+w9%P@C*|XF#^>~NF|V;gJL%~!e8t7u>B~lXiH|#$Mr>Go2))9 z9uQN6mUBGHA5Axm&dbaGV zKmF`oZro&KNrSu7Z+%$K1lnmcxHNtWgVNo3dktD)+d2S_R37Y+sB={G+ykJ9xuw zpVI49?orPww+H;Q=f~U}M|SJht+H|+MF+U;x8S0Kwl>z8*?R>Q{s1tsYVjdFT)g?Z zf1mKNeW_Jr=O4)uH~r@ffR$$>#jRWO9{VI>ixi{8q3h=!MGU>MK9TNiw1D2;6ap+qKS$if(D@w%b@r7ERg;j|x zA2*6fWulfPs=JPG=CNetvK(NPhV9UAR$I84vSl+K)0}VKWU(5$nQpY7lF$lDES4W;$6bdm^Lqn&S)3|+ z&apK|1VMTp zrlC{%a87=q9p_V_UxW{_RMT>uvhDB3UwtDok=Rmm982Le!=;$ViQPfHF>g6p*7))j z+n4UTUnwK+?nVI8&lYxiyKeW4Ry6guG_vHbg?l$90w#(x`U@eV{Xm6TgQ>_08f2`NbM4NUw(+3H-EN-`{|HcRUt9z(pNk4pK1`8RL)c` za^ryFZxKR?;1W)8_wHSdZIeKN{DQQ$! zj`SS@{5N{a{ne|rkATWf&5UxW@f$i(F7zZ19pu}goW~dJb-t~3E$86Gg25C56shUr zT>#(rsjf=J)ddL`#7fEF&O=RWqzL<0k9{Wz;aopiADQxM-QuoPP}R_d=EQxXT{Djd z_)BT7SPGYjTq}BQxr)y&6$GK2A;v_2--$zC1%cE44j3f=I5>~Q;v+ghfnzM*QNzov z_@$CZQD48F+r9!++rCXy9|f1=v|H|}e2OAH!B7BdYs(L*kS5;L7 zT3YBw?o_T~{gYB3+D96&lSU&JK%@Mcm6S%zK@N6L1;j3&#U!t;BXVu=AE6a*CU zVFoVq0FBtIv$oJF`r%=QE#kvVypvVDS97K5`}-q}Fu`eA^*x-&VVoq#eegW^oP&Qb ze{2I5f3M$(@W)*ScN5hRS!5;wIK5xa%+KQhZY{x8h0s9$=HNDID7e>`u|;rV)v#b> zXw&~$M52mYsPU3e0vhTfn(gE?`lFGf6o#2T+7MyR9{Zd{D=bf$NBQFfk=eV*;qd7nLn#M8A=)w z%6i7%^{~iBjhf0bbrN*1=}kQWCuxbh`;jMLF^NV9~-XX6O5MMeKVx zmyvkfKf|(5j^e8)@hIT@Cj9KiO(GK~^+8M7fU-N@;%Shzgw^g;34Q@ZcjM$P#Tw#O zwtVlpK(p*Iap?DfPc$Ve0*%`F9jLk=jjfJMUiK;XdnQD~z#J*s3*|X7s~P~EdPoA; z&g1#jW?Mq7`|$ATD?^Kx1ih~n&T{TN#CbU(g?`o4Z)MPFq4Uo$1fYX12tLj=(Ermg zA?#x1zqZnhx?TJ%rCM5A)S}+?_WEdieaH)RvQhn{nA4u^SRsn65Y@(d7MmscyrC>O zvaG8bACil&7uh9>ZJYSCtwa=?vY;kj8mo+(3{@gazBP-NB3tcZ$vSv;Rftfr%wwF! z7sxJq0-^pI3KHfJS?e>u2GE(lgIRL-=4OO`W_;3$J82|1Ufk9JG$L&L*qGTMuGk$- ztrYT8YZ>M{&J=53$8vzA+XGP(@Le2fv7VZc10%~7a0vsBtzJ{`@}|mmANu^cuxz0; zERvFBW1#{SwIN-ow#d6|ZE!3M;BBK}vgeUw)IE-QX`hHztJac=JZ4EObJbJW|n zyCE51+Su8_Jxs#a>x=QAFf-^?fNhoB*?kO241<)x1 z%kk9JWp{+mOUWoyuDa_!tSNsjpQN~iVp?c=w10s?s4;ELVJ1!LF;~ztoNE}gt<7}S z;dHV@G0kqU{!8`6#l;>9}VwhjHL5#{OY@-SZ&9$&;O7=J(@5HY6(~g(JHmogQ@|TLB=EPSEb44Y1*n~ z!=V(!@fYk6^JOm%_lWMT_=3K#l6I}lwMiqmBQ@_k%I5G0$b-f#)DxIB$WbeFq4eq2 z{IxcY2L_RvO8B=q*SOwT{f>AI$Umm-vpNz;Cpi33rxbh^4ckzJ>FQaYN&wx}%a(ne zzSj&s2iFgQ{E7;~`5I(eo28k<&-kNtA%!ZWVDIJ7xjoQbWZ7Oe_w)Qr2SH{_W5U9) zSS-VT0?6XG0hI}K6ic@UF+ZkWnjW2&>JZF2SYA8NgPIL4_J&8hCMkXl=Zwwe(jhs} zoyevZnbEB{XK7@?m)@Sk^@fr?^(~t|E(e}`L@z07Wa+U7M*>qe@>72ojFt0 z9qiiuU1B@%yF36lo&*hvdXAcVxjaWx43(7jP{O6U8|B$<2HMA&)1_FlE(ZUdq0Ls0|wvJIg&4+uhwmvIP$Zlm};cr>$i`d=7?gSnFg@-HAVAiJCm;A^G> zbva-S1I19~#s?co%(1_0Bou}&=Uo!~suSPzGe?p!Yb3aca)o8wOOh2Y25?&?c2CwJ73@Cwl7sxeT!e6UvCbQnRg6cVB{-9 zwnrUPQc_+91@RsTnV{;4%)6Fq_^2LL7^K5tzGWlB!w*?dDz|(nB?&#8(FXy!Ul!7@P#b#2=bWrb;=1v`LoFDSkEGfx&%C)t)bM^gfGpkssZoz6fE~ z5~C-unH=^Mr@wXF9N#nTqPBa_O`K1XdBD!7$p`>$!-xugJz*L)`703MLo@^(5_VW_ z^R7Vd7PWit&#nLcv*GljNYb5Qc@%Qj^mLkn=pE4o4?^jkc2Q>1@KR$UQ^F#BtG=S_ z*&E-&4$2m3!jyCkYWiElnqlj&38IHxT7Kg_91iW76nAn?$9jDYvz$}T8ogrHD8qc4 zfBh%hbq@h1aQ6MSrCE`?QhFeiibP;lA<-0RGcT|t7)@5l32GPEzi*tREMSXf-wkB+ zzx{)6Jo2OprtT1|YVWs|%A>4-6KZ!#$HX-9p(}B>#FUumtTHj5i=Oy>8{>y#Os)No z$QBlQZ@dkNx=ki$h`OR=0aXnJ+6`>X%`q7Q7 zRi0t9g*xcdutw-pwi4L z_#3duYs)ATY-#R2Fx1ZmFq5htb!|+XDds{jjx+PK-1?-^lpHtV6#n7&?cmoWG=$dZKU3e>;I1 zS%+e!KD;P`AIP4)`2Bv&O^KbfckKh2dE1Zp$pT;{u$+Mn$Aag?l$fE$x`FG3RU9+O7>S8GVK)WLOk=(*c3^+Q5V@IhFw@>0*eW0h8 z)lign_}S%|iS+#QS0a_Lm!?A;Yk~_odv@gdYdU6)mfy`-^agA$Aq8s zJ3sUO`brb7#B}z8;vZoK={Se1crL?cp(T$@SQ+D$;y$$c!ffK#naoV|!m9@u zDLfh6d^J?Jsfg7gRptLFniv>7G?$||Z4Y`Lgqy*`OS6{w`gGhO7iDavzubbsF*2_1 zw{^kB!5I*s$(FaYFB(G5+>E66$CEZvIiPeX^bfIbPxGl=fdyBcTHi zSZI^UD8z}`sfnCWRAJ*GOIb#0Q?sd7vQ9HXub(sgwMk}g$+^Wyd!cu!NW>qoQ|95*f4k3g|N1DAgY|CZvj z`X*W(Ul!EL{(JXUJU}3rCFaDi7&PT!iCRwKON z02%Fb_(HqqCI6-!zM6nuWXV~d#wkhqlL~(=1{_JP**k4Nk zTe@zVTO8EXW_cfR5tI@+PiFAs@7o0#4jL9|JP)T;g<;v1x>%PJHwsmzQhT}p9kOYa7tHc!b^~?abzHZu$ zm9GgJ8EL7d^u-wv5z!lt#fwB3gY!z+cF|k+E@Q9u+w2yE8XorXMSQ`RI$WPphK*}x z)$!PhB6;0I2uZZb(?XZ$G{2IC-ofJV8Ez2*Hw*5~RLaWD{rs7uv8m~ERd2*gdloPO zYRmK2+O>g8n+?(Bd5X<6!}QT5NON>0UU8U>ss?@*QWGqaFJ-6wHa+49i8me6s)L^z=Hp;*EID+$~Hpm z03?w$Kal)^j+9GM%qw%%#Az0-c~Lv7g}tA+p!P_X|LQ-36F&4<0e1w8o-lH18#@c! z*X{5R?PzQ<-k4dd+uhInc$R_GR_-UyQu;dZftj8_DLk^UP*FaOG**QyR#o5Blu&}N zsCEJiRFq@;^sJhD*FEyPEtqX7x$-xWbw+rPOpAmo*rxEG=Q6QT6RlTNvi)c-r8jvY zlR~;n14iNb?o*@e_Y`qOvyY5(K&*P!bkYSM#yZzPV3j^ODtnP`tToOjKq<)tL1c*kSgEMZ&-2*=beS z@2l}v6UoKjd4+xJb1c6Z#9F&MA76lp%t}9O`7w+i%(=a{t<1Qly`oJ}POMq!uKw!W z+!TUy`*vKxpv9MM!q1xdu*ixMSoG5+KX1I~cg|*u6yiT-jaILM6m96y<16O1D3!fFEe!bF4!K6hR|BZoV*>scHhlbRJqC%PWigP>_{X- z8Q0CH;qk*YBSx7tJxtw5Rhp0Oeyq~{1nfe5XCeVQh{Ek1pY$?N_0iTlV@}axlb=NK z!eA9(Vu2VaMT}59?lquO5QT&mPJWgYXsG;R9t%PbI7lU0iEDk^U#(yAMtb3m_u4mG z|Lqi1=KO^(#g@?FbLM?fba8-1bP>xuLGxV?%G47MyyUy)zmC8I6@t5qlz2@mi;G!$ zYGzH&7bbOJ;B@E=gf_2sQMp4Q{Gx7zYkT+Q;^<`~ZhpGFSWUAErW3n0(^fddaZi!G z*ARm0wHc1;?a_C1f(mWgTA2%u6bNd`P^rccfGgFOmSMbv<~-Ec#6%38LL0Roo&3A0 zd>~$%=D7HTGOnkNAVynRK$cAZ_VxD##Gn0KN$)3LYg`ZyqWooFF$?6r#^nkieRAV1 zXb?g&bs{{(>Gyajwv}m>1w;v%1!u!VBI@mRT|eSh@`u0TeWy8aNue5MAuEGBD8!m5 z@G^ZfQa`_Q;I^Kg9?<@_l=PWGHg-=MO4+YTH;<5omE_nfzR?C;QJz7A`H2~?B5)~e z`R#-8NUp<7(^Xpk&V9pu2Iw71ewOD!*7Fmyb%OV|(^uIwx`e?eSMnsDb=%dgLpM!bJ|w!FXT z?{uHU&-6as4zJYuEl+B-NEDm2^i?~*s;Y{bAS(V_CPBTuj=ksp97)+WXI2nr&xoGq z?|1!x{1B!=1o7|*e#+H~_&<^vrj5f)NlXCuZb947ovAm-8tSr<`) zPAbXcSO+M6C?+!>IX7=$X6C$br5qbNvu`E>y~kq=#;9K!-GamMSm2bJj-<~KSc<=? z1OmTB>nF93SiJjMBF2r546f78Uw(gCTh@x5tYZE5r<(q98}%POqI6xNg0?Qg|DfnS#qV5tqAgvYn;1i5$n#F-4^AG&)0ypIvD0{@_pywS?*)y%`8&-F3&MI^KXOcrdc=qe0oZ84~g}WKn8oz61`sf$Ja7gM{w;KeZof6z{~C# z5?mihJm&0FT*7-PXaX996pEvl2>KPoWvXiSalSY>2`6zrM$%cdY4RFsNehOCcJbUB%Hx6WMIUf(DT{{bWKWc8 z?SFjC)EPXf86vWQGHjq3REwh@+7Z{E#(HE6;qea!AH%ZGuwWj;6TgJxXg$?A#TrK= z0cR(tq_ZMJ`_dz49zH%*0cxXI-5DeG7&YD)H!{~?(-RRU=0v%ZfAU+uyM)_E{?DFY z1RpQnJ!4^NXFPG{6wX@??5&^<=CP9Pcw5bnX*9~bwwt92OO_L@PASwv9oa0QrgGwk zf!dCyC(FJ~?Q(Vy<0BVexy|LDEr=TPNnV`Lnx)c1I!0QZ=XtZ>NB^b6BD~{z*?093 z&M`X@do@E5YHhjeb(qDhX!2&UZWQs64nktnbk&c$uvw{Se zH}}#u_7dv4JZx7ygI<-?l%tZ^x%sp2vatmNq4WEO9L#Y8$sDZL>+!kUZSQ(|dWr*? z=;)Lj9UTpp>*{u&59a7_R#aBD9<4SvKIP+k5%Zx=*0zoQP;$Hq^X(45tqc`jGouK7D2vTA9J;6g=WIuJ*{K!e4+MPH}J?*qWlwsSUO@W zFKsyQ`pCx5{;$~IFGmVMcN5OY@y-~idvptd>&}|)@h)9c+2vR7T(l#1I>8fP*@vs> zK@0!{1@@AAV=gTA`z~P^dgzZ62fv*U(C?`i(qGc*0#*a7sQ6tl&U_Rdo+=GN^PRp} zfZO%Br#B|-D7UXbjCg&&UG4+4^EAC>{WIN+knvHsFYH&}3x3bc1Y_uKIaApsOy>L^ zeZU6k_2!z{-*oSJ&Ygw;IO8Q@^FNlzllB+-*{g415m%%Z@41Vab_m})-|c1j(lP#F z`n{5d_ri7Ryu>WzA}AzMlSyy1rnD3M70?5@ zUg`7)J+8>QIzc02LseBySp3P{6`R@x{yw`ulm%gWRj7M?dCQ;PK>9q!R&tD(26jCHyon$PZ6RGJ^6?wJ%<^TDgTmbPHNlO;Ms<&ckZ?)a{-5kAF z0g1eboW0-ZW{1O7)Cz{EcB5f0*fqx$FAdA#%6xIex$^knuI6#)d;rCH2xw91v!Gw1 z`57yqU9|-?8H2WDm#%WQ#E+L72rk`0`_Z?Z>V3O80W>3cf(80>X>h1lJmi6O$h4tc zcL8W77o3l~If1`<2<(TH6v9s6yNH14Ij{C*AoTFiZDwu`e3;(a!$VId*Mtc;yv)ug z_wS%LVM~a7@Dt-nF9ltHBANTXzh9MJE|?ye0I85wFd!f_%WxKk_b+45ds`1&=<$?lb za#5vh<|u%YLdu)aL;U#7?GdWOmfk6(tNp!awi}PU3W6Q)?WG%+@2?1@o9aTVVx#=5 z;Jft18*EjY>r8iP=T6gFYui?$$JdXqCM^*vvsa9r$d}#lpRWz9KzpM7Tbe$L*{dFpRFhTC1vGbH`kI0K#v zWvZHD+ke$<>_5^O-UmL_t!t>kLUPUbD|P|OXQr9}R9~95GO2*KvcPYSW9eH_(AxRp zmWIa0u|I#JO$EXMqKh(;fnK<#6Zuc!j`Nbuy-_HFkWJeU_V$FCbcV{x%CEhFk06zb zi>9u31JK#AF$#hpS%QzUfSFj-{#n`~5G?~Ln^nK1vBdRUG=sMIYlz&7<9AkGY;K|P2IKpr@e0e?q5wG zX1|L^PBVsiHuvQk=>DkU00sqwwoef-kwE|oP5HEE9-eKu)RU9m8Urji9UXFeL`7v~ zdzz& zhsQ4ZQH+w_Sc(%*PL9n?%=Cv={<*TL%)8rsfBOMUQ!TSozVm-mOixb-AyQNC0ZIfg zod7xlqXRyHbFXG&fToj^ldN>49qXwMAVetK&8+|GtbofA?Vf4kb`Qt%{FpIQno;N!x3E~ zLT}P)KC|klL*e&ICyJR*YxkR{qugQY!t6En7D;TsQs@7@yI>2gLRsJJ8NTKcRc(0e zm6w{L^9-m4e%Hk79Rk5*AkgjM^hX2l^7;nA-CVzESrNe}b|>mAWbf(0p>-$KWtQsA znB1Sdc%?I8{Riyq{Ym8OQRI^kJs)?qJ^VzfZ<-@iuuO}q6s8>U$a`S@zxcve z5gWW(w`#2~&u}@iUW10W$Kq;KCSmuery;dF;a>!nx`}dxL&h{1-@p1eptp%?eCEu) zXUn;Z<=-*iww}2W{XrV4(&7NW0^SzwzCEz?ZKs}CtAJGO;P!8163-=6D(ji=g$|I7 z0Dv_BLV%=M8wp5wYg@SatQ3$1w(R}=naP@+xy;(4DB4Xy0lsMd{8+&PWO<8a8_D8x zX-@&Vw$`FX+J*TIqJND0V^73v4%E&yG25r~O?<&d4f`Py%4Q*|9 z1y~vpIjC0pBfUN=IS3JaIh~qW)0C5g|Mcn8g}H>1uT)wEtdv61Mohmg@mHMPg}KyW z*6frjbh~<5S~+A0a9+TY^#j?{>!?kL1x04fC=>)g8F=q!{*DTM5?Nud4ZoE7*{z5O zgYs_FxaO~~XO!@%a-?W75ZW$Ul=V4UxG@524hZzf`2A;?;uGoHv~5#EPQI5gpBte8 z1g#p9r4v?N`hC6Ni@O)LI|(+}fa?jHt2=3Jx%{k`d@~BYi8HuSCu>P`KaYKT5G`k6 zl0g=R4=jmo8Cp{xiyzb2Sr0uudSJA5kSyR`9RUKzmdOeX%@!r4O^p&*y3Jj;YI}B{ z+FpXa7x9ZDKBS3OQ?UN*d|6&m@w$&l!&Dp(cODV`0f}rev>-?sly3V(sxjZx&n9*E zP6d&Zbaw2XvBZb4QqtS!fl3saL`{SNcz>~vKG_Yoc1SQWZEs@<>6hizaqkD7BtcSC ztK!p{{~fOt8LqZWy%F5YjKS~Hwsp)mo;`Vz{VWhKQm}$wI`olwS-J~&!{&s0!O$%4OG!llWh85=_5LRwY|ArW@t1f?rx7`OUkck~$ z@1SohKU>1pQDqO!lArY(8E^uo&7OqR7nREczRYhguV)Wo5_r}=`zBN3=n@|ggvB@` zjtm3HI?f)KCayaur-?f@C>8_e05m~R`#!Hlw&M6~?I+QX{B@EVHD=f@m<0+FY))|!H9nFCLF zdd<>oCgbg%%|2A&o@8(7&Cj?+1;mynuioxGYP(fcCf#;;=mPN2>f2wU?-IuSg7#hF zBNP!d3~2|rNl*HygMV^J>mm#kHW;H$_XXf`0y(k*MYoUTbXw zzh7E`Ir&i-S#`yP-#``Ahvz_6Jw#u}f;lr@ItrU|zOHby7CSZeZB8@Yt{Iq|!Qyij zz?ybOgDVug9-|h^2p}x47~l8TDE?QCNPv}COWA5mu)m#w8#AkF@XYw}B-9Djl1z14 zZa?OYup0TMGS~YuCi+x(0Aitef1h^gtB_TlQeb3wTh-)f z``P%DzyO#!p16ISwMyMbu!9Stp}<3+`tg_amZG9iU-y5?UE{f`f})}+2e~D?5Q}-z zBz|2Zqi8^(yU$4Gx`UKnK)N%Viaz3C+60`CPK|$Td_HQj6TMW9=#6EeohHzKyCM6C zkMjhdBPILM&nvhcJ@G5nyRkV%pZF;)Ig<92@A%?=0|~M=Ha3$jvc_CcjgOjal9%IF zJ()(dD(R&f{v9t0B_)&c^Zglr`;A$#ubt(pB=CRmei3e5Nc77Z3BU9$J#pde$d3d? zpKjGc{mveFvibebOAGHzmZy;^8q9TVY!a4m=2!@GEN1~ME?23HUp1%K{e9sm75H#F zzhje|e~#sLb_O0kXv~_x#jj9Is}jZ>&7+oHo>DQnzZO&q!XZ?9mRaJFr z_c?p^#yOxQ6>#*uX44hof_{O7ceP0RuQbAuH7_lgnKM&I= z>2&;14;_9XUAI+j)X;wWO(znVyr(LVu7pQ2+}??tU(C*_B7#{RN|^n6mA&+knY{Wqc}^{drU z-3tOos?DHL{BO()g7H&!7+Sbt;)V(M%upjMhe6!bC zPl2f0RE`n1Tlr7rD7x}89`s*a>RROc^KtP`!1$?CVid5)iKsq6#p&4UtSQiAnccUH zZLqkku9Q94#vk~i1lS3phnVe(?dOE%AY4!aazQGBOkwv7sriyT`B^)U_fB!`n@PXA zg2!3tVmo&*>}t_p?%2JBTs_xdE!V56sGuYZlP~U#SpcN;T?aebSEH5(q7-G(?U2A-D$=MCbIyd#M_xZ&N29@1RW*m(ZUM;E~u`yktP?+g2F*v&c2xfy~UMe2l93&G0r zUOg`A|NIp&Cs|AX|CjrGpE8vX$Z3+FE;i!z{h7dtI6XV-G&Up!g{!@kG3Z4hMc+)f zKTzJ32u-O@xkeY+^TdH!@5O5_#XZqKb?>RWIbuOMBNu#+g_DZ(B3>pJpY5+rn`rq~ z8)|EW_gm9_<%{(|E={-QmcJ!a%ytqdj@vq%ig9qbT9K<|jCqw^mPz!LkFx{paFsaB zl9)3F1d~%gm{{LBMLF-(R#*35e4Pffk`w&^5*PqjtASzw>!xTVtY>4j^0(c#xPX+i z!=tl6*AZ3pGaZI9`X~njHg}l0(0+Fifpa>`fjI(D}HSg{&9lSlGf z`th7g-~~$j7a3^Q&$Uv&=k&u^Qq=g(&tcT-fWIGZkhTG!C#2V8!3#HOH&JQSqV@-NYxq=!h%t=Gpe%Nuzm2rjw1?n2U7d)P{cQem6Kk!{; z2gy~{*5*v>MPvLs4|-k*EEYWaI3nl;)2Oj6eaYvjED=deTz@Pd&sFllK?x(nvOS0D zoW11K;QTFkdVO2C$oOM9+B5T>OxKpo$t9=2?`!75bvllDl>E`3fN;;zjFs`)-}ZD& zkXNBL2bVWoz_OFC_|4k8iv1|T-c+R=YEij-Dv_hIZhz+4ZdvQRrJO^xDK!@&wJ-8+ zl=tj`_?B`zudxE>6;@$X7PpN>W-rn3@BKKh{eGB_ISW@xcG+tD;{a%ZUy_rpfSinH zs?l25+<&|M?Pp9n5>r8^AAtfx4B=;Kfhk|z`kj;aWWkh`$FSOGiF=3 zt7IgfKb}`K8`W`GM5kGGHlMt;x7QQ+fKveYzWSL3Z?M(Qy)Qe<@4FAYg`O&o@v%ty z6HCA(K8}SDMs7~kc&=J#MU7j!nSlpfD}@yWDH=-S2MMXYz{kgU&2T%>?F3$Z36RWX z!wmHJIJ=GatX6*O`1XOfp=iwx>4Fx?*e*1?UIa1pgp+Ubgm*^&L^_rvcY<>^5=DCq zfK%YvL(-P(p4hx+>3PuE_^(WC0!$)sIZBx;Lfct zs$uwKDnXrF)Q21B+t7D&+=^|4WQfBa35NyUb=!?E`;FNO!3*GMNwotr}K-@_-8p#!-O8pg(N-zSS}6bo>tvP5|hE_71@wC_Ya-yclR z`!aSi+zd7&`?da3dDEJFm4}nf*8JJM)9DADTY-aLOMvT7pPDn8Dk>7)68ll?&RdT>Zp9G zggp1uiDSAh;=&)vM)$Xz1~vuQQZwsW0=yTZjYh5dI__R4bnJ~RJGE^&EnRt{dP~kc z`%5%S%GNXmBZ7jKkAoAo&C@LzV!)OUrd?#vai~C`XY21s~6kN&Q7-1P~WvG@A z{&*i!hk2Rw_iu-My6Te=vkx9pAkzr9d#WU9?`i2uP+lCWKk(N^O6n(Kc{yXXOLA1* zo1a7a(v6ZhsX{_JzwB(jHsT)#yP#*LCZc+hIM!1oWUlMVb8j0-HTT7_XOYE8G{%&G z8u&;>oBwB1s~MjLW+Nlo|J)nEEh?ju076oUTnixTq34)I=q<*@8vFUOrRk8_7Iz5` zyZJ!xBlL1!8t(&zUwh;~XnN0@m01)w@S#h-vC=$4V7%$B{jP}59V-SDuy{;yII<7> zwoBc@k9Wn2y(LRbrhA$D2>uY{y4p@LJg}PkV7t7Wdx4C64gsskN3 zpc>bUKI-FPFfzH(Y!-l|pof&EyX^d4yOYzuf*$5!$;EjZf0E`yK<3+uVr@Tpcz$^w zvodPsuMWlV)IH=pEcqJ!Z%L1083)^%g^E}*K1^r|npee%$?Sm~TNFFy(7Wda_kC)8Icn9U2be=((1$H=5V@8~YiZ9Z<5FCv-cw|) zDWv9n1An4+DJg_x8B}%vP{;612q77SoY>4^-7uK_b^?1p+4!xuj7wDNQX%_bua$y) za%uqx(f7Xdg6APeTAzv0`5ZJO>S|BEvGh(dvPwF*onaGgPgYCz5NrwAXi)Ufx#CQe z8$xLVm%XDXE_3qDB$Qoo==PylD=;wLR{~e2)fy0MixK_?1(?9^kRxZQVSrnUxYTZzqY<#bwXWn@yFZh7|!e_JU3FnUzS}{n9C!2w@Pt%$!Tulv~ zC?@OPtO3Dp{3KEtDO>%l>gn~093<_FE)ExOivXa<%*YNtKGt`IlRYw8Q}D37XY6%H z@ML8JWV=3OJB}TTv;5*>~g%FGxm%b|u=L+D>;39|^s7q*X~kY#9VTPMmF+ zjxb<$4a^4>x`G|`>d4cOZgL40Gwb1nBC1wa`EhSDCCwIz_2qRfyBvp7#**IkMmZ|s z6uM%KXaDQi^9g%90w#8tL5~rlG|>UoheyNnFFT0!7m8NX()&qZhne+` zV@yHmj~{Ky$~Wd>&jgl0s*jFoq#&EGTzkJT86EBvzu|WyJGgUx#&WEjRGhyP7@sS6 zmMGQ+n`H^A{0ylBpok30>k)58(n=>xa9+87Ev317)1#m^xKr(nntcXT@Y5xWdOy*qD|M zxF6m||7W7MA$miOKTd(x?rX>}pW^^jC*MC$_&+BsZUuWC{GXc{O$(1e zx!|9iQ2Q4N2_UEnUP_ZnPag7?^A4=>{{3L@qmAX}v&BwyN$cWUyV4onF0fCtvB4-l z0I~w2qk@T9X)uO_kZG;tuMrXl<%@%Qx=M>?uWd_0|8uUMF2seG@vmj;SjgN>tbR%c z{=)2=NZ>VwSqdKs?=3z3!fsD?;4+H;SE>x^ua(Ec4fUQGQu=X$NVr8&D2-^cwd5_J z04@L+9}mljh3QCx`Xw4zsTViwj*@tqD!r-XuC7c!El-D#1a29We>k$#w*MUjz|*ew zFgOytBW=%Y7EbYU^+R1^XX>u|UkqP}G@Y%gzZ)eU#;;<71L9F|v0N1oJR9Jdt$A|} zW&qnXaYYmU=V;$pjzk$Uo0j52Tgv z9|!@27Bz1~vJA5+7quLh;c1xM?DZa*_M&zK;gLhn#^X1Dzi{^0f{0{ew0HPu!_4^o zLsoFsHN1r;wHS-}_UCfHB^W9#N$Gm?X8&rF2Mf4U|6C=+rJH&MHPU0K#Hk0U7SzH~ z?X67IcQZKa(f}4OpXEp0`3W1rNW6Y~&$4TDNaIJnq|6=x)t4_2u4m9ViPpcQP(Z3r zv~_1|LZ0OO$Rz(w&wMPP1oSf;AGh=o+9r2DSnkmFHwzIfIVqkmI%m64cF5p&tZ6`N z6NQJa6A)XVTUYgB{`|<9X-PSDS7s@9F8s0;+e@y`9ALC%;`Byv`VaqaeL``|A#2b^ z_x`Uy{G@j_-6Fj3li_EubLD?$?J*o#nBhp|4=Af-iyp>S^v`?I&;l_TF~~3cwZSMO z=B6j)zJF5i5r-hf5%B30Nj6S~ zI-CBs{nLwpa4E2Y5%Tln5wP^f6Rvb=@JwPc6nU!g@p8}S=?0e9y*d0%W&waB{wWJa z{rYYzsI9uPB%ZeG2brGPCq>DPeA)06eBp*U*KU$4FNuAm6X{K0(9boIuH1Yj^RVIK zUx+}Fc`wJ3?aR;kY7_RhgnV$!Tkpa!NmYNospg*{UFa2BM{6yA*yDVS`ea`A3k0^C zVNM&RGuj>*6lKzsrFL?)692owJ}2u4L?wd;k9#AuBx_?qAZDq5dju3nLd%MZK4G$6 zw(xx6tW$P$ze^lKjix0Iak|Ex%fg@syG;RjphtUtvVGnNlX^6K5Z!)twr^5)b)kh* zm5@%Ken}q8F*Ev&{%9j-aiO77%Y%7a8zN4T@&5?&|9^oLQ%Co~W_F;(Us9jJ5W$5GXTTo=7p~5MTv1<04Fy3w^}n_fZ?2w!+8jP85=`72kq;Jr zW{KkZx&Jemo?@YHS>$h@<~ggb#JUKJE*YgP8}o=Z3>?JD_V0(3qK{1-r}#*D`xLim z9*m);9U6*Y@LRQis)?@bcZu9CKrmsBO*pf%$flpA~?%FgZEe`(*)B@Y|r8 zBrGOfkS1W;(GvvHS4!-e%+-le7C{_WU>|O?T~;wl;ek@z3z?;S7ggwP>H|!mAUfl> z%v!Y;7;{h?mY3K31z)O-wAdl{$n+G)CoyfkDr*Y-wU04?qnlkNXkx4DtI*-T+wtbu z!6m?_?~@Z{*4EMjskK0$WTWSP&D9xl8{4mRc6%g6@FVc1@paxokEBN_oOmn}a9^)8 zwSNB|Tx`_%%;BmN3i||9E0h5>S2Nj)&O5EA(+1DA9UsEAI^X8qt!#eNNWDBkDMm)y zsT0sbEOd8~eC(#W_9tc|kWYS8Q3`jv4=>4BjJd6?ERXrmdzas}t3<>lj4T*`j@Cm4 z#_eCQAG*6BuwGZ)!G{ROu&Cvqj!l6otDFkQD3fLuWcu~cI0`SVe+5pIa2zX9^$-N4 z_nC9P`Ub>NDFQOe);Qp1Mz#1SA>|8Kp|@@`Ee-Owl7do1o!L0lGe|+AZkyy}EYd}j z$}?&ilJsF@Oq7QNX(s%5pf6w4Nz#d1_iMtY3If9vqysyax1QZm*dGT!GiC4K%iIOL<4n5vVSwf^vMe90c;fiC$avl4X7;;BUp6zf2eQcr=SyO?sy!(e@Cf zmqUc=ISKeQ@3`f{wQF*9&a>X~27+~-1?TC+=mb03O~nLX#qt+@VbP9J9*lV~aHW^O zJ@$?jUWIR1;Z@G&_h!D9{ckHUt(q=+7zV1zAy`3#-o4n)vN9!LKa8`>sw&~ZmDLlU zM6_FZ4_cr{DRuXONN!It{b6pTD_$f`C&NxTuGQ)|jH}No_pG{8;b=Vk;8qHcxu|7fOITN#;w1&8y(7w+rJ?t+J4*&pYV& z<)mK|BXP?~PC~?G+22(tWM!By2nfVv*2AdIiYb>p8flaXfh4`Jx6^e-Lr4bP^;7Q$ z2wAM!nkvi=5xqADPrAatSwygajL;B)T)wxsu1qvew4V|tgF1JfEN$glUfmRfy{hPf zB1(>Y_v+DK8Z5&9wLY86NAkD}u2_BaA|prC9BCzvYTI=d7FcB)X1YUuoTaU;(5e$m z46cdEOGB+^z=hrWi-euO7RF-dTd|&AM<=j%^C*343X5ezK^t1GF7QZ4Ch^kNME=;s z1b*pER0atx=^cr}dB8ShG>NJH#V}X(MBCfw{(&?0 zz)W#-GInZL0bnH*sj0UXuSiIkD*C4Gf>VC@dVs(FG+1Hz`OT zpMW0rS%_ku7-A!qK~tcB{4r9v|CPYYCycC~em*l};T{O7UD6iDe72xrCNoXanWJ1< zIi{r|(b&##YU!AXtx31HN=&+DX3L69CeZ46p@t>Js9-$o-^$Us!rH-v{1Y^Wa%%ql zjSw$e2?Nz+)W!}<*Wu?PpKYzADR6Lizu4KMnwnp)COGnn*e=dQgK}T0c=*?bDsoCf zxZb~@m}x`c3rA((6Oar2rFW$J(#e#mIV37$ye%fvxS15V4^U%(ZQ?a2C52}$rwM&3t6B#!;DDP6RE#olYc%T&efSjUgtbTu*e-&_)QL#j zaU>Rf+r`Tu-W%alKqjdk*Et_Kgg>e3zT_RmxcJtMc?nxER|+ddAH2x{9G@qznY=Ps z!)j^1LVk##X&)n+iu)vGnzbProPEpoitAv2<0q-7u0f=epU~3zBRiRv0O8{0ok0Ds z_}V7DY4~>ZZ)~iIgyZqovUTphIlQojh&B}&4zba75u0^=%Vb?%opm>*^~Dj4s`W8^ z+7c#e7SXB3KIFY4r@k<`l3XNAB;~9Xh3xAVz;yB7`|npwD#lrlyVM@TJ;qrl$>Ne# zlLKf*|H^l~$y7UI*12Hs1XjPABh8O{62L_g^kbQ$mKzANH$sHvoUG=gDzleAU*;G~ zzxSA#BMN2oGf|q2DbtLggi^|7Exh~~ z))mX|6~RHj0-+l{QVqp|dya4k_u&YPWGfw}Qug?jfMH+io>WW+s()0O7pn0XzI_gh zC)0?gz~KKClQEU)Bi_@G-bj86m~2-pskvO?0}IF(xXGgcpbvaO6YK+ngU)*kVuNHu z`)4KqBPvVS&J0@?4%ZH6;nXaYujq>X%!64~SUdkP>57TsjPX!O#5%uz2>+%~K*rV$ z4Nu2k0HiuV`ax(-Ip%CFj`r^6--VsL67XPTMa^DdJ6_1V6I7MNXgMM+J;@F}KR}v~ zV{ePPRNY$L*S~Y?yk0Tc2BTF9qS*p@4*-^M{4J=>G8I>ll#~?3NMryQ<7p1g43Gb# zaID=s)25x!NI@wrPuZGv9}X~%4;hPe%eY`=^=2#}wcUlna83hV`|GqOUFm~KcT%z7 zIkLE@d#KG+lbu9H2{Ljz^0MOr%;tQ&mtwv?Zjx7KS5&5&XuEwr!UM5Q6HW1x!Adc4 zEY|+l%uIgc(nHM;Gl99{aOxV`ChPbEDIv`amQr;kG9c8=8{Q;MFNTm=k%So|KQYRf zvW`N3Em|OESYFAIvBrsx#?Ej)fW&puHdwEpJCCUXH-+)LjSU>FdLy!30I9DmTXBdO zkcHdF*G;O?p!-p}?TKcZKR?0P2Q;IQ7s5n2WKLds^(#x+TJ)26`ELrv40NJ^*Lt`U z!g=OsUd{biPHQWwYV(I`Sm&ZQx*%qhqIyY@#0b35=vjIU@TF~st>R>e^_gT{v+vmp z)r@Ia>??k#hJv4DrJP8MF>_`(*5szgCfc zbnc&);05H27z!Q|zui1C^U@Oo)B=6JdaRxlNnBl(N>3GqEcr9i^$S~QWxHeo=m=!H1jMWa2UEP${Hn$ZeCBiMF%QDq zgRWU`2mP{e88H`B_Q7X||G$U-yDxuvbjK{oZ)>oqx_Zjx28Z)&x)?w{x{2||0p}C` z-Y^M_PO0(m-28leTAFPG|9-8{iOnE}mxrVL;$nj9G1CX2PDkq~VjX*fD}~iG)1pVO zcQ2DxQ59H^jp5-+#>UF#J83Adqip{xaHh*5-YwRR0GX=*`cjA9)5kR^6K=T6>V)%i z`IPDDw~`lsd_Zj?ETkY%#{5!b-E>25De|J=3Y#c09N4>BK6#U_cKT|?!O^jIAdxpd zG4XxDP3luyyYA9bShULL{QE*e6K<&G_4in84%Dt2g6Iu6U;-@4w?JR0Nt?)U8TZ_! zK)59gaz1i*cv#-bH?0_Om7VOEh&h7OgbNuLSImbm{;=$||FyNUnRb6B3_7|iAi*Ys z`OJcf13l?^89<=AA$Xe1pbqSgm!pJVFQ$h*DP8lO$h$N7bHCctZM6U?NAXHx4UO(x z#Vn<(8EAMOs`GsmESS$*?g89S*k*T;YA_A`7B zx+YrN3Yq+L0|hX-j@*O(t+1I@Hz2obc|JC<;>fir#(ET&~2Z|@9mRiV4J=IRJtwOzt= z1b0s-sY5|=x;xUegIR~T-BVcg&Dy!9PBe2~34{C8rkT%K zbdSgb1V~_@O=mog)QKDTE)la10{MruvYJj9p?j!sE#b}v_Z`w+G#eKs(3fMh$cpx- z*RSJp!;xcHn6DwAo66j+y=YL9&pnvs_PYurwxQ6DGHkj`zGqXe_3uPUPzn$vsy@8` z@#Pn~{th4RLPr2M3Il#77x_m_=BedsROm`tY`{vFiWw$45@~&Q_NTYSwthb{jF}Sq z^0hk1tNo52isk8F`u*$NTeOYV@ll>AB1VkO;HgZ4Sj&X z4=q6Kq=98;Ki zi3T+>#A(c|KijT=|Am_tCSM)NCwv`A!|Md3)H6i>1N(djELOSI8KkZ7$44=^pJfK8J`GV|)T{N^bzFh1U{m@3-_}f!zn*(`<%uMt6&_BdW^XZM=^g zPm*4JEF-zWUop3w{Ea#FxB<`2ucv$Hlo&<;xhjtgl4gIqd3y4$^j;d|wp${BI^{gKz{@2vCvaV{8|A*`E=5O^4gD*mrQXbdB$4xIK^G2o0qxE(#llTAF zNnBH%N%gw$7HVq8eXNObu(0%U%F3tp7Spq~+X}BkE&eVx+`mD|Nwyw2&tq6_e&cPB z7@CP+w~Sa8-P9)A^|Cy*q4xc|Q~NdM2FK!Ic5{7Ivs&u2<~`VY^He>c(*yUDW2BZb z;AXkljhSAS)XYsi7zry#xteKx+0mdTyWlz_3ThikKV1HZyGIw6E|Dha;w8N?gyEi1 zf6Uu41IM#eCV-uFJ*&A|{;ga6^A@OVBa}Ed2Bno#0zr7s_WTWBYJiAT2q^>Gk1BB- zx>kZgdS99Aj9#tl`?Hu1oCI^zcnw{#`?}1(OIn5DK3}GY; z@gSx*hH3Ed=qP^YDNAc*C6$oXh{eBTA;y5I2cwk+8>rg?w|YP-32nOHnFR%&Cj>iv z418i}uS`&yDvG?rU;p|V0ExOyiaF81Ya_=LB*YU@cKuS+0jWksl{sjU^E#+?HmD7Q z$nT1oqDL%qjgF=2{mFB?727y_V)9~p3yq%sGwLzpYm`g@roIo6*-Rsh#l7LYs^CdZ zyB!b3mM4#N%KTqcz#poWZD8 zH&RFKr#v%2c_q3~33r|QZr}uXL_=R!<(q$q-0#DKSVy6Nn2H#u0Db!+myI8f;MX!5 z^?76RWZ)4aFMpg^uwT=Tz~b#~5XWF-j!vQmK!NJ;M|Ffp@d zHdiXfOe_-ICC;iYzWqtCs_L6E7d7QG;{LWax{t75Bs~^kkiM8$S&!gPg2(5+Ac5m^ z{x~c}mtqh%MAXtttb~iDz#!5Mp)a3e*|mR>c0Kae4>TZip_I7=1V7-j@ z>|0$gVD>A>LHf4rKJ-RZR%44!s~ieEBH7TOz;?hk>D8Hn8#b}!8`^#G5k^k^gy0_Z z0Y;I0w3|MH-;V6pGJ;*jdfyuS?&a6ZolQGHK|cJ(f~Yy=5M_rnG|a^(Z2$pcRZI+V z=o`@OYZ?AFZCC&HmV>KE_LM6hWYl&l}Dk_niBvH^Ui>PXG;{E zp3Sb(p>-cGFmNh^G>*v(+d|N`pG<{XzodLG2Vfk_07U)sjULixlmpC5Vsvl$>XO2D znc1(ss&AQ>OpmA7_-g@Jb8hbdUtT%K&C6?YI7T zPW4aPX4@F=7k|7To)x>->-D?6-xuytQ?&}L`WRs4d6Brd58{^33$Le_qjX>2hqtu} zykNXBsvVfK6T`yf# zBk}-NN@TyQ*4Iz}L89b>UM3+ueK3(MpbWYv48^q@1D(R+FOQ>0Y9;WmWuW6ye1VwT zLC^2r_6+)6Un$m9n~$@s(fv(r@GSOxfMeEW9_ZA-Viuc6&@$jv2IEJWM&@@r=2S0r z?Y^K&g@_vV`t<@&)D?K5Bx(4hjtD6~FM2wBm!q}enr5-DfQxM2fJ`M}X}CYZGkkf4 z(}w6tQ@clv&SK;!zTU$4eOT=Av7JnU#n?NU22SNIC19a)Wa0bS=*3smatf3!1JJ+? zwGN*jrJ7tuq=?D<$G014eAPc@08ZV6SLh(_H9JBc(^?6)l)cFW-5V9TPfgtK>Wg?C zs{7nHms@3dDwU30I#522w&8q_@3l73iIhqUvCcR-mn^A&D9#%(UW}^1z3q5*^&0Tr zhETy|jbFVT^7h)^vB-evc9aM+nXGvuZW?eN6Bt0W5r@Y8H<{%BcEgYRxPY}7yJp`F Shf)ymr=s{up+wF!=zjn)n4g3I diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi index 2dffe94cea4cad37c443c856115ba7a656edd08b..a0afc5be4f6e37c5a8a998064fbc2869553dc6a9 100644 GIT binary patch delta 246354 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

xf8xP> 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�#&+w8rdD*z{+^=x5|JmmGlO5ALgRMA+2J&A3 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

Sp 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 HvJLwm^m%oI diff --git a/icons/turf/walls/dark_pod.dmi b/icons/turf/walls/dark_pod.dmi new file mode 100644 index 0000000000000000000000000000000000000000..cdfe8fc7fe46c8c1470e9e497e27850d507f4f6c GIT binary patch literal 14106 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 literal 49104 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 diff --git a/icons/turf/walls/red_pod.dmi b/icons/turf/walls/red_pod.dmi new file mode 100644 index 0000000000000000000000000000000000000000..0897fb07b81936e8385a9675dbd3c66613e5fed8 GIT binary patch literal 13673 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 dgw;#|NAG^MYBuiicQ=i{|~B7Os@a{ literal 0 HcmV?d00001 diff --git a/icons/turf/walls/red_pod.png b/icons/turf/walls/red_pod.png new file mode 100644 index 0000000000000000000000000000000000000000..f9f3fa7a31363f353d81962bb55ec72d81dffeef GIT binary patch literal 1481 zcmV;)1vdJLP)|dEGZAMF(Q4K zY>RKsAC}{hN&KU|ijpWV8SS*y<5QQN^*v_ipeEOsmX7yp9^xF&vvfH1yX7;)M-2q+GX(3g<>Qb7 z)B~MQsm8AlfIY&t74%axa0&XVXfzXNP3Og)8jyrwnj}S4^tV}=Qk-;Nb#vcFh8+p`a)2=Ts&!pCaucj@?9E$7lPlunLn@Jz>A=K{Cdvc>!IE) zpP`N#7LX8969-Zuu{o{~f6w`4+cqTu^vEebJ#uw8E5j(!3|t0&(KnijjN$|2k6(bEfkE5*HhCH|%mX!Go&X|-77F!1c!=`3hidMj ziO)Qw4YFLCVo6<=m9fnRp>rCD@4zG!U+~h^Cxl9U>zm}60t}5AK&B3b6k1D~uz!lr z-miNofhfRH{A}*C3MjU~SMhP2iw%8n0}tBHx5+aFC=B-jhhbZxK)|s^{|NpsHedA+ zUzm{a*|AXsq=|72LTTW}webz|5~jx|3Dc%RPXdldL7U%#ssYDU53!^1$~^mFAEiN= zSXr1E*m1PB$VUQ(Nho000)fwVA>fI$!b zt@0A4aV}xngza3wF_!d_>oASY`MfHs&bf#4ALKNcKs7}HhG{rOfC+Ki$GaNGmf61_e~Y!UCcU2W^#SG4n^n&@UB3%hcyd zJdcw87F4A=t&FWH^%?awN1PwryA>uD7Is10W_g*63Y25XsF9q?i}=%^YFKk^$+xiv zhp*M`z6%SlIB27M?2BPiH4G0as)jY!4Lrj1aLdHp!Z#eWLEfk3n zTfQ0e_sdJzruT^(wq+)ObvnT|cGW`*Smh|RV&Z|qGNkoQ8mz*5Sl&!PKetqui98RUuk3I!ZTRWRBvNJ zvmT0@Q?+O_(QH{fu{IO3+EiH)byk~dm%O8Ms$ZC4M&}f!Iv<+#P*x1Zac Date: Sat, 18 Jul 2026 01:04:08 +0000 Subject: [PATCH 023/445] Automatic changelog for PR #96838 [ci skip] --- html/changelogs/AutoChangeLog-pr-96838.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96838.yml diff --git a/html/changelogs/AutoChangeLog-pr-96838.yml b/html/changelogs/AutoChangeLog-pr-96838.yml new file mode 100644 index 000000000000..d4c92358b85c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96838.yml @@ -0,0 +1,6 @@ +author: "YesterdaysPromise" +delete-after: True +changes: + - refactor: "Podfloors (floors normaly seen in Survival Pods of miners) are now considered a subtype of plastitanium tiles, and thus craftable." + - rscadd: "To match with plastitanium, pofloors recieved 2 red variants." + - rscadd: "Plastitanium can now make a new wall-filling item which you can reskin in hand to make regular plastitanium walls, pod walls, as well as added unmarked pod walls ('dark shuttle') and red unmarked pod walls ('dark red shuttle')." \ No newline at end of file From ff92e5399ec8f880052d83a0660022c829591359 Mon Sep 17 00:00:00 2001 From: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:47:33 -0400 Subject: [PATCH 024/445] [NO GBP] fixes RCDing on space & openspace (#97010) ## About The Pull Request Another `interact_with_atom()` blunder where this time the issue was way more obvious and I don't know how I missed it. ## Why It's Good For Me It fixes my bug closes #97012 --- code/game/turfs/open/openspace.dm | 15 ++++++++++----- code/game/turfs/open/space/space.dm | 6 ++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm index da9141e400cf..404543ea2794 100644 --- a/code/game/turfs/open/openspace.dm +++ b/code/game/turfs/open/openspace.dm @@ -124,14 +124,19 @@ if(istype(tool, /obj/item/stack/rods)) build_with_rods(tool, user) - else if(ismetaltile(tool)) + return ITEM_INTERACT_SUCCESS + + if(ismetaltile(tool)) build_with_floor_tiles(tool, user) - else if(istype(tool, /obj/item/stack/thermoplastic)) + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/stack/thermoplastic)) build_with_transport_tiles(tool, user) - else if(istype(tool, /obj/item/stack/sheet/mineral/titanium)) - build_with_titanium(tool, user) + return ITEM_INTERACT_SUCCESS - return ITEM_INTERACT_SUCCESS + if(istype(tool, /obj/item/stack/sheet/mineral/titanium)) + build_with_titanium(tool, user) + return ITEM_INTERACT_SUCCESS /turf/open/openspace/build_with_floor_tiles(obj/item/stack/tile/iron/used_tiles) if(!CanCoverUp()) diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index 5045e6136ffe..b6cc5368aebd 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -137,9 +137,11 @@ GLOBAL_LIST_EMPTY(starlight) if(istype(tool, /obj/item/stack/rods)) build_with_rods(tool, user) - else if(ismetaltile(tool)) + return ITEM_INTERACT_SUCCESS + + if(ismetaltile(tool)) build_with_floor_tiles(tool, user) - return ITEM_INTERACT_SUCCESS + return ITEM_INTERACT_SUCCESS /turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) return From c1a66998c8770bb0d359842157e660439e0dd1da Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 01:47:52 +0000 Subject: [PATCH 025/445] Automatic changelog for PR #97010 [ci skip] --- html/changelogs/AutoChangeLog-pr-97010.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97010.yml diff --git a/html/changelogs/AutoChangeLog-pr-97010.yml b/html/changelogs/AutoChangeLog-pr-97010.yml new file mode 100644 index 000000000000..6e77b617ae38 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97010.yml @@ -0,0 +1,4 @@ +author: "lelandkemble" +delete-after: True +changes: + - bugfix: "RCDing space works again" \ No newline at end of file From f70eda476a822c9c3fe9c1d4d4528f4eae8f9d92 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 04:10:08 +0000 Subject: [PATCH 026/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-96838.yml | 6 ------ html/changelogs/AutoChangeLog-pr-97010.yml | 4 ---- html/changelogs/archive/2026-07.yml | 8 ++++++++ 3 files changed, 8 insertions(+), 10 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-96838.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97010.yml diff --git a/html/changelogs/AutoChangeLog-pr-96838.yml b/html/changelogs/AutoChangeLog-pr-96838.yml deleted file mode 100644 index d4c92358b85c..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96838.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "YesterdaysPromise" -delete-after: True -changes: - - refactor: "Podfloors (floors normaly seen in Survival Pods of miners) are now considered a subtype of plastitanium tiles, and thus craftable." - - rscadd: "To match with plastitanium, pofloors recieved 2 red variants." - - rscadd: "Plastitanium can now make a new wall-filling item which you can reskin in hand to make regular plastitanium walls, pod walls, as well as added unmarked pod walls ('dark shuttle') and red unmarked pod walls ('dark red shuttle')." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97010.yml b/html/changelogs/AutoChangeLog-pr-97010.yml deleted file mode 100644 index 6e77b617ae38..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97010.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "lelandkemble" -delete-after: True -changes: - - bugfix: "RCDing space works again" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index 7f4d06607baa..8db99dbe5e8b 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -561,8 +561,16 @@ - rscdel: Material fishing rods no longer innately catch material fish, you need a dimensional anomaly core in an anomalous lure hook to catch material fish now. + YesterdaysPromise: + - refactor: Podfloors (floors normaly seen in Survival Pods of miners) are now considered + a subtype of plastitanium tiles, and thus craftable. + - rscadd: To match with plastitanium, pofloors recieved 2 red variants. + - rscadd: Plastitanium can now make a new wall-filling item which you can reskin + in hand to make regular plastitanium walls, pod walls, as well as added unmarked + pod walls ('dark shuttle') and red unmarked pod walls ('dark red shuttle'). lelandkemble: - bugfix: Wumborian Fugu floats + - bugfix: RCDing space works again nPocToDark: - rscadd: Curse of Ra is back. Now you can buy some pharaon costumes in DeusVend (premium) From 8e86f845f7843eb16b463487f5a7dca347bb0d12 Mon Sep 17 00:00:00 2001 From: CabinetOnFire Date: Sun, 19 Jul 2026 12:31:14 +0200 Subject: [PATCH 027/445] Fixes behavior tree builder shitting up line endings on the compiled BT files and causing fake changes to appear on git (#97042) --- tools/build_bt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/build_bt.py b/tools/build_bt.py index 805d5d3d60f8..0a7f70ffd26e 100644 --- a/tools/build_bt.py +++ b/tools/build_bt.py @@ -304,13 +304,13 @@ def main() -> int: # either write or check depending on flag if check_mode: - existing = compiled_path.read_text(encoding='utf-8') if compiled_path.exists() else '' + existing = compiled_path.read_text(encoding='utf-8', newline='') if compiled_path.exists() else '' if existing != compiled_text: print(f'OUT OF DATE: {compiled_path.relative_to(repo_root)}', file=sys.stderr) dirty += 1 else: compiled_path.parent.mkdir(parents=True, exist_ok=True) - compiled_path.write_text(compiled_text, encoding='utf-8') + compiled_path.write_text(compiled_text, encoding='utf-8', newline='') # Remove stale compiled files that no longer correspond to a source tree — stale = [p for p in generated_dir.rglob('*.compiled.json') if p not in generated_paths] From ed2082e31422971927ffdda52dd5a5e9b21a88f9 Mon Sep 17 00:00:00 2001 From: Kashargul <144968721+Kashargul@users.noreply.github.com> Date: Sun, 19 Jul 2026 14:56:09 +0200 Subject: [PATCH 028/445] remove biomeTarget only when (#97017) ## About The Pull Request Removes the onlyWhen condition for biome install. If the lockfile changes, this prevents the dependencies from being updated, so people need to manually update them as it only ever gets installed when it's fully missing. It slightly increases the toolchain time, but at most it's 0.x to 3 seconds, because it checks if the biome version in the lockfile has changed. ## Why It's Good For The Game ## Changelog :cl: fix: biome install being skipped after a dependency update /:cl: --- tools/build/build.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/build/build.ts b/tools/build/build.ts index 19d7957da2f7..ab4022da3478 100644 --- a/tools/build/build.ts +++ b/tools/build/build.ts @@ -311,9 +311,6 @@ export const BunTarget = new Juke.Target({ export const BiomeInstallTarget = new Juke.Target({ dependsOn: [BunTarget], inputs: ['package.json', 'bun.lock'], - onlyWhen: () => { - return Juke.glob('node_modules/@biomejs/**').length === 0; - }, executes: () => { return bun('.', 'install'); }, From 62d35eb976a5b38897b04e807736841a0322e6aa Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 19 Jul 2026 14:56:32 +0200 Subject: [PATCH 029/445] Fixes watcher gaze runtimes, visual improvements (#97023) ## About The Pull Request Fixed missing signal unregs, gaze popup on affected mobs only appears above cliented mobs, gaze no longer swaps states after the ability triggers. ## Changelog :cl: qol: Watcher gaze visuals no longer flicker if you turn around after the ability has already been activated and is finishing the animation fix: Fixed watcher gaze runtimes /:cl: --- .../basic/lavaland/watcher/watcher_gaze.dm | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm index 27687114d284..f15edce30a12 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm @@ -39,28 +39,37 @@ owner.visible_message(span_warning("[owner]'s eye glows ominously!")) if (do_after(owner, delay = wait_delay, target = owner, hidden = TRUE)) trigger_effect() + proxmon_cleanup() else deltimer(stage_timer) clear_current_overlay() + proxmon_cleanup() + tracked_mobs.Cut() StartCooldown() - tracked_mobs.Cut() - QDEL_NULL(proximity_monitor) return TRUE /datum/action/cooldown/mob_cooldown/watcher_gaze/Destroy() + proxmon_cleanup() tracked_mobs.Cut() - QDEL_NULL(proximity_monitor) deltimer(stage_timer) clear_current_overlay() return ..() /datum/action/cooldown/mob_cooldown/watcher_gaze/Remove(mob/removed_from) + proxmon_cleanup() tracked_mobs.Cut() - QDEL_NULL(proximity_monitor) deltimer(stage_timer) clear_current_overlay() return ..() +/datum/action/cooldown/mob_cooldown/watcher_gaze/proc/proxmon_cleanup() + if (!QDELETED(proximity_monitor)) + QDEL_NULL(proximity_monitor) + for (var/victim_ref in tracked_mobs) + var/mob/living/victim = locate(victim_ref) + if (victim) + UnregisterSignal(victim, list(COMSIG_ATOM_POST_DIR_CHANGE, COMSIG_MOB_STATCHANGE)) + /// Do some effects to whoever is looking at us /datum/action/cooldown/mob_cooldown/watcher_gaze/proc/trigger_effect() deltimer(stage_timer) @@ -68,7 +77,7 @@ for (var/mob/living/viewer in viewers(effect_radius, owner)) if (!valid_target(viewer)) continue - if (!apply_effect(viewer)) + if (!apply_effect(viewer) || !viewer.client) continue var/image/flashed_overlay = image( icon = 'icons/effects/eldritch.dmi', @@ -100,6 +109,7 @@ /// Animate our effect out /datum/action/cooldown/mob_cooldown/watcher_gaze/proc/hide_eye() show_indicator_overlay("eye_close") + tracked_mobs.Cut() stage_timer = addtimer(CALLBACK(src, PROC_REF(clear_current_overlay)), animation_time, TIMER_STOPPABLE) /// Display an animated overlay over our head to indicate what's going on From b005caf758370a023460087cb502a8f6479516c3 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:57:00 +0000 Subject: [PATCH 030/445] Automatic changelog for PR #97017 [ci skip] --- html/changelogs/AutoChangeLog-pr-97017.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97017.yml diff --git a/html/changelogs/AutoChangeLog-pr-97017.yml b/html/changelogs/AutoChangeLog-pr-97017.yml new file mode 100644 index 000000000000..98b2d07fbb0c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97017.yml @@ -0,0 +1,4 @@ +author: "Kashargul" +delete-after: True +changes: + - bugfix: "biome install being skipped after a dependency update" \ No newline at end of file From 2f6605b40ba3562aeceb3982c289dec1afe9caf4 Mon Sep 17 00:00:00 2001 From: CabinetOnFire Date: Sun, 19 Jul 2026 14:57:08 +0200 Subject: [PATCH 031/445] Fixes repairbots not getting their interaction mode set and not being able to reliably repair (#97034) ## About The Pull Request Repairbots set BB_REPAIRBOT_INTERACTION_TYPE after finding a valid interaction target, but a higher priority decorator would cancel the parallel as soon as a target was found, preventing that node from actually running Increased wait length on the bot_patrol subtree as well, as a 1 second wait could end and keep restarting the parallels which is a bit wasteful and can cause some edge-cases. Also reduced target find delay a bit since I noticed it took quite a bit for bots to even look for targets (The processing difference is pretty minimal) ## Why It's Good For The Game Repairbots fix windows again fixes https://github.com/tgstation/tgstation/issues/97008 ## Changelog :cl: fix: Repairbots fix windows and other things reliably again /:cl: --- .../datums/ai/bots/bot_patrol.bt.compiled.json | 2 +- .../basic/bots/repairbot/repairbot.bt.compiled.json | 2 +- .../bots/repairbot/repairbot_find_target.bt.compiled.json | 2 +- .../repairbot/repairbot_repair_target.bt.compiled.json | 2 +- code/datums/ai/bots/bot_patrol.bt.json | 2 +- .../mob/living/basic/bots/repairbot/repairbot.bt.json | 2 +- .../basic/bots/repairbot/repairbot_find_target.bt.json | 8 ++++++++ .../basic/bots/repairbot/repairbot_repair_target.bt.json | 2 +- 8 files changed, 15 insertions(+), 7 deletions(-) diff --git a/build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json b/build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json index a192bb7c18fd..000666dd21ea 100644 --- a/build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json +++ b/build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json @@ -1 +1 @@ -{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/bot_mode_flag","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_next_beacon_target","target_key":"beacon_target"}],"key":"previous_beacon_target"},{"type":"/datum/bt_node/ai_behavior/find_first_beacon_target","target_key":"beacon_target"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"beacon_target","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/arrive_at_beacon","target_key":"beacon_target"}]}],"key":"beacon_target"}]}],"observer_abort":2,"flag":2}],"observer_abort":2,"cooldown_key":"bot_beacon_cooldown"},{"type":"/datum/bt_node/ai_behavior/wait","duration":10}]} +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/bot_mode_flag","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_next_beacon_target","target_key":"beacon_target"}],"key":"previous_beacon_target"},{"type":"/datum/bt_node/ai_behavior/find_first_beacon_target","target_key":"beacon_target"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"beacon_target","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/arrive_at_beacon","target_key":"beacon_target"}]}],"key":"beacon_target"}]}],"observer_abort":2,"flag":2}],"observer_abort":2,"cooldown_key":"bot_beacon_cooldown"},{"type":"/datum/bt_node/ai_behavior/wait","duration":50}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json index 622583c84e82..44f369334f47 100644 --- a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json @@ -1 +1 @@ -{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/repairbot_repair_target"},{"type":"/datum/bt_node/subtree/repairbot_find_target"},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/repairbot_repair_target"},{"type":"/datum/bt_node/subtree/repairbot_find_target"},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":5.0,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json index f6922d5734eb..460614e4c077 100644 --- a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json @@ -1 +1 @@ -{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_robot","target_key":"robot target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/deconstructable","target_key":"deconstruct_target","minimum_distance":1}]}],"invert":false},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/refillable_target","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_grille_target","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_window_fix","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_girder","target_key":"Current Target","minimum_distance":1}]},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"interaction_type","value":1},{"type":"/datum/bt_node/ai_behavior/cancel_current_plan"}]},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_wall_target","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"interaction_type","value":2},{"type":"/datum/bt_node/ai_behavior/cancel_current_plan"}]}]}],"observer_abort":0,"invert":true,"key":"Current Target"}]} +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_robot","time_between_perform":0,"target_key":"robot target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/deconstructable","target_key":"deconstruct_target","minimum_distance":1}]}],"invert":false},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached","time_between_perform":0,"target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/refillable_target","time_between_perform":0,"target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_grille_target","time_between_perform":0,"target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf","time_between_perform":0,"target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_window_fix","time_between_perform":0,"target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_girder","time_between_perform":0,"target_key":"Current Target","minimum_distance":1}]},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"interaction_type","value":1},{"type":"/datum/bt_node/ai_behavior/cancel_current_plan"}]},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_wall_target","time_between_perform":0,"target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"interaction_type","value":2},{"type":"/datum/bt_node/ai_behavior/cancel_current_plan"}]}]}],"observer_abort":0,"invert":true,"key":"Current Target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json index 5ff44f5bafda..f9f10e0da5f1 100644 --- a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json @@ -1 +1 @@ -{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/subtree/repairbot_emagged"}],"observer_abort":3},{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/bot_interact","target_key":"Current Target"}],"key":"interaction_type","value":1},{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder","ability_key":"girder_build_ability","target_key":"Current Target"}],"key":"interaction_type","value":2}]}]}],"observer_abort":3,"key":"Current Target"}],"observer_abort":1,"invert":true},{"type":"/datum/bt_node/subtree/bot_patrol"}]} +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/subtree/repairbot_emagged"}],"observer_abort":3},{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/bot_interact","target_key":"Current Target"}],"key":"interaction_type","value":1},{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder","ability_key":"girder_build_ability","target_key":"Current Target"}],"key":"interaction_type","value":2}]}]}],"observer_abort":1,"key":"Current Target"}],"observer_abort":1,"invert":true},{"type":"/datum/bt_node/subtree/bot_patrol"}]} diff --git a/code/datums/ai/bots/bot_patrol.bt.json b/code/datums/ai/bots/bot_patrol.bt.json index 9d6f1f84067a..faa8708e2e41 100644 --- a/code/datums/ai/bots/bot_patrol.bt.json +++ b/code/datums/ai/bots/bot_patrol.bt.json @@ -81,7 +81,7 @@ "type": "leaf", "behavior": "/datum/bt_node/ai_behavior/wait", "vars": { - "duration": "1 SECONDS" + "duration": "5 SECONDS" } } ] diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json index 2949201eb088..11f16b782b3f 100644 --- a/code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json @@ -11,7 +11,7 @@ "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", "repeat_secondary": true, - "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "repeat_secondary_delay": "0.5 SECONDS", "finish_on_primary": true, "children": [ { diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json index b9d7b7f5c7f1..4e7a2f41aab3 100644 --- a/code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json @@ -15,6 +15,7 @@ "type": "leaf", "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_robot", "vars": { + "time_between_perform": 0, "target_key": "BB_ROBOT_TARGET", "minimum_distance": 1 } @@ -51,6 +52,7 @@ "type": "leaf", "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached", "vars": { + "time_between_perform": 0, "target_key": "BB_CURRENT_TARGET", "minimum_distance": 1 } @@ -59,6 +61,7 @@ "type": "leaf", "behavior": "/datum/bt_node/ai_behavior/bot_search/refillable_target", "vars": { + "time_between_perform": 0, "target_key": "BB_CURRENT_TARGET", "minimum_distance": 1 } @@ -67,6 +70,7 @@ "type": "leaf", "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_grille_target", "vars": { + "time_between_perform": 0, "target_key": "BB_CURRENT_TARGET", "minimum_distance": 1 } @@ -75,6 +79,7 @@ "type": "leaf", "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf", "vars": { + "time_between_perform": 0, "target_key": "BB_CURRENT_TARGET", "minimum_distance": 1 } @@ -83,6 +88,7 @@ "type": "leaf", "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_window_fix", "vars": { + "time_between_perform": 0, "target_key": "BB_CURRENT_TARGET", "minimum_distance": 1 } @@ -91,6 +97,7 @@ "type": "leaf", "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_girder", "vars": { + "time_between_perform": 0, "target_key": "BB_CURRENT_TARGET", "minimum_distance": 1 } @@ -118,6 +125,7 @@ "type": "leaf", "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_wall_target", "vars": { + "time_between_perform": 0, "target_key": "BB_CURRENT_TARGET", "minimum_distance": 1 } diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json index d8e44907e6ce..43139015fa58 100644 --- a/code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json @@ -24,7 +24,7 @@ "type": "decorator", "decorator": "/datum/bt_node/decorator/bb_key_set", "vars": { - "observer_abort": "BT_ABORT_BOTH", + "observer_abort": "BT_ABORT_SELF", "key": "BB_CURRENT_TARGET" }, "child": { From d0d2e016b5eb5e76cbf670b81b47ecd591c603d7 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:57:39 +0000 Subject: [PATCH 032/445] Automatic changelog for PR #97023 [ci skip] --- html/changelogs/AutoChangeLog-pr-97023.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97023.yml diff --git a/html/changelogs/AutoChangeLog-pr-97023.yml b/html/changelogs/AutoChangeLog-pr-97023.yml new file mode 100644 index 000000000000..e568edee023a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97023.yml @@ -0,0 +1,5 @@ +author: "SmArtKar" +delete-after: True +changes: + - qol: "Watcher gaze visuals no longer flicker if you turn around after the ability has already been activated and is finishing the animation" + - bugfix: "Fixed watcher gaze runtimes" \ No newline at end of file From 426fbefb474ee0b499deeee1fa1213ca7c39ed21 Mon Sep 17 00:00:00 2001 From: CabinetOnFire Date: Sun, 19 Jul 2026 14:57:56 +0200 Subject: [PATCH 033/445] Fixes some looping sounds treating start and end sound as lists. (#97037) ## About The Pull Request Some looping sounds thought start and end sounds were lists. While this is technically supported by DM, looping sounds and more specifically rustg-sound-length does not, and will runtime if given a list. ## Why It's Good For The Game bug fix good ## Changelog :cl: fix: Some looping sounds no longer runtime when stopping/starting /:cl: --- code/datums/looping_sounds/item_sounds.dm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/datums/looping_sounds/item_sounds.dm b/code/datums/looping_sounds/item_sounds.dm index 517dc752944d..7694873eb645 100644 --- a/code/datums/looping_sounds/item_sounds.dm +++ b/code/datums/looping_sounds/item_sounds.dm @@ -18,7 +18,7 @@ /datum/looping_sound/tape_recorder_hiss mid_sounds = list('sound/items/taperecorder/taperecorder_hiss_mid.ogg' = 1) - start_sound = list('sound/items/taperecorder/taperecorder_hiss_start.ogg' = 1) + start_sound = 'sound/items/taperecorder/taperecorder_hiss_start.ogg' volume = 10 /datum/looping_sound/heartbeat @@ -35,11 +35,11 @@ use_sound_tokens = TRUE /datum/looping_sound/chainsaw - start_sound = list('sound/items/weapons/chainsaw_start.ogg' = 1) + start_sound = 'sound/items/weapons/chainsaw_start.ogg' start_length = 0.85 SECONDS mid_sounds = list('sound/items/weapons/chainsaw_loop.ogg' = 1) mid_length = 0.85 SECONDS - end_sound = list('sound/items/weapons/chainsaw_stop.ogg' = 1) + end_sound = 'sound/items/weapons/chainsaw_stop.ogg' end_volume = 35 volume = 40 ignore_walls = FALSE @@ -54,13 +54,13 @@ volume = 5 /datum/looping_sound/tesla_cannon - start_sound = list('sound/items/weapons/gun/tesla/tesla_start.ogg' = 1) + start_sound = 'sound/items/weapons/gun/tesla/tesla_start.ogg' start_volume = 100 start_length = 200 MILLISECONDS mid_sounds = list('sound/items/weapons/gun/tesla/tesla_loop.ogg' = 1) mid_length = 3.8 SECONDS volume = 100 - end_sound = list('sound/items/weapons/gun/tesla/power_breaker_fan.ogg' = 1) + end_sound = 'sound/items/weapons/gun/tesla/power_breaker_fan.ogg' end_volume = 15 ignore_walls = FALSE reserve_random_channel = TRUE From 727575e80a9393dbabdca8979b94eaf3ede69ed7 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:58:19 +0000 Subject: [PATCH 034/445] Automatic changelog for PR #97034 [ci skip] --- html/changelogs/AutoChangeLog-pr-97034.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97034.yml diff --git a/html/changelogs/AutoChangeLog-pr-97034.yml b/html/changelogs/AutoChangeLog-pr-97034.yml new file mode 100644 index 000000000000..86e4f60fa135 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97034.yml @@ -0,0 +1,4 @@ +author: "CabinetOnFire" +delete-after: True +changes: + - bugfix: "Repairbots fix windows and other things reliably again" \ No newline at end of file From ecdd3e21c232cbc74560646fbad35f94c7f33b73 Mon Sep 17 00:00:00 2001 From: QuiteLiterallyAnything <154708292+QuiteLiterallyAnything@users.noreply.github.com> Date: Sun, 19 Jul 2026 07:58:30 -0500 Subject: [PATCH 035/445] Makes broken mirrors deconstruct properly (#97011) ## About The Pull Request This PR makes broken mirrors deconstruct to broken mirror wallframes. A stray and seemingly duplicate definition for `atom_deconstruct()` has also been removed. ## Why It's Good For The Game Broken mirrors currently deconstruct to intact mirror wallframes. This seems like an oversight. ## Changelog :cl: fix: Broken mirrors no longer produce an intact mirror upon deconstruction. /:cl: --- code/game/objects/structures/mirror.dm | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 03315f2ad4d1..43a6e7108f93 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -108,9 +108,6 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) return ITEM_INTERACT_SUCCESS return ITEM_INTERACT_BLOCKING -/obj/structure/mirror/atom_deconstruct() - new /obj/item/wallframe/mirror(loc, 1) - /obj/structure/mirror/proc/display_radial_menu(mob/living/carbon/human/user) if(!can_use_mirror(user)) return @@ -321,6 +318,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) /obj/structure/mirror/atom_deconstruct(disassembled = TRUE) if(!disassembled) new /obj/item/shard(loc) + else if(broken) + new /obj/item/wallframe/mirror/broken(loc) else new /obj/item/wallframe/mirror(loc) @@ -353,13 +352,19 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) /obj/item/wallframe/mirror name = "mirror" - desc = "An unmounted mirror. Attach it to a wall to use." + desc = "An unmounted mirror. Attach it to a wall for use." icon = 'icons/obj/watercloset.dmi' icon_state = "mirror" custom_materials = list(/datum/material/glass = SHEET_MATERIAL_AMOUNT * 5, /datum/material/silver = SHEET_MATERIAL_AMOUNT * 2) result_path = /obj/structure/mirror pixel_shift = 28 +/obj/item/wallframe/mirror/broken + name = "broken mirror" + desc = "An unmounted and broken mirror. Attach it to a wall for decor." + icon_state = "mirror_broke" + result_path = /obj/structure/mirror/broken + /obj/structure/mirror/magic name = "magic mirror" desc = "Turn and face the strange... face." From f25bcb4d923199d2bcd384c03f082163336415ca Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:59:16 +0000 Subject: [PATCH 036/445] Automatic changelog for PR #97037 [ci skip] --- html/changelogs/AutoChangeLog-pr-97037.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97037.yml diff --git a/html/changelogs/AutoChangeLog-pr-97037.yml b/html/changelogs/AutoChangeLog-pr-97037.yml new file mode 100644 index 000000000000..8b331824b71d --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97037.yml @@ -0,0 +1,4 @@ +author: "CabinetOnFire" +delete-after: True +changes: + - bugfix: "Some looping sounds no longer runtime when stopping/starting" \ No newline at end of file From e63d97d16bfd43be3fc80a2bb3eb800a14691c66 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:59:34 +0000 Subject: [PATCH 037/445] Automatic changelog for PR #97011 [ci skip] --- html/changelogs/AutoChangeLog-pr-97011.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97011.yml diff --git a/html/changelogs/AutoChangeLog-pr-97011.yml b/html/changelogs/AutoChangeLog-pr-97011.yml new file mode 100644 index 000000000000..e21d04045324 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97011.yml @@ -0,0 +1,4 @@ +author: "QuiteLiterallyAnything" +delete-after: True +changes: + - bugfix: "Broken mirrors no longer produce an intact mirror upon deconstruction." \ No newline at end of file From 6a5cd11d58a13fddebf57d60c12fa6a5ba6626cb Mon Sep 17 00:00:00 2001 From: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com> Date: Sun, 19 Jul 2026 11:47:30 -0400 Subject: [PATCH 038/445] fixes kilo genetics missing stuff (#97030) I forget ## Changelog :cl: fix: kilo genetics now has gene disks and a flesh reshaper /:cl: --- _maps/map_files/KiloStation/KiloStation.dmm | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm index 859999fbc45b..57fc94ac901e 100644 --- a/_maps/map_files/KiloStation/KiloStation.dmm +++ b/_maps/map_files/KiloStation/KiloStation.dmm @@ -27874,6 +27874,23 @@ dir = 8 }, /obj/effect/turf_decal/stripes/line, +/obj/structure/table, +/obj/item/storage/box/disks{ + pixel_x = 5; + pixel_y = 9 + }, +/obj/item/storage/box/bodybags{ + pixel_x = -8; + pixel_y = 9 + }, +/obj/item/storage/box/gloves{ + pixel_x = 6; + pixel_y = 1 + }, +/obj/item/flesh_shears{ + pixel_x = -10; + pixel_y = -5 + }, /turf/open/floor/iron/showroomfloor, /area/station/science/genetics) "iMr" = ( From dec610f059992926f33dfd5dc6b889d49cb290d4 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:47:48 +0000 Subject: [PATCH 039/445] Automatic changelog for PR #97030 [ci skip] --- html/changelogs/AutoChangeLog-pr-97030.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97030.yml diff --git a/html/changelogs/AutoChangeLog-pr-97030.yml b/html/changelogs/AutoChangeLog-pr-97030.yml new file mode 100644 index 000000000000..9d19e1ed1936 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97030.yml @@ -0,0 +1,4 @@ +author: "TheRyeGuyWhoWillNowDie" +delete-after: True +changes: + - bugfix: "kilo genetics now has gene disks and a flesh reshaper" \ No newline at end of file From 6ccd5a338e7ad631e2e71b89be46d6cad345169c Mon Sep 17 00:00:00 2001 From: YesterdaysPromise <122572637+YesterdaysPromise@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:48:34 +0200 Subject: [PATCH 040/445] Fixes icewing watcher butcher results. (#97038) ## About The Pull Request Randomly noticed in the other PR, Icewing watchers for some reason have two sets of butcher results. That is an issue if I've seen one. ## Why It's Good For The Game Just code cleanup. I am pretty sure the latter, correct line overwrites the former anyway. If it doesn't, then it makes icewing sinew obtainable again. ## Changelog N/A --- code/modules/mob/living/basic/lavaland/watcher/watcher.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher.dm index 4eceea8a73cc..1c6850aa55e7 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher.dm @@ -107,7 +107,6 @@ health = 130 projectile_type = /obj/projectile/temp/watcher/ice_wing gaze_attack = /datum/action/cooldown/mob_cooldown/watcher_gaze/ice - butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/bone = 1) butcher_results = list( /obj/item/stack/sheet/bone = 1, /obj/item/stack/ore/diamond = 5, From 816bbed5cb959369f2ce9fddae0c0fce51aa037d Mon Sep 17 00:00:00 2001 From: Roxy <75404941+TealSeer@users.noreply.github.com> Date: Sun, 19 Jul 2026 10:52:28 -0500 Subject: [PATCH 041/445] Convert three JSX interfaces to TSX (#97026) ## About The Pull Request Convert disposal unit, electropack, and exosuit control console interfaces to typescript ## Why It's Good For The Game Types are good ## Changelog N/A --- .../items/devices/radio/electropack.dm | 4 + code/modules/recycling/disposal/bin.dm | 2 +- .../{DisposalUnit.jsx => DisposalUnit.tsx} | 51 +++++++----- .../{Electropack.jsx => Electropack.tsx} | 29 ++++--- ...lConsole.jsx => ExosuitControlConsole.tsx} | 80 ++++++++++++------- 5 files changed, 107 insertions(+), 59 deletions(-) rename tgui/packages/tgui/interfaces/{DisposalUnit.jsx => DisposalUnit.tsx} (55%) rename tgui/packages/tgui/interfaces/{Electropack.jsx => Electropack.tsx} (83%) rename tgui/packages/tgui/interfaces/{ExosuitControlConsole.jsx => ExosuitControlConsole.tsx} (64%) diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm index d6557c4aaa80..db99e99ff587 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -101,6 +101,10 @@ data["power"] = on data["frequency"] = frequency data["code"] = code + return data + +/obj/item/electropack/ui_static_data(mob/user) + var/list/data = list() data["minFrequency"] = MIN_FREE_FREQ data["maxFrequency"] = MAX_FREE_FREQ return data diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm index 0b36785f681a..b5ad526c7604 100644 --- a/code/modules/recycling/disposal/bin.dm +++ b/code/modules/recycling/disposal/bin.dm @@ -22,7 +22,7 @@ GLOBAL_VAR_INIT(disposals_animals_spawned, 0) var/full_pressure = FALSE /// Is the pressure charging var/pressure_charging = TRUE - // True if flush handle is pulled + /// True if flush handle is pulled var/flush = FALSE /// The attached pipe trunk var/obj/structure/disposalpipe/trunk/trunk = null diff --git a/tgui/packages/tgui/interfaces/DisposalUnit.jsx b/tgui/packages/tgui/interfaces/DisposalUnit.tsx similarity index 55% rename from tgui/packages/tgui/interfaces/DisposalUnit.jsx rename to tgui/packages/tgui/interfaces/DisposalUnit.tsx index 08bb0a9ad19c..417fec1331d9 100644 --- a/tgui/packages/tgui/interfaces/DisposalUnit.jsx +++ b/tgui/packages/tgui/interfaces/DisposalUnit.tsx @@ -8,17 +8,28 @@ import { import { useBackend } from '../backend'; import { Window } from '../layouts'; +type DisposalUnitData = { + flush: boolean; + full_pressure: boolean; + pressure_charging: boolean; + panel_open: boolean; + per: number; + isai: boolean; +}; + export const DisposalUnit = (props) => { - const { act, data } = useBackend(); - let stateColor; - let stateText; - if (data.full_pressure) { + const { act, data } = useBackend(); + const { flush, full_pressure, pressure_charging, panel_open, per, isai } = + data; + let stateColor: string; + let stateText: string; + if (full_pressure) { stateColor = 'good'; stateText = 'Ready'; - } else if (data.panel_open) { + } else if (panel_open) { stateColor = 'bad'; stateText = 'Power Disabled'; - } else if (data.pressure_charging) { + } else if (pressure_charging) { stateColor = 'average'; stateText = 'Pressurizing'; } else { @@ -34,32 +45,32 @@ export const DisposalUnit = (props) => { {stateText} - + act('reset', { reset: 'freq', }) } - /> + > + Reset + } > { buttons={ } > { - const { act, data } = useBackend(); - const { mechs = [] } = data; + const { act, data } = useBackend(); + const { mechs } = data; return ( @@ -26,17 +42,17 @@ export const ExosuitControlConsole = (props) => { <> } > @@ -61,40 +79,44 @@ export const ExosuitControlConsole = (props) => { - - {(typeof mech.charge === 'number' && `${mech.charge}%`) || - 'Not Found'} - + {mech.charge !== null ? ( + + {mech.charge}% + + ) : ( + Not Found + )} - {(typeof mech.airtank === 'number' && ( + {mech.airtank !== null ? ( `${toFixed(value, 2)} kPa`} /> - )) || - 'Not Equipped'} + ) : ( + 'Not Equipped' + )} - {(mech.pilot.length > 0 && - mech.pilot.map((pilot) => ( - - {pilot} - {mech.pilot.length > 1 ? '|' : ''} - - ))) || - 'None'} + {mech.pilot.length > 0 + ? mech.pilot.map((pilot) => ( + + {pilot} + {mech.pilot.length > 1 ? '|' : ''} + + )) + : 'None'} {mech.location || 'Unknown'} - {mech.cargo_space >= 0 && ( + {mech.cargo_space !== undefined && ( Date: Sun, 19 Jul 2026 12:00:40 -0400 Subject: [PATCH 042/445] Fixes some bugs with GetFlatIcon, improves performance of Select Equipment by a lot (#97016) ## About The Pull Request Fixes a bug that has apparently existed for five years now. After resizing the canvas to fit an oversized overlay (like a wide tail sprite), the new bounds get reassigned with X and Y swapped. This leads to bad icons being often being generated if they are larger than 32x32. For me it just showed one overlay of the mob's tail, with the rest of them yeeted off to who knows where. This bug also applies to universal icon equivalent proc and has been fixed there as well. Also does some optimization on select_equipment. This ui can create a noticeable amount of lag on the server if an admin is using it. I've stopped it from regenerating the icon if there's an unrelated ui update (like clicking on the favorite button or something). I also made the previews one-direction instead of 4, which was the biggest gain because GetFlatIcon was the biggest lag contributor for this proc.
Before image 7K5japEGZe
After tracy-profiler_QbM8LM1pGe 8XBxy41sHW
Note: This was not a full refactor, and there is room for improvement. These were just the lowest hanging fruit for now. The rest would require touching the outfit equip procs and probably doing a refactor there to reduce the amount of `update_body()` calls are happening (there's quite a few happening needlessly). ## Why It's Good For The Game Fixes a long-standing bug, and reduces lag. ## Changelog :cl: fix: fixed an issue that would cause preview icons to randomly become corrupt or invisible if they met certain criteria code: select equipment now just shows one direction of previews, greatly reducing lag /:cl: --- code/__HELPERS/icons.dm | 4 ++-- code/modules/admin/verbs/selectequipment.dm | 20 +++++++++++++------ .../spritesheet/batched/universal_icon.dm | 4 ++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 3380da135819..9898db8bacf4 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -553,8 +553,8 @@ world ) flatX1 = addX1 - flatX2 = addY1 - flatY1 = addX2 + flatX2 = addX2 + flatY1 = addY1 flatY2 = addY2 // Blend the overlay into the flattened icon diff --git a/code/modules/admin/verbs/selectequipment.dm b/code/modules/admin/verbs/selectequipment.dm index 5731702e6caa..f042af64c7d1 100644 --- a/code/modules/admin/verbs/selectequipment.dm +++ b/code/modules/admin/verbs/selectequipment.dm @@ -28,6 +28,10 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(select_equipment, R_FUN, "Select Equipment", mob/ta var/datum/outfit/selected_outfit = /datum/outfit //serializable string for the UI to keep track of which outfit is selected var/selected_identifier = "/datum/outfit" + /// Cached flat icon of the dummy wearing selected_outfit, so ui_data() doesn't redraw the sprite on every refresh when it doesn't need to + var/icon/cached_dummy_icon + /// The selected_identifier cached_dummy_icon was last rendered for. + var/cached_for_identifier /datum/select_equipment/New(_user, mob/target) user = CLIENT_FROM_VAR(_user) @@ -105,12 +109,16 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(select_equipment, R_FUN, "Select Equipment", mob/ta if(!dummy_key) init_dummy() - var/icon/dummysprite = get_flat_human_icon(null, - dummy_key = dummy_key, - outfit_override = selected_outfit, - no_anim = TRUE, - ) - data["icon64"] = icon2base64(dummysprite) + if(isnull(cached_dummy_icon) || cached_for_identifier != selected_identifier) + cached_dummy_icon = get_flat_human_icon(null, + dummy_key = dummy_key, + showDirs = list(SOUTH), + outfit_override = selected_outfit, + no_anim = TRUE, + ) + cached_for_identifier = selected_identifier + + data["icon64"] = icon2base64(cached_dummy_icon) data["name"] = target_mob var/datum/preferences/prefs = user?.client?.prefs diff --git a/code/modules/asset_cache/spritesheet/batched/universal_icon.dm b/code/modules/asset_cache/spritesheet/batched/universal_icon.dm index efd120ac782a..caa9d7f00c53 100644 --- a/code/modules/asset_cache/spritesheet/batched/universal_icon.dm +++ b/code/modules/asset_cache/spritesheet/batched/universal_icon.dm @@ -579,8 +579,8 @@ ) flatX1 = addX1 - flatX2 = addY1 - flatY1 = addX2 + flatX2 = addX2 + flatY1 = addY1 flatY2 = addY2 // Blend the overlay into the flattened icon From f0308efb17f45746a05ff85484746433826d9ca1 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:00:58 +0000 Subject: [PATCH 043/445] Automatic changelog for PR #97016 [ci skip] --- html/changelogs/AutoChangeLog-pr-97016.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97016.yml diff --git a/html/changelogs/AutoChangeLog-pr-97016.yml b/html/changelogs/AutoChangeLog-pr-97016.yml new file mode 100644 index 000000000000..00488eaab14d --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97016.yml @@ -0,0 +1,5 @@ +author: "vinylspiders" +delete-after: True +changes: + - bugfix: "fixed an issue that would cause preview icons to randomly become corrupt or invisible if they met certain criteria" + - admin: "select equipment now just shows one direction of previews, greatly reducing lag" \ No newline at end of file From a66dbcc2957d2876487729826c53e040a8d5cf21 Mon Sep 17 00:00:00 2001 From: CabinetOnFire Date: Sun, 19 Jul 2026 18:18:40 +0200 Subject: [PATCH 044/445] makes the behavior tree compiler use bootstrap (#97040) ## About The Pull Request for anyone that doesnt have python installed LOL ## Why It's Good For The Game apparently our headcoder has never used python before (self report if u ask me) --- .vscode/tasks.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index eda5a2557f5e..6622ca0cadc4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,7 +3,10 @@ "tasks": [ { "type": "shell", - "command": "python tools/build_bt.py", + "command": "tools/bootstrap/python tools/build_bt.py", + "windows": { + "command": ".\\tools\\bootstrap\\python.bat tools/build_bt.py" + }, "group": "build", "presentation": { "reveal": "silent", "panel": "shared" }, "label": "Compile BT Trees" From b888c7d2c1227b2fcd692998a612ddf823321100 Mon Sep 17 00:00:00 2001 From: Fghj240 <43589874+Fghj240@users.noreply.github.com> Date: Sun, 19 Jul 2026 09:22:52 -0700 Subject: [PATCH 045/445] Health analyzer disease hover text (#96987) ## About The Pull Request Adds hover text to the "Warning: Advanced Disease detected" text that tells you you can cure it by taking the cure or spaceacillin or eating food or sleeping. Also changes "possible cure" into just "cure" because there's no way for it to be wrong ## Why It's Good For The Game Knowledge check gets a lot of players killed. ## Changelog :cl: spellcheck: changed "possible cure" to "cure" in healthscans to show they can't be wrong qol: Health analyzer has hover text for disease curing strategies /:cl: --------- Co-authored-by: Fghj240 --- code/game/machinery/medical_kiosk.dm | 2 +- code/game/objects/items/devices/scanners/health_analyzer.dm | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/game/machinery/medical_kiosk.dm b/code/game/machinery/medical_kiosk.dm index a6b6452a0d2b..9673da82666a 100644 --- a/code/game/machinery/medical_kiosk.dm +++ b/code/game/machinery/medical_kiosk.dm @@ -246,7 +246,7 @@ if(istype(disease, /datum/disease/advance)) var/datum/disease/advance/advanced_disease = disease cure_text = advanced_disease.generate_cure_text(2) - sickness_data = "\nName: [disease.name].\nType: [disease.spread_text].\nStage: [disease.stage]/[disease.max_stages].\nPossible Cure: [cure_text]" + sickness_data = "\nName: [disease.name].\nType: [disease.spread_text].\nStage: [disease.stage]/[disease.max_stages].\nCure: [cure_text]" if(patient.can_bleed()) //Blood levels Information blood_name = LOWER_TEXT(blood_type.get_blood_name()) diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index 8e823fab54ed..758b5e84233e 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -456,12 +456,12 @@ else cure_text = disease.cure_text render_list += "\ - Warning: [disease.form] detected
\ + [conditional_tooltip("Warning: [disease.form] detected", "Supply listed cure or [/datum/reagent/medicine/spaceacillin::name], or treat with food and rest.", tochat)]
\
\ Name: [disease.name].
\ Type: [disease.spread_text].
\ Stage: [disease.stage]/[disease.max_stages].
\ - Possible Cure: [cure_text]
\ + Cure: [cure_text]\
" // Time of death @@ -817,7 +817,7 @@ var/datum/disease/advance/advanced_disease = disease disease_cure = advanced_disease.generate_cure_text(1) render += "Warning: [disease.form] detected
\ -
Name: [disease.name].
Type: [disease.spread_text].
Stage: [disease.stage]/[disease.max_stages].
Possible Cure: [disease_cure]
\ +
Name: [disease.name].
Type: [disease.spread_text].
Stage: [disease.stage]/[disease.max_stages].
Cure: [disease_cure]
\
" if(!length(render)) From 8831bf5e027a5412d525e0c253e41142d8a7cd1b Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:23:14 +0000 Subject: [PATCH 046/445] Automatic changelog for PR #96987 [ci skip] --- html/changelogs/AutoChangeLog-pr-96987.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96987.yml diff --git a/html/changelogs/AutoChangeLog-pr-96987.yml b/html/changelogs/AutoChangeLog-pr-96987.yml new file mode 100644 index 000000000000..6a887dc79369 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96987.yml @@ -0,0 +1,5 @@ +author: "Fghj240" +delete-after: True +changes: + - spellcheck: "changed \"possible cure\" to \"cure\" in healthscans to show they can't be wrong" + - qol: "Health analyzer has hover text for disease curing strategies" \ No newline at end of file From 4c5128ca54f4ad6f7a33d1580b9e6df278d72431 Mon Sep 17 00:00:00 2001 From: Hatterhat <31829017+Hatterhat@users.noreply.github.com> Date: Sun, 19 Jul 2026 11:23:26 -0500 Subject: [PATCH 047/445] adds missing plant nutriments to the botanical dispenser (#96923) ## About The Pull Request Adds Enduro Grow and Liquid Earthquake to the botanical chemical dispenser found in the lifebringer ship/conveniently admin-spawnable for botany-related stuff/etc. ## Why It's Good For The Game While the lifebringer ship already has a biogenerator that lets you make these reagents, it always felt a little weird that the dispenser that dispensed basically everything else... didn't have these. For completion's sake, we might as well give it to them. ## Changelog :cl: qol: The botanical chemical dispenser (as seen on the lifebringer ship) is now also capable of dispensing Enduro Grow and Liquid Earthquake. /:cl: Co-authored-by: Hatterhat --- code/modules/reagents/chemistry/machinery/chem_dispenser.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index b55a689b313b..df94fc3b5c32 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -755,6 +755,8 @@ /datum/reagent/plantnutriment/eznutriment, /datum/reagent/plantnutriment/left4zednutriment, /datum/reagent/plantnutriment/robustharvestnutriment, + /datum/reagent/plantnutriment/endurogrow, + /datum/reagent/plantnutriment/liquidearthquake, /datum/reagent/water, /datum/reagent/toxin/plantbgone, /datum/reagent/toxin/plantbgone/weedkiller, From a3154d740daec9f4d9fc62a52dcfc70a3acd8362 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:23:47 +0000 Subject: [PATCH 048/445] Automatic changelog for PR #96923 [ci skip] --- html/changelogs/AutoChangeLog-pr-96923.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96923.yml diff --git a/html/changelogs/AutoChangeLog-pr-96923.yml b/html/changelogs/AutoChangeLog-pr-96923.yml new file mode 100644 index 000000000000..4e6d4dc4f7d1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96923.yml @@ -0,0 +1,4 @@ +author: "Hatterhat" +delete-after: True +changes: + - qol: "The botanical chemical dispenser (as seen on the lifebringer ship) is now also capable of dispensing Enduro Grow and Liquid Earthquake." \ No newline at end of file From 1730ec573eeb927544234b49b8fb77b349c140d2 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:24:35 -0400 Subject: [PATCH 049/445] Some quirks menu backends improvements (#96999) ## About The Pull Request I noticed a similar pattern of silliness here as with https://github.com/tgstation/tgstation/pull/96808 where there was a `tainted` var to limit updates in `ui_data()` _in addition to_ the same data being passed through `ui_static_data()`. Having two identical data keys being sent in both `ui_data()` and `ui_static_data()` like this causes issues and doesn't really make sense. This PR takes full advantage of the state var that was being half-used and removes the need for a bunch of `update_static_data()`, calls which is great because those full updates were laggy as they were unneeded. Thanks to the `tainted` var it will only update the data when: -a new character is swapped to -a quirk is added or removed -a species is changed -rare invalid give/remove quirk error condition ## Why It's Good For The Game Faster UI that actually works properly!
Works cCtZayEptp
Limitations update right away like they're supposed to (the nearsightedness is temp modified to test) 2ewt2r4kiy
## Changelog :cl: fix: species quirk incompatibilities will now update immediately without the need to add/remove a quirk to force a ui update code: quirks backend improved, it will no longer send full updates constantly and be faster /:cl: --- .../client/preferences/middleware/quirks.dm | 32 +++++++++++++------ code/modules/client/preferences/species.dm | 3 ++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/code/modules/client/preferences/middleware/quirks.dm b/code/modules/client/preferences/middleware/quirks.dm index f950db615643..8c17baa0fa7b 100644 --- a/code/modules/client/preferences/middleware/quirks.dm +++ b/code/modules/client/preferences/middleware/quirks.dm @@ -1,6 +1,13 @@ /// Middleware to handle quirks /datum/preference_middleware/quirks - var/tainted = FALSE + /// Used to track whether or not we need to update changes in ui_data + var/tainted = TRUE + /// Remember what the last species we chose was, to avoid having to validate quirks again + var/previous_species_value + /// The current selected quirks, saved so we can cheaply resend them in ui_data only when necessary, without having to use expensive update_static_data() calls + var/list/cached_selected_quirks + /// The current species compatibility, saved so we can cheaply resend them in ui_data only when necessary, without having to use expensive update_static_data() calls + var/list/cached_species_compatibility action_delegations = list( "give_quirk" = PROC_REF(give_quirk), @@ -28,9 +35,11 @@ return TRUE /datum/preference_middleware/quirks/post_set_preference(mob/user, preference, value) - if(preference != "species") + if(preference != "species" || value == previous_species_value) return + tainted = TRUE + previous_species_value = value preferences.validate_quirks() /datum/preference_middleware/quirks/proc/get_species_compatibility() @@ -47,19 +56,20 @@ var/list/data = list() - data["selected_quirks"] = get_selected_quirks() data["default_quirk_balance"] = SSquirks.default_quirk_points - data["species_disallowed_quirks"] = get_species_compatibility() return data /datum/preference_middleware/quirks/get_ui_data(mob/user) var/list/data = list() - if (tainted) + if (tainted || isnull(cached_selected_quirks)) // if one of these is null, both are null tainted = FALSE - data["selected_quirks"] = get_selected_quirks() - data["species_disallowed_quirks"] = get_species_compatibility() + cached_selected_quirks = get_selected_quirks() + cached_species_compatibility = get_species_compatibility() + + data["selected_quirks"] = cached_selected_quirks + data["species_disallowed_quirks"] = cached_species_compatibility return data @@ -96,7 +106,7 @@ /datum/preference_middleware/quirks/on_new_character(mob/user) tainted = TRUE - preferences.update_static_data(user, always_instant = TRUE) + previous_species_value = null /datum/preference_middleware/quirks/proc/give_quirk(list/params, mob/user) var/quirk_name = params["quirk"] @@ -107,12 +117,13 @@ // If the client is sending an invalid give_quirk, that means that // something went wrong with the client prediction, so we should // catch it back up to speed. + tainted = TRUE preferences.update_static_data(user, always_instant = TRUE) return TRUE preferences.all_quirks = new_quirks + tainted = TRUE preferences.character_preview_view?.update_body() - preferences.update_static_data(user, always_instant = TRUE) return TRUE @@ -127,12 +138,13 @@ // If the client is sending an invalid remove_quirk, that means that // something went wrong with the client prediction, so we should // catch it back up to speed. + tainted = TRUE preferences.update_static_data(user, always_instant = TRUE) return TRUE preferences.all_quirks = new_quirks + tainted = TRUE preferences.character_preview_view?.update_body() - preferences.update_static_data(user, always_instant = TRUE) return TRUE diff --git a/code/modules/client/preferences/species.dm b/code/modules/client/preferences/species.dm index 1c74d7981b65..c36b1f33e058 100644 --- a/code/modules/client/preferences/species.dm +++ b/code/modules/client/preferences/species.dm @@ -27,6 +27,9 @@ return values /datum/preference/choiced/species/apply_to_human(mob/living/carbon/human/target, value) + if(value == target.dna.species.type) // we're already this species + return + target.set_species(value, icon_update = FALSE, pref_load = TRUE) /datum/preference/choiced/species/compile_constant_data() From 1e78ab0acbefbb15c67e829a3d6797b65295fccd Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:24:53 +0000 Subject: [PATCH 050/445] Automatic changelog for PR #96999 [ci skip] --- html/changelogs/AutoChangeLog-pr-96999.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96999.yml diff --git a/html/changelogs/AutoChangeLog-pr-96999.yml b/html/changelogs/AutoChangeLog-pr-96999.yml new file mode 100644 index 000000000000..2e0c9d27bf32 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96999.yml @@ -0,0 +1,5 @@ +author: "vinylspiders" +delete-after: True +changes: + - bugfix: "species quirk incompatibilities will now update immediately without the need to add/remove a quirk to force a ui update" + - code_imp: "quirks backend improved, it will no longer send full updates constantly and be faster" \ No newline at end of file From 2c6a0497d15693c1eea9ec908567bd1837febf61 Mon Sep 17 00:00:00 2001 From: nPocToDark Date: Sun, 19 Jul 2026 20:14:58 +0300 Subject: [PATCH 051/445] Cytology Expansion: Friendly Animals (#97003) ## About The Pull Request Rabbits, stoats, MOTHROACHES, sheeps, foxes, lizards and penguins now available to growing in cytology growing vats. NnZZdOrDjy FGiWTac5Vb x7mzPe0ZrX 8Cjf0jaHwb fhkVbI41Za DaPyceFpMw 9mLIp829V1 dreamseeker_Ziqdm1F0vZ dreamseeker_p98n5ynxWo ## Why It's Good For The Game Alternative way to obtain creatures without spending large ammout money in cargo. Good interactions with genetic infusion. And more usage of cytology in common. ## Changelog :cl: add: Rabbits, stoats, MOTHROACHES, sheeps, foxes, lizards and penguins now available to growing in cytology growing vats. /:cl: --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/__DEFINES/research.dm | 9 +- code/_globalvars/lists/xenobiology.dm | 14 ++ .../mob/living/basic/farm_animals/rabbit.dm | 1 + .../mob/living/basic/farm_animals/sheep.dm | 1 + code/modules/mob/living/basic/pets/fox.dm | 1 + .../mob/living/basic/pets/penguin/penguin.dm | 1 + code/modules/mob/living/basic/stoats/stoat.dm | 1 + .../modules/mob/living/basic/vermin/lizard.dm | 1 + .../basic/vermin/mothroach/mothroach.dm | 1 + .../vatgrowing/samples/cell_lines/common.dm | 150 ++++++++++++++++++ 10 files changed, 179 insertions(+), 1 deletion(-) diff --git a/code/__DEFINES/research.dm b/code/__DEFINES/research.dm index f2be6fef6dac..b6708c7568c3 100644 --- a/code/__DEFINES/research.dm +++ b/code/__DEFINES/research.dm @@ -48,24 +48,31 @@ #define CELL_LINE_TABLE_GRAPE "cell_line_grape_table" #define CELL_LINE_TABLE_HAUBEROACH "cell_line_hauberoach_table" #define CELL_LINE_TABLE_MEGACARP "cell_line_megacarp_table" +#define CELL_LINE_TABLE_PENGUIN "cell_line_penguin_table" #define CELL_LINE_TABLE_MOUSE "cell_line_mouse_table" #define CELL_LINE_TABLE_PINE "cell_line_pine_table" #define CELL_LINE_TABLE_PUG "cell_line_pug_table" +#define CELL_LINE_TABLE_FOX "cell_line_fox_table" +#define CELL_LINE_TABLE_STOAT "cell_line_stoat_table" +#define CELL_LINE_TABLE_RABBIT "cell_line_rabbit_table" +#define CELL_LINE_TABLE_MOTHROACH "cell_line_mothroach_table" #define CELL_LINE_TABLE_SLIME "cell_line_slime_table" #define CELL_LINE_TABLE_SNAKE "cell_line_snake_table" +#define CELL_LINE_TABLE_LIZARD "cell_line_lizard_table" #define CELL_LINE_TABLE_SNAIL "cell_line_snail_table" #define CELL_LINE_TABLE_VATBEAST "cell_line_vatbeast_table" #define CELL_LINE_TABLE_NETHER "cell_line_nether_table" #define CELL_LINE_TABLE_GLUTTON "cell_line_glutton_table" #define CELL_LINE_TABLE_CLOWNANA "cell_line_clownana_table" #define CELL_LINE_TABLE_LONGFACE "cell_line_longface_table" -#define CELL_LINE_TABLE_FROG "cell_line_frog_table" +#define CELL_LINE_TABLE_FROG "cell_line_frog_table" #define CELL_LINE_TABLE_AXOLOTL "cell_line_axolotl_table" #define CELL_LINE_TABLE_WALKING_MUSHROOM "cell_line_walking_mushroom_table" #define CELL_LINE_TABLE_QUEEN_BEE "cell_line_bee_queen_table" #define CELL_LINE_TABLE_BUTTERFLY "cell_line_butterfly_table" #define CELL_LINE_TABLE_MEGA_ARACHNID "cell_line_table_mega_arachnid" #define CELL_LINE_TABLE_GOAT "cell_line_table_goat" +#define CELL_LINE_TABLE_SHEEP "cell_line_table_SHEEP" #define CELL_LINE_TABLE_PIG "cell_line_table_pig" //! Biopsy cell line organ types diff --git a/code/_globalvars/lists/xenobiology.dm b/code/_globalvars/lists/xenobiology.dm index 69f8e079ad4a..4a07c40dacdd 100644 --- a/code/_globalvars/lists/xenobiology.dm +++ b/code/_globalvars/lists/xenobiology.dm @@ -11,6 +11,8 @@ GLOBAL_LIST_INIT_TYPED(cell_line_tables, /list, list( /datum/micro_organism/cell_line/sholean_grapes = 2, /datum/micro_organism/cell_line/slime = 2, /datum/micro_organism/cell_line/goat = 1, + /datum/micro_organism/cell_line/sheep = 1, + /datum/micro_organism/cell_line/lizard = 1, ), CELL_LINE_TABLE_MOIST = list( @@ -22,6 +24,9 @@ GLOBAL_LIST_INIT_TYPED(cell_line_tables, /list, list( /datum/micro_organism/cell_line/snake = 1, /datum/micro_organism/cell_line/glockroach = 1, /datum/micro_organism/cell_line/hauberoach = 1, + /datum/micro_organism/cell_line/stoat = 1, + /datum/micro_organism/cell_line/mothroach = 1, + /datum/micro_organism/cell_line/penguin = 1, ), CELL_LINE_TABLE_BLOB = list( @@ -39,6 +44,8 @@ GLOBAL_LIST_INIT_TYPED(cell_line_tables, /list, list( /datum/micro_organism/cell_line/slime = 1, /datum/micro_organism/cell_line/vat_beast = 2, /datum/micro_organism/cell_line/pig = 1, + /datum/micro_organism/cell_line/fox = 1, + /datum/micro_organism/cell_line/rabbit = 1, ), CELL_LINE_TABLE_BEAR = list(/datum/micro_organism/cell_line/bear = 1), @@ -56,11 +63,17 @@ GLOBAL_LIST_INIT_TYPED(cell_line_tables, /list, list( CELL_LINE_TABLE_GRAPE = list(/datum/micro_organism/cell_line/sholean_grapes = 1), CELL_LINE_TABLE_HAUBEROACH = list(/datum/micro_organism/cell_line/hauberoach = 1), CELL_LINE_TABLE_MEGACARP = list(/datum/micro_organism/cell_line/megacarp = 1), + CELL_LINE_TABLE_PENGUIN = list(/datum/micro_organism/cell_line/penguin = 1), CELL_LINE_TABLE_MOUSE = list(/datum/micro_organism/cell_line/mouse = 1), CELL_LINE_TABLE_PINE = list(/datum/micro_organism/cell_line/pine = 1), CELL_LINE_TABLE_PUG = list(/datum/micro_organism/cell_line/pug = 1), + CELL_LINE_TABLE_FOX = list(/datum/micro_organism/cell_line/fox = 1), + CELL_LINE_TABLE_STOAT = list(/datum/micro_organism/cell_line/stoat = 1), + CELL_LINE_TABLE_RABBIT = list(/datum/micro_organism/cell_line/rabbit = 1), + CELL_LINE_TABLE_MOTHROACH = list(/datum/micro_organism/cell_line/mothroach = 1), CELL_LINE_TABLE_SLIME = list(/datum/micro_organism/cell_line/slime = 1), CELL_LINE_TABLE_SNAKE = list(/datum/micro_organism/cell_line/snake = 1), + CELL_LINE_TABLE_LIZARD = list(/datum/micro_organism/cell_line/lizard = 1), CELL_LINE_TABLE_SNAIL = list(/datum/micro_organism/cell_line/snail = 1), CELL_LINE_TABLE_VATBEAST = list(/datum/micro_organism/cell_line/vat_beast = 1), CELL_LINE_TABLE_NETHER = list(/datum/micro_organism/cell_line/netherworld = 1), @@ -80,6 +93,7 @@ GLOBAL_LIST_INIT_TYPED(cell_line_tables, /list, list( CELL_LINE_TABLE_BUTTERFLY = list(/datum/micro_organism/cell_line/butterfly = 1), CELL_LINE_TABLE_MEGA_ARACHNID = list(/datum/micro_organism/cell_line/mega_arachnid = 1), CELL_LINE_TABLE_GOAT = list(/datum/micro_organism/cell_line/goat = 1), + CELL_LINE_TABLE_SHEEP = list(/datum/micro_organism/cell_line/sheep = 1), CELL_LINE_TABLE_PIG = list(/datum/micro_organism/cell_line/pig = 1), CELL_LINE_TABLE_ALGAE = list( /datum/micro_organism/cell_line/frog = 2, diff --git a/code/modules/mob/living/basic/farm_animals/rabbit.dm b/code/modules/mob/living/basic/farm_animals/rabbit.dm index c9433bbe923c..76d630000c0d 100644 --- a/code/modules/mob/living/basic/farm_animals/rabbit.dm +++ b/code/modules/mob/living/basic/farm_animals/rabbit.dm @@ -55,6 +55,7 @@ AddElement(/datum/element/pet_bonus, "hop") AddElement(/datum/element/animal_variety, icon_prefix, pick("brown", "black", "white"), TRUE) AddElement(/datum/element/can_be_held) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_RABBIT, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) if(prob(20)) // bunny name = "bunny" diff --git a/code/modules/mob/living/basic/farm_animals/sheep.dm b/code/modules/mob/living/basic/farm_animals/sheep.dm index 3b02f1517068..64da53715800 100644 --- a/code/modules/mob/living/basic/farm_animals/sheep.dm +++ b/code/modules/mob/living/basic/farm_animals/sheep.dm @@ -43,6 +43,7 @@ item_harvest_sound = 'sound/items/handling/surgery/scalpel1.ogg', \ ) AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_SHEEP, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) RegisterSignal(src, COMSIG_LIVING_CULT_SACRIFICED, PROC_REF(on_sacrificed)) /mob/living/basic/sheep/update_overlays() diff --git a/code/modules/mob/living/basic/pets/fox.dm b/code/modules/mob/living/basic/pets/fox.dm index 1e1e25f378a9..1e982d473f27 100644 --- a/code/modules/mob/living/basic/pets/fox.dm +++ b/code/modules/mob/living/basic/pets/fox.dm @@ -58,6 +58,7 @@ AddElement(/datum/element/tiny_mob_hunter, MOB_SIZE_SMALL) AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/can_be_held) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_FOX, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) /datum/ai_controller/basic_controller/fox blackboard = list( diff --git a/code/modules/mob/living/basic/pets/penguin/penguin.dm b/code/modules/mob/living/basic/pets/penguin/penguin.dm index d462c344c8c4..c69942aed326 100644 --- a/code/modules/mob/living/basic/pets/penguin/penguin.dm +++ b/code/modules/mob/living/basic/pets/penguin/penguin.dm @@ -38,6 +38,7 @@ AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/ai_flee_while_injured) AddElement(/datum/element/pet_bonus, "honk") + AddElement(/datum/element/swabable, CELL_LINE_TABLE_PENGUIN, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) AddComponent(/datum/component/profound_fisher) AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) diff --git a/code/modules/mob/living/basic/stoats/stoat.dm b/code/modules/mob/living/basic/stoats/stoat.dm index d9730e4c4f50..3f71e5730fe5 100644 --- a/code/modules/mob/living/basic/stoats/stoat.dm +++ b/code/modules/mob/living/basic/stoats/stoat.dm @@ -48,6 +48,7 @@ AddElement(/datum/element/wears_collar) AddElement(/datum/element/can_be_held) AddComponent(/datum/component/obeys_commands, pet_commands) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_STOAT, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) if(can_breed) add_breeding_component() diff --git a/code/modules/mob/living/basic/vermin/lizard.dm b/code/modules/mob/living/basic/vermin/lizard.dm index 38311a4c73d5..c8d0a40a6b35 100644 --- a/code/modules/mob/living/basic/vermin/lizard.dm +++ b/code/modules/mob/living/basic/vermin/lizard.dm @@ -60,6 +60,7 @@ ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) AddElement(/datum/element/pet_bonus, "tongue") AddElement(/datum/element/can_be_held) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_LIZARD, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) AddElement(/datum/element/basic_eating, heal_amt = 5, food_types = edibles) ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(edibles)) diff --git a/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm b/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm index 1e70c723a740..2a64aa285a18 100644 --- a/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm +++ b/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm @@ -60,6 +60,7 @@ AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/pet_bonus, "squeak") AddElement(/datum/element/can_be_held) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOTHROACH, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) add_verb(src, /mob/living/proc/toggle_resting) ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) diff --git a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm index f70cedc652de..f07f629de0fe 100644 --- a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm +++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm @@ -112,6 +112,28 @@ virus_suspectibility = 1 resulting_atom = /mob/living/basic/goat +/datum/micro_organism/cell_line/sheep + desc = "Ovis cells" + required_reagents = list( + /datum/reagent/consumable/nutriment/protein, + /datum/reagent/consumable/nutriment, + ) + + supplementary_reagents = list( + /datum/reagent/growthserum = 3, + /datum/reagent/consumable/nutriment/vitamin = 1, + /datum/reagent/barbers_aid = 4, + ) + + suppressive_reagents = list( + /datum/reagent/toxin = -1, + /datum/reagent/baldium = -3, + /datum/reagent/consumable/sugar = -2, + ) + + virus_suspectibility = 1 + resulting_atom = /mob/living/basic/sheep + /datum/micro_organism/cell_line/pig desc = "Porcine stem cells" required_reagents = list( @@ -191,6 +213,92 @@ virus_suspectibility = 3 resulting_atom = /mob/living/basic/pet/dog/pug +/datum/micro_organism/cell_line/fox + desc = "Vulpes cells" + required_reagents = list( + /datum/reagent/consumable/nutriment/protein, + /datum/reagent/consumable/liquidgibs, + ) + + supplementary_reagents = list( + /datum/reagent/growthserum = 3, + /datum/reagent/consumable/nutriment/vitamin = 3, + ) + + suppressive_reagents = list( + /datum/reagent/consumable/garlic = -2, + /datum/reagent/consumable/coco = -2, + /datum/reagent/consumable/corn_syrup = -2, + /datum/reagent/consumable/flour = -3, + ) + + virus_suspectibility = 1 + resulting_atom = /mob/living/basic/pet/fox + +/datum/micro_organism/cell_line/stoat + desc = "Ermine cells" + required_reagents = list( + /datum/reagent/consumable/nutriment/protein, + /datum/reagent/consumable/liquidgibs, + ) + + supplementary_reagents = list( + /datum/reagent/consumable/eggyolk = 3, + /datum/reagent/consumable/eggwhite = 2, + /datum/reagent/growthserum = 2, + /datum/reagent/consumable/nutriment/vitamin = 1, + ) + + suppressive_reagents = list( + /datum/reagent/consumable/garlic = -2, + /datum/reagent/consumable/coco = -2, + /datum/reagent/consumable/corn_syrup = -2, + ) + + virus_suspectibility = 2 + resulting_atom = /mob/living/basic/stoat + +/datum/micro_organism/cell_line/rabbit + desc = "Lepus cells" + required_reagents = list(/datum/reagent/consumable/nutriment/protein) + + supplementary_reagents = list( + /datum/reagent/growthserum = 2, + /datum/reagent/consumable/nutriment = 1, + /datum/reagent/blood = 2, // we really need a Holy Grenade with this one + /datum/reagent/consumable/nutriment/vitamin = 1, + ) + + suppressive_reagents = list( + /datum/reagent/consumable/garlic = -2, + /datum/reagent/consumable/tomatojuice = -1, + /datum/reagent/consumable/potato_juice = -1, + /datum/reagent/consumable/lemonjuice = -1, + /datum/reagent/consumable/limejuice = -1, + ) + + virus_suspectibility = 5 + resulting_atom = /mob/living/basic/rabbit + +/datum/micro_organism/cell_line/mothroach + desc = "Lepidoptera cells" + required_reagents = list(/datum/reagent/consumable/nutriment/protein) + + supplementary_reagents = list( + /datum/reagent/cellulose = 4, + /datum/reagent/consumable/nutriment = 1, + /datum/reagent/consumable/nutriment/vitamin = 1, + ) + + suppressive_reagents = list( + /datum/reagent/toxin/pestkiller = -2, + /datum/reagent/consumable/poisonberryjuice = -4, + /datum/reagent/consumable/ethanol/bug_spray = -4, + ) + + virus_suspectibility = 1 + resulting_atom = /mob/living/basic/mothroach + /datum/micro_organism/cell_line/bear //bears can't really compete directly with more powerful creatures, so i made it possible to grow them real fast. desc = "Ursine cells" required_reagents = list( @@ -251,6 +359,25 @@ virus_suspectibility = 1 resulting_atom = /mob/living/basic/carp/mega +/datum/micro_organism/cell_line/penguin + desc = "Spheniscidae cells" + required_reagents = list( + /datum/reagent/consumable/nutriment/protein) + + supplementary_reagents = list( + /datum/reagent/water/salt = 2, + /datum/reagent/consumable/nutriment/vitamin = 2, + /datum/reagent/medicine/strange_reagent/fishy_reagent = 10, + ) + + suppressive_reagents = list( + /datum/reagent/toxin/bungotoxin = -6, + /datum/reagent/carbondioxide = -3, + ) + + virus_suspectibility = 1 + resulting_atom = /mob/living/basic/pet/penguin/baby + /datum/micro_organism/cell_line/snake desc = "Ophidic cells" required_reagents = list( @@ -269,6 +396,29 @@ resulting_atom = /mob/living/basic/snake +/datum/micro_organism/cell_line/lizard + desc = "Squamata cells" + required_reagents = list(/datum/reagent/consumable/nutriment/protein) + + supplementary_reagents = list( + /datum/reagent/growthserum = 2, + /datum/reagent/consumable/liquidgibs = 2, + /datum/reagent/consumable/nutriment = 1, + /datum/reagent/consumable/nutriment/vitamin = 1, // Mostly similar to mouse supplementary reagents. + /datum/reagent/consumable/nutriment/fat/oil = 2, + /datum/reagent/consumable/rice = 1, + /datum/reagent/consumable/eggyolk = 1, + ) + + suppressive_reagents = list( + /datum/reagent/consumable/corn_syrup = -6, + /datum/reagent/consumable/sugar = -3, + /datum/reagent/consumable/salt = -3, + /datum/reagent/consumable/tomatojuice = -1, + /datum/reagent/sulfur = -3, //sulfur repels lizards too. + ) + + resulting_atom = /mob/living/basic/lizard /////////////////////////////////////////// /// SLIMES, OOZES & BLOBS /// From 696031418d2b020bcdaa937db9d7c1118c51346c Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:15:17 +0000 Subject: [PATCH 052/445] Automatic changelog for PR #97003 [ci skip] --- html/changelogs/AutoChangeLog-pr-97003.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97003.yml diff --git a/html/changelogs/AutoChangeLog-pr-97003.yml b/html/changelogs/AutoChangeLog-pr-97003.yml new file mode 100644 index 000000000000..ea33ad8d1b73 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97003.yml @@ -0,0 +1,4 @@ +author: "nPocToDark" +delete-after: True +changes: + - rscadd: "Rabbits, stoats, MOTHROACHES, sheeps, foxes, lizards and penguins now available to growing in cytology growing vats." \ No newline at end of file From 3eb0e60c4dedb21d6887b2826163871cc278e1c0 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:00:21 +0000 Subject: [PATCH 053/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-96923.yml | 4 --- html/changelogs/AutoChangeLog-pr-96987.yml | 5 ---- html/changelogs/AutoChangeLog-pr-96999.yml | 5 ---- html/changelogs/AutoChangeLog-pr-97003.yml | 4 --- html/changelogs/AutoChangeLog-pr-97011.yml | 4 --- html/changelogs/AutoChangeLog-pr-97016.yml | 5 ---- html/changelogs/AutoChangeLog-pr-97017.yml | 4 --- html/changelogs/AutoChangeLog-pr-97023.yml | 5 ---- html/changelogs/AutoChangeLog-pr-97030.yml | 4 --- html/changelogs/AutoChangeLog-pr-97034.yml | 4 --- html/changelogs/AutoChangeLog-pr-97037.yml | 4 --- html/changelogs/archive/2026-07.yml | 33 ++++++++++++++++++++++ 12 files changed, 33 insertions(+), 48 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-96923.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96987.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96999.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97003.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97011.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97016.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97017.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97023.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97030.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97034.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97037.yml diff --git a/html/changelogs/AutoChangeLog-pr-96923.yml b/html/changelogs/AutoChangeLog-pr-96923.yml deleted file mode 100644 index 4e6d4dc4f7d1..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96923.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - qol: "The botanical chemical dispenser (as seen on the lifebringer ship) is now also capable of dispensing Enduro Grow and Liquid Earthquake." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96987.yml b/html/changelogs/AutoChangeLog-pr-96987.yml deleted file mode 100644 index 6a887dc79369..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96987.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Fghj240" -delete-after: True -changes: - - spellcheck: "changed \"possible cure\" to \"cure\" in healthscans to show they can't be wrong" - - qol: "Health analyzer has hover text for disease curing strategies" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96999.yml b/html/changelogs/AutoChangeLog-pr-96999.yml deleted file mode 100644 index 2e0c9d27bf32..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96999.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "vinylspiders" -delete-after: True -changes: - - bugfix: "species quirk incompatibilities will now update immediately without the need to add/remove a quirk to force a ui update" - - code_imp: "quirks backend improved, it will no longer send full updates constantly and be faster" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97003.yml b/html/changelogs/AutoChangeLog-pr-97003.yml deleted file mode 100644 index ea33ad8d1b73..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97003.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "nPocToDark" -delete-after: True -changes: - - rscadd: "Rabbits, stoats, MOTHROACHES, sheeps, foxes, lizards and penguins now available to growing in cytology growing vats." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97011.yml b/html/changelogs/AutoChangeLog-pr-97011.yml deleted file mode 100644 index e21d04045324..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97011.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "QuiteLiterallyAnything" -delete-after: True -changes: - - bugfix: "Broken mirrors no longer produce an intact mirror upon deconstruction." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97016.yml b/html/changelogs/AutoChangeLog-pr-97016.yml deleted file mode 100644 index 00488eaab14d..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97016.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "vinylspiders" -delete-after: True -changes: - - bugfix: "fixed an issue that would cause preview icons to randomly become corrupt or invisible if they met certain criteria" - - admin: "select equipment now just shows one direction of previews, greatly reducing lag" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97017.yml b/html/changelogs/AutoChangeLog-pr-97017.yml deleted file mode 100644 index 98b2d07fbb0c..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97017.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Kashargul" -delete-after: True -changes: - - bugfix: "biome install being skipped after a dependency update" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97023.yml b/html/changelogs/AutoChangeLog-pr-97023.yml deleted file mode 100644 index e568edee023a..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97023.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - qol: "Watcher gaze visuals no longer flicker if you turn around after the ability has already been activated and is finishing the animation" - - bugfix: "Fixed watcher gaze runtimes" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97030.yml b/html/changelogs/AutoChangeLog-pr-97030.yml deleted file mode 100644 index 9d19e1ed1936..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97030.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "TheRyeGuyWhoWillNowDie" -delete-after: True -changes: - - bugfix: "kilo genetics now has gene disks and a flesh reshaper" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97034.yml b/html/changelogs/AutoChangeLog-pr-97034.yml deleted file mode 100644 index 86e4f60fa135..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97034.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "CabinetOnFire" -delete-after: True -changes: - - bugfix: "Repairbots fix windows and other things reliably again" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97037.yml b/html/changelogs/AutoChangeLog-pr-97037.yml deleted file mode 100644 index 8b331824b71d..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97037.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "CabinetOnFire" -delete-after: True -changes: - - bugfix: "Some looping sounds no longer runtime when stopping/starting" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index 8db99dbe5e8b..d78f123a180d 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -574,3 +574,36 @@ nPocToDark: - rscadd: Curse of Ra is back. Now you can buy some pharaon costumes in DeusVend (premium) +2026-07-19: + CabinetOnFire: + - bugfix: Some looping sounds no longer runtime when stopping/starting + - bugfix: Repairbots fix windows and other things reliably again + Fghj240: + - spellcheck: changed "possible cure" to "cure" in healthscans to show they can't + be wrong + - qol: Health analyzer has hover text for disease curing strategies + Hatterhat: + - qol: The botanical chemical dispenser (as seen on the lifebringer ship) is now + also capable of dispensing Enduro Grow and Liquid Earthquake. + Kashargul: + - bugfix: biome install being skipped after a dependency update + QuiteLiterallyAnything: + - bugfix: Broken mirrors no longer produce an intact mirror upon deconstruction. + SmArtKar: + - qol: Watcher gaze visuals no longer flicker if you turn around after the ability + has already been activated and is finishing the animation + - bugfix: Fixed watcher gaze runtimes + TheRyeGuyWhoWillNowDie: + - bugfix: kilo genetics now has gene disks and a flesh reshaper + nPocToDark: + - rscadd: Rabbits, stoats, MOTHROACHES, sheeps, foxes, lizards and penguins now + available to growing in cytology growing vats. + vinylspiders: + - bugfix: species quirk incompatibilities will now update immediately without the + need to add/remove a quirk to force a ui update + - code_imp: quirks backend improved, it will no longer send full updates constantly + and be faster + - bugfix: fixed an issue that would cause preview icons to randomly become corrupt + or invisible if they met certain criteria + - admin: select equipment now just shows one direction of previews, greatly reducing + lag From 65f906ca3273dc5e816d901a747c779cca96758d Mon Sep 17 00:00:00 2001 From: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:37:33 -0500 Subject: [PATCH 054/445] Recache jukebox lists when admins add music (#97029) ## About The Pull Request Rebuild the cache when admins add music to the jukebox ## Why It's Good For The Game Slightly sloppy way to do it but the usecase is like 1 every ??? rounds so It does not need to be optimized.. ## Changelog :cl: admin: Adding music as an admin rebuilds the list THAT round instead of having to wait for the next /:cl: --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/datums/components/jukebox.dm | 4 ++-- code/modules/admin/verbs/jukebox_moderation.dm | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/code/datums/components/jukebox.dm b/code/datums/components/jukebox.dm index 07004dd05b36..b66adc23e88d 100644 --- a/code/datums/components/jukebox.dm +++ b/code/datums/components/jukebox.dm @@ -91,9 +91,9 @@ return load_songs_from_config() /// Loads the config sounds once, and returns a copy of them. -/datum/jukebox/proc/load_songs_from_config() +/datum/jukebox/proc/load_songs_from_config(force = FALSE) var/static/list/config_songs - if(isnull(config_songs)) + if(isnull(config_songs) || force) config_songs = list() var/list/tracks = flist(CONFIG_JUKEBOX_SOUNDS) for(var/track_file in tracks) diff --git a/code/modules/admin/verbs/jukebox_moderation.dm b/code/modules/admin/verbs/jukebox_moderation.dm index 4b7bf35594c5..23fd8118d67a 100644 --- a/code/modules/admin/verbs/jukebox_moderation.dm +++ b/code/modules/admin/verbs/jukebox_moderation.dm @@ -25,6 +25,7 @@ ADMIN_VERB(upload_jukebox_music, R_SERVER, "Jukebox Upload Music", "Upload a val message_admins("[key_name_admin(user)] uploaded [clean_name] to the jukebox!") to_chat(user, span_notice("Successfully uploaded [clean_name]!")) + refresh_jukebox_songs() ADMIN_VERB(browse_jukebox_music, R_SERVER, "Jukebox Browse Music", "Browse music files for moderation.", ADMIN_CATEGORY_SERVER) var/list/files = flist(CONFIG_JUKEBOX_SOUNDS) @@ -51,7 +52,18 @@ ADMIN_VERB(browse_jukebox_music, R_SERVER, "Jukebox Browse Music", "Browse music message_admins(msg) log_admin(msg) SSblackbox.record_feedback("associative", "jukebox_deletion", 1, list("round_id" = "[GLOB.round_id]", "deletor" = "[key_name_admin(user)]", "deleted" = "[choice]")) + refresh_jukebox_songs() if ("Download") user << ftp(file(path)) else return + +/// Recache the songs from config then force replace all the songs. +/proc/refresh_jukebox_songs() + var/refreshed = FALSE + for(var/obj/machinery/jukebox/jukebox as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/jukebox)) + if(!refreshed) + jukebox.music_player.load_songs_from_config(TRUE) + refreshed = TRUE + + jukebox.music_player.songs = jukebox.music_player.init_songs() From 79733652f04a1cfd63f0f1969792399128b4359d Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 22:37:51 +0000 Subject: [PATCH 055/445] Automatic changelog for PR #97029 [ci skip] --- html/changelogs/AutoChangeLog-pr-97029.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97029.yml diff --git a/html/changelogs/AutoChangeLog-pr-97029.yml b/html/changelogs/AutoChangeLog-pr-97029.yml new file mode 100644 index 000000000000..e846a5e21e55 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97029.yml @@ -0,0 +1,4 @@ +author: "FalloutFalcon" +delete-after: True +changes: + - admin: "Adding music as an admin rebuilds the list THAT round instead of having to wait for the next" \ No newline at end of file From 1733ef94dc81da2ebf02b5356dc34c6f91739311 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:52:15 -0500 Subject: [PATCH 056/445] Delete empty file (#97043) --- code/datums/ai/monkey/monkey_subtrees.dm | 1 - tgstation.dme | 1 - 2 files changed, 2 deletions(-) delete mode 100644 code/datums/ai/monkey/monkey_subtrees.dm diff --git a/code/datums/ai/monkey/monkey_subtrees.dm b/code/datums/ai/monkey/monkey_subtrees.dm deleted file mode 100644 index 8b137891791f..000000000000 --- a/code/datums/ai/monkey/monkey_subtrees.dm +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tgstation.dme b/tgstation.dme index ce36566e5100..1abcd92bd34d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1188,7 +1188,6 @@ #include "code\datums\ai\hauntium\haunted_controller.dm" #include "code\datums\ai\monkey\monkey_bt_nodes.dm" #include "code\datums\ai\monkey\monkey_controller.dm" -#include "code\datums\ai\monkey\monkey_subtrees.dm" #include "code\datums\ai\movement\_ai_movement.dm" #include "code\datums\ai\movement\ai_movement_basic_avoidance.dm" #include "code\datums\ai\movement\ai_movement_complete_stop.dm" From 7047e52ffea2356ef52d3e88485e1d5a88c439d3 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:52:49 -0400 Subject: [PATCH 057/445] Fixes an eye-related runtime (#97044) ## About The Pull Request `code/modules/surgery/organs/internal/eyes/_eyes.dm, line 570: invalid object type 0:0 proc name: blink (/obj/item/organ/eyes/proc/blink)` -Bug eyes can have blink_animation = FALSE which is supposed to prevent blinking, and it does for some sources. But a number of things call `blink()` without first checking this var (like the emote for instance). Just put a guard in the proc itself, it's super cheap. ## Why It's Good For The Game Just fixes an oversight ## Changelog Not player-facing because even bugged it ultimately results in not blinking, just more quietly with this fix. --- code/modules/surgery/organs/internal/eyes/_eyes.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/modules/surgery/organs/internal/eyes/_eyes.dm b/code/modules/surgery/organs/internal/eyes/_eyes.dm index dfa710cd4fe2..ea00f31a5159 100644 --- a/code/modules/surgery/organs/internal/eyes/_eyes.dm +++ b/code/modules/surgery/organs/internal/eyes/_eyes.dm @@ -532,6 +532,8 @@ animate(time = wait_time) /obj/item/organ/eyes/proc/blink(duration = BLINK_DURATION, restart_animation = TRUE) + if(!blink_animation) + return var/left_delayed = prob(50) // Storing blink delay so mistimed blinks of lizards don't get cut short var/sync_blinking = synchronized_blinking && (owner.get_organ_loss(ORGAN_SLOT_BRAIN) < BRAIN_DAMAGE_ASYNC_BLINKING) From abdc340b3c6e744ff4e3375b69d73ea52a65f09f Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:53:42 -0400 Subject: [PATCH 058/445] Fixes an immerse runtime (#97046) ## About The Pull Request ``` usr: someone) usr.loc: (start area (8,248,1)) src: null call stack: stack trace("addtrait immersed overridden. ...", "code/datums/signals.dm", 43) the hot spring (243,186,6) (/turf/open/water/hot_spring): RegisterSignal(the fence (/obj/structure/fence), "addtrait immersed", "dip_in", 0) the hot spring (243,186,6) (/turf/open/water/hot_spring): enter hot spring(the fence (/obj/structure/fence)) the hot spring (243,186,6) (/turf/open/water/hot_spring): on atom inited(the hot spring (243,186,6) (/turf/open/water/hot_spring), the fence (/obj/structure/fence), 1) the hot spring (243,186,6) (/turf/open/water/hot_spring): SendSignal("atom_init_success_on", /list (/list)) Atoms (/datum/controller/subsystem/atoms): InitAtom(the fence (/obj/structure/fence), 1, /list (/list)) Atoms (/datum/controller/subsystem/atoms): CreateAtoms(/list (/list), null, "subsystem init 60") Atoms (/datum/controller/subsystem/atoms): InitializeAtoms(/list (/list), null) Away Mission (/datum/map_template): initTemplateBounds(/list (/list)) Away Mission (/datum/map_template): load new z(0) Load Away Mission (/datum/admin_verb/load_away_mission): avd do verb(someone (/client)) Admin Verbs (/datum/controller/subsystem/admin_verbs): dynamic invoke verb(someone (/client), /datum/admin_verb/load_away_mi... (/datum/admin_verb/load_away_mission)) Darkinite (/client): Load Away Mission() ``` ## Changelog Not player facing, just removes a lot of runtime spam --- code/game/turfs/open/water.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/game/turfs/open/water.dm b/code/game/turfs/open/water.dm index 21fe6b5b01b6..f7f863eecc06 100644 --- a/code/game/turfs/open/water.dm +++ b/code/game/turfs/open/water.dm @@ -254,6 +254,8 @@ . = ..() if(!(flags_1 & INITIALIZED_1)) return + if(!(arrived.flags_1 & INITIALIZED_1)) // If arrived hasn't finished its own Initialize() yet (e.g. during its own creation during mapload), on_atom_inited() will already call enter_hot_spring() for it once + return enter_hot_spring(arrived) /turf/open/water/hot_spring/on_atom_inited(datum/source, atom/movable/movable) From 4cbe3dd01c15474c0706d0b5ba5b652bd85890e3 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:54:44 -0400 Subject: [PATCH 059/445] Fix for blood decal hard deletes take 2 (#97028) ## About The Pull Request They're still hard deleting. The only thing I can imagine would be left causing this, is changeTurf() shenanigans. Let's see if this fixes the issue maybe? Also found a similar source of issue in the salt decal. Fixes https://github.com/tgstation/tgstation/issues/96310 Fixes https://github.com/tgstation/tgstation/issues/96288 Fixes https://github.com/tgstation/tgstation/issues/96190 Fixes https://github.com/tgstation/tgstation/issues/96310 ## Changelog Not player-facing --- code/game/objects/effects/decals/cleanable/blood.dm | 11 +++++++---- code/game/objects/effects/decals/cleanable/food.dm | 11 ++++++++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/code/game/objects/effects/decals/cleanable/blood.dm b/code/game/objects/effects/decals/cleanable/blood.dm index b44e40f37810..f4514db7c4d5 100644 --- a/code/game/objects/effects/decals/cleanable/blood.dm +++ b/code/game/objects/effects/decals/cleanable/blood.dm @@ -8,6 +8,10 @@ clean_type = CLEAN_TYPE_BLOOD color = BLOOD_COLOR_RED + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered) + ) + /// Amount of blood, in units, in this decal /// Spent when drying or making footprints var/bloodiness = BLOOD_AMOUNT_PER_DECAL @@ -64,16 +68,15 @@ total_dry_time = drying_time START_PROCESSING(SSblood_drying, src) - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(on_entered) - ) AddElement(/datum/element/connect_loc, loc_connections) if (bloodiness || GET_ATOM_BLOOD_DECAL_LENGTH(src)) update_appearance() -/obj/effect/decal/cleanable/blood/Destroy() +/obj/effect/decal/cleanable/blood/Destroy(force) STOP_PROCESSING(SSblood_drying, src) + // connect_loc only unregisters via COMSIG_MOVABLE_MOVED, which never fires when the turf we're on gets replaced by ChangeTurf() + RemoveElement(/datum/element/connect_loc, loc_connections) return ..() /// Returns the default blood type for this decal for maploaded decals diff --git a/code/game/objects/effects/decals/cleanable/food.dm b/code/game/objects/effects/decals/cleanable/food.dm index 243b10dfebb7..0618eec98dae 100644 --- a/code/game/objects/effects/decals/cleanable/food.dm +++ b/code/game/objects/effects/decals/cleanable/food.dm @@ -33,14 +33,19 @@ desc = "A sizable pile of table salt. Someone must be upset." icon_state = "salt_pile" var/safepasses = 3 //how many times can this salt pile be passed before dissipating - -/obj/effect/decal/cleanable/food/salt/Initialize(mapload, list/datum/disease/diseases) - . = ..() var/static/list/loc_connections = list( COMSIG_ATOM_ENTERED = PROC_REF(on_entered) ) + +/obj/effect/decal/cleanable/food/salt/Initialize(mapload, list/datum/disease/diseases) + . = ..() AddElement(/datum/element/connect_loc, loc_connections) +/obj/effect/decal/cleanable/food/salt/Destroy(force) + // connect_loc only unregisters via COMSIG_MOVABLE_MOVED, which never fires when the turf we're on gets replaced by ChangeTurf() + RemoveElement(/datum/element/connect_loc, loc_connections) + return ..() + /obj/effect/decal/cleanable/food/salt/CanAllowThrough(atom/movable/mover, border_dir) . = ..() if(is_species(mover, /datum/species/snail)) From 5855d43c0db0f29966a17f84f1f41028ffcfc36c Mon Sep 17 00:00:00 2001 From: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com> Date: Mon, 20 Jul 2026 02:00:20 +0300 Subject: [PATCH 060/445] Fixes blastoff misspell (#97049) ## About The Pull Request closes #97048 also idk if thats intentional or no though image ## Changelog :cl: spellcheck: Fixed blastoff misspelling. /:cl: --- code/modules/reagents/chemistry/reagents/drug_reagents.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 912d068cf021..01bb4b3f8e3e 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -583,7 +583,7 @@ psychonaut.apply_status_effect(/datum/status_effect/tower_of_babel) /datum/reagent/drug/blastoff - name = "bLaStOoF" + name = "bLaSToFF" description = "A drug for the hardcore party crowd said to enhance one's abilities on the dance floor.\nMost old heads refuse to touch this stuff, perhaps because memories of the luna discotheque incident are seared into their brains." color = "#9015a9" taste_description = "holodisk cleaner" From 69ca4647f9ebd0257cd7afd9b4f5da8339137688 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 23:00:38 +0000 Subject: [PATCH 061/445] Automatic changelog for PR #97049 [ci skip] --- html/changelogs/AutoChangeLog-pr-97049.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97049.yml diff --git a/html/changelogs/AutoChangeLog-pr-97049.yml b/html/changelogs/AutoChangeLog-pr-97049.yml new file mode 100644 index 000000000000..3cc6ca6301e7 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97049.yml @@ -0,0 +1,4 @@ +author: "Aliceee2ch" +delete-after: True +changes: + - spellcheck: "Fixed blastoff misspelling." \ No newline at end of file From 90e8cad9a0bd6b8949fa8a13f7b8eed3071994cf Mon Sep 17 00:00:00 2001 From: PapaMichael Date: Sun, 19 Jul 2026 19:02:26 -0400 Subject: [PATCH 062/445] Fixes a bunch of pronoun/verb agreement (#97052) ## About The Pull Request Fixed a bunch of cases where a verb wasn't properly being modified for its pronoun. Also discovered and fixed fishing examine spans starting lowercase ## Why It's Good For The Game Grammar being right is good ## Changelog :cl: PapaMichael spellcheck: More verbs match their pronouns /:cl: --- code/_onclick/hud/screen_objects/alert.dm | 2 +- code/datums/components/riding/riding.dm | 2 +- code/datums/elements/art.dm | 6 +++--- .../mood_events/generic_positive_events.dm | 4 ++-- .../status_effects/debuffs/heart_attack.dm | 2 +- code/datums/status_effects/drug_effects.dm | 2 +- code/game/objects/items/devices/flashlight.dm | 2 +- code/game/objects/items/emags.dm | 4 ++-- code/game/objects/items/stacks/medical.dm | 2 +- .../objects/items/weaponry/melee/chainsaw.dm | 2 +- code/modules/antagonists/brother/brother.dm | 2 +- .../antagonists/heretic/magic/flesh_surgery.dm | 6 +++--- .../components/unary_devices/airlock_pump.dm | 4 ++-- .../cargo/markets/market_items/hostages.dm | 2 +- code/modules/fishing/fish/_fish.dm | 18 +++++++++--------- code/modules/fishing/fishing_equipment.dm | 2 +- .../mob/living/basic/farm_animals/rabbit.dm | 2 +- .../modules/mob/living/basic/pets/dog/corgi.dm | 2 +- code/modules/projectiles/gun.dm | 2 +- code/modules/projectiles/guns/magic/wand.dm | 4 ++-- .../religion/dreams/dream_projection.dm | 2 +- .../operations/operation_plastic_surgery.dm | 4 ++-- .../surgery/operations/operation_revival.dm | 6 +++--- code/modules/vehicles/vehicle_key.dm | 2 +- 24 files changed, 43 insertions(+), 43 deletions(-) diff --git a/code/_onclick/hud/screen_objects/alert.dm b/code/_onclick/hud/screen_objects/alert.dm index 3f2654abb259..7954f783c10d 100644 --- a/code/_onclick/hud/screen_objects/alert.dm +++ b/code/_onclick/hud/screen_objects/alert.dm @@ -557,7 +557,7 @@ SIGNAL_HANDLER if(QDELETED(offer.offered_item)) - examine_list += span_warning("[source]'s arm appears tensed up, as if [source.p_they()] plan on pulling it back suddenly...") + examine_list += span_warning("[source]'s arm appears tensed up, as if [source.p_they()] plan[source.p_s()] on pulling it back suddenly...") /atom/movable/screen/alert/give/hand screentip_override_text = "Take Hand" diff --git a/code/datums/components/riding/riding.dm b/code/datums/components/riding/riding.dm index 5b094e65e57b..482ac97ffe0c 100644 --- a/code/datums/components/riding/riding.dm +++ b/code/datums/components/riding/riding.dm @@ -399,7 +399,7 @@ return rider.visible_message( - span_warning("As [disarmer] shoves [rider] around, [rider.p_they()] lose [rider.p_their()] [pick("balance", "grip", "hold")] and fall off [parent]!"), + span_warning("As [disarmer] shoves [rider] around, [rider.p_they()] lose[rider.p_s()] [rider.p_their()] [pick("balance", "grip", "hold")] and fall[rider.p_s()] off [parent]!"), span_warning("As [disarmer] shoves you around, you lose your [pick("balance", "grip", "hold")] and fall off [parent]!") ) if(rider.is_blind()) diff --git a/code/datums/elements/art.dm b/code/datums/elements/art.dm index 93e156081d9d..1bc5bcc197c1 100644 --- a/code/datums/elements/art.dm +++ b/code/datums/elements/art.dm @@ -30,7 +30,7 @@ msg = "[source.p_Theyre()] fair to middling, enough to be called an \"art object\"." if (0 to BAD_ART) user.add_mood_event("artbad", /datum/mood_event/artbad) - msg = "Wow, [source.p_they()] sucks." + msg = "Wow, [source.p_they()] suck[source.p_s()]." user.visible_message(span_notice("[user] stops and looks intently at [source]."), \ span_notice("You appraise [source]... [msg]")) @@ -60,7 +60,7 @@ msg = "What \a [pick("masterpiece", "chef-d'oeuvre")] [source.p_theyre()]. So [pick("subversive", "revolutionary", "unitizing", "egalitarian")]!" else user.add_mood_event("artbad", /datum/mood_event/artbad) - msg = "Wow, [source.p_they()] sucks." + msg = "Wow, [source.p_they()] suck[source.p_s()]." user.visible_message(span_notice("[user] stops to inspect [source]."), \ span_notice("You appraise [source], inspecting the fine craftsmanship of the proletariat... [msg]")) @@ -84,7 +84,7 @@ msg = "What \a [pick("masterpiece", "chef-d'oeuvre")] [source.p_theyre()]. So [pick("relatable", "down to earth", "true", "real")]!" else user.add_mood_event("artbad", /datum/mood_event/artbad) - msg = "Wow, [source.p_they()] sucks." + msg = "Wow, [source.p_they()] suck[source.p_s()]." user.visible_message(span_notice("[user] stops to inspect [source]."), \ span_notice("You appraise [source], inspecting the fine craftsmanship of the proletariat... [msg]")) diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm index e72a6446bfe1..6027b961679f 100644 --- a/code/datums/mood_events/generic_positive_events.dm +++ b/code/datums/mood_events/generic_positive_events.dm @@ -58,7 +58,7 @@ description = "[friend.name] is way too nice for this station." return - description = "[friend.name] is great to be around, [friend.p_they()] makes me feel so happy!" + description = "[friend.name] is great to be around, [friend.p_they()] make[friend.p_s()] me feel so happy!" /datum/mood_event/warmhug description = "Warm cozy hugs are the best!" @@ -544,7 +544,7 @@ if(!morbid) description = "It felt nice to pet \the [fish]." else - description = "I caress \the [fish] as [fish.p_they()] squirms under my touch, blissfully unaware of how cruel this world is." + description = "I caress \the [fish] as [fish.p_they()] squirm[fish.p_s()] under my touch, blissfully unaware of how cruel this world is." /datum/mood_event/kobun description = "You are all loved by the Universe. I’m not alone, and you aren’t either." diff --git a/code/datums/status_effects/debuffs/heart_attack.dm b/code/datums/status_effects/debuffs/heart_attack.dm index 592162ea0f89..eded21621cd0 100644 --- a/code/datums/status_effects/debuffs/heart_attack.dm +++ b/code/datums/status_effects/debuffs/heart_attack.dm @@ -126,7 +126,7 @@ return var/mob/living/carbon/human/human_owner = owner var/cannot_grasp = (human_owner.usable_hands <= 0 || owner.incapacitated & INCAPABLE_RESTRAINTS || HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED)) - return span_warning("[owner.p_they()] looks to be doubling over" + "[cannot_grasp ? " in pain!" : ", clutching [owner.p_their()] chest in pain!"]") + return span_warning("[owner.p_they()] look[owner.p_s()] to be doubling over" +"[cannot_grasp ? " in pain!" : ", clutching [owner.p_their()] chest in pain!"]") /datum/status_effect/heart_attack/on_remove() REMOVE_TRAIT(owner, TRAIT_DISEASELIKE_SEVERITY_HIGH, type) diff --git a/code/datums/status_effects/drug_effects.dm b/code/datums/status_effects/drug_effects.dm index 70184a4a123e..ac418899c604 100644 --- a/code/datums/status_effects/drug_effects.dm +++ b/code/datums/status_effects/drug_effects.dm @@ -53,7 +53,7 @@ duration = amplitude owner.set_jitter_if_lower(100 SECONDS) owner.Paralyze(duration) - owner.visible_message(span_warning("[owner] drops to the ground as [owner.p_they()] start seizing up."), \ + owner.visible_message(span_warning("[owner] drops to the ground as [owner.p_they()] start[owner.p_s()] seizing up."), \ span_warning("[pick("You can't collect your thoughts...", "You suddenly feel extremely dizzy...", "You can't think straight...","You can't move your face properly anymore...")]")) return TRUE diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index b5d6ee151a13..dc6628cb9b95 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -1021,7 +1021,7 @@ return SHAME var/obj/item/organ/eyes/eyes = user.get_organ_slot(ORGAN_SLOT_EYES) if(!eyes) - user.visible_message(span_suicide("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but [user.p_they()] don't have any!")) + user.visible_message(span_suicide("[user] is trying to squirt [src]'s fluids into [user.p_their()] eyes... but [user.p_they()] [user.p_do()]n't have any!")) return SHAME user.visible_message(span_suicide("[user] is squirting [src]'s fluids into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!")) burn_loop(get_fuel()) diff --git a/code/game/objects/items/emags.dm b/code/game/objects/items/emags.dm index 96e5ca19d2f7..385a4a30bef2 100644 --- a/code/game/objects/items/emags.dm +++ b/code/game/objects/items/emags.dm @@ -40,8 +40,8 @@ user.visible_message( span_notice("[user] holds [emag_card] to [src], getting the two cards stuck together!"), - span_notice("As you hold [emag_card] to [src], [emag_card.p_their()] magnets attract to one another, \ - and [emag_card.p_they()] become stuck together!"), + span_notice("As you hold [emag_card] to [src], their magnets attract to one another, \ + and they become stuck together!"), visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, ) playsound(src, 'sound/effects/bang.ogg', 33, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index d39b70ccf3d8..e8c28793b2d2 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -635,7 +635,7 @@ return list(/datum/reagent/medicine/c2/lenturi = 10) /obj/item/stack/medical/ointment/suicide_act(mob/living/user) - user.visible_message(span_suicide("[user] is squeezing [src] into [user.p_their()] mouth! [user.p_do(TRUE)]n't [user.p_they()] know that stuff is toxic?")) + user.visible_message(span_suicide("[user] is squeezing [src] into [user.p_their()] mouth! [capitalize(user.p_do())]n't [user.p_they()] know that stuff is toxic?")) return TOXLOSS /obj/item/stack/medical/mesh diff --git a/code/game/objects/items/weaponry/melee/chainsaw.dm b/code/game/objects/items/weaponry/melee/chainsaw.dm index 55e30d63d157..770fbdd721a8 100644 --- a/code/game/objects/items/weaponry/melee/chainsaw.dm +++ b/code/game/objects/items/weaponry/melee/chainsaw.dm @@ -98,7 +98,7 @@ user.visible_message(span_suicide("[user] begins to tear [user.p_their()] head off with [src]! It looks like [user.p_theyre()] trying to commit suicide!")) var/obj/item/bodypart/head/myhead = user.get_bodypart(BODY_ZONE_HEAD) if(!myhead) - visible_message(span_suicide("[user] realises that [user.p_they()] cannot cut off [user.p_their()] head because [user.p_they()] don't have one!")) + visible_message(span_suicide("[user] realises that [user.p_they()] cannot cut off [user.p_their()] head because [user.p_they()] [user.p_do()]n't have one!")) return SHAME playsound(src, 'sound/items/weapons/chainsawhit.ogg', 100, TRUE) diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 298a51ef40f5..b86e6437ca25 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -94,7 +94,7 @@ return if (HAS_MIND_TRAIT(flashed, TRAIT_UNCONVERTABLE)) - flashed.balloon_alert(source, "[flashed.p_they()] resist!") + flashed.balloon_alert(source, "[flashed.p_they()] resist[flashed.p_s()]!") return if (!team.add_brother(flashed, key_name(source))) // Shouldn't happen given the former, more specific checks but just in case diff --git a/code/modules/antagonists/heretic/magic/flesh_surgery.dm b/code/modules/antagonists/heretic/magic/flesh_surgery.dm index c50b9c81e12d..6f20537d0c80 100644 --- a/code/modules/antagonists/heretic/magic/flesh_surgery.dm +++ b/code/modules/antagonists/heretic/magic/flesh_surgery.dm @@ -168,7 +168,7 @@ new /obj/effect/temp_visual/cult/sparks(get_turf(to_heal)) var/condition = (to_heal.damage > 0) ? "better" : "perfect" caster.visible_message( - span_warning("[caster]'s hand glows a brilliant red as [caster.p_they()] restore \the [to_heal] to [condition] condition!"), + span_warning("[caster]'s hand glows a brilliant red as [caster.p_they()] restore[caster.p_s()] \the [to_heal] to [condition] condition!"), span_notice("Your hand glows a brilliant red as you restore \the [to_heal] to [condition] condition!"), ) @@ -323,12 +323,12 @@ if(using_on_self) caster.visible_message( - span_danger("[caster]'s hand glows a brilliant red as [caster.p_they()] begin[caster.p_es()] forcing [inserted_organ] into [caster.p_their()] [zone_organ_goes_in]!!"), + span_danger("[caster]'s hand glows a brilliant red as [caster.p_they()] begin[caster.p_s()] forcing [inserted_organ] into [caster.p_their()] [zone_organ_goes_in]!!"), span_userdanger("You begin forcing [inserted_organ] into your [zone_organ_goes_in]!") ) else caster.visible_message( - span_danger("[caster]'s hand glows a brilliant red as [caster.p_they()] begin[caster.p_es()] forcing [inserted_organ] into [victim]'s [zone_organ_goes_in]!!"), + span_danger("[caster]'s hand glows a brilliant red as [caster.p_they()] begin[caster.p_s()] forcing [inserted_organ] into [victim]'s [zone_organ_goes_in]!!"), span_notice("You begin forcing [inserted_organ] into [victim]'s [zone_organ_goes_in].") ) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm index daac7d513819..ddcb66267a9e 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm @@ -605,7 +605,7 @@ // unbolt all the doors but don't open them for(var/obj/machinery/door/airlock/airlock as anything in (internal_airlocks + external_airlocks)) airlock.unbolt() - audible_message(span_notice("[src] whirrs as [p_they()] loses power, disengaging airlock bolts.")) + audible_message(span_notice("[src] whirrs as [p_they()] lose[p_s()] power, disengaging airlock bolts.")) deltimer(emergency_stop_timer) set_on(FALSE) @@ -616,7 +616,7 @@ for(var/obj/machinery/door/airlock/airlock as anything in internal_airlocks) if(open_airlock_on_cycle) INVOKE_ASYNC(airlock, TYPE_PROC_REF(/obj/machinery/door/airlock, secure_open)) - audible_message(span_notice("[src] whirrs as [p_they()] regains power, re-engaging airlock bolts.")) + audible_message(span_notice("[src] whirrs as [p_they()] regain[p_s()] power, re-engaging airlock bolts.")) /obj/machinery/atmospherics/components/unary/airlock_pump/unbolt_only open_airlock_on_cycle = FALSE diff --git a/code/modules/cargo/markets/market_items/hostages.dm b/code/modules/cargo/markets/market_items/hostages.dm index 04bd070e6b01..cfda52415afa 100644 --- a/code/modules/cargo/markets/market_items/hostages.dm +++ b/code/modules/cargo/markets/market_items/hostages.dm @@ -25,7 +25,7 @@ )) desc += " DISCLAIMER: The offer will expire once the creature is returned to the station." if(humie_mob) - desc += "[mob.p_they(TRUE)] may be delivered handcuffed, for safety of course." + desc += "[mob.p_They()] may be delivered handcuffed, for safety of course." price = new_price RegisterSignal(mob, COMSIG_LIVING_RETURN_FROM_CAPTURE, PROC_REF(on_return_from_capture)) diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index 605e88323e32..08eadf59f47d 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -552,25 +552,25 @@ GLOBAL_LIST_INIT(fish_compatible_fluid_types, list( . += span_boldnicegreen("Caught by [catcher_name] on [catch_date].") if(HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISH) || HAS_TRAIT(loc, TRAIT_EXAMINE_FISH)) - . += span_notice("[p_theyre(TRUE)] [size] cm long.") - . += span_notice("[p_they(TRUE)] weighs [weight] [span_tooltip("the standard unit of measurement for space age fish", "kiloclam")].") + . += span_notice("[p_Theyre()] [size] cm long.") + . += span_notice("[p_They()] weigh[p_s()] [weight] [span_tooltip("the standard unit of measurement for space age fish", "kiloclam")].") if(HAS_TRAIT(src, TRAIT_FISH_GENEGUNNED)) - . += span_warning("[p_theyve(TRUE)] been edited by a fish genegun. [p_they(TRUE)]'ll die if edited again.") + . += span_warning("[p_Theyve()] been edited by a fish genegun. [p_They()]'ll die if edited again.") . += get_health_warnings(user, always_deep = FALSE) if(HAS_TRAIT(src, TRAIT_FISHING_BAIT)) - . += span_smallnoticeital("[p_they(TRUE)] can be used as a fishing bait.") + . += span_smallnoticeital("[p_They()] can be used as a fishing bait.") if(bites_amount) - . += span_warning("[p_theyve(TRUE)] been bitten by someone.") + . += span_warning("[p_Theyve()] been bitten by someone.") /obj/item/fish/proc/get_health_warnings(mob/user, always_deep = FALSE) if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_DEEPER_FISH) && !always_deep) return if(status == FISH_DEAD) - return span_deadsay("[p_theyre(TRUE)] [HAS_MIND_TRAIT(user, TRAIT_NAIVE) ? "taking the big snooze" : "dead"].") + return span_deadsay("[p_Theyre()] [HAS_MIND_TRAIT(user, TRAIT_NAIVE) ? "taking the big snooze" : "dead"].") var/list/warnings = list() if(get_starvation_mult()) @@ -590,7 +590,7 @@ GLOBAL_LIST_INIT(fish_compatible_fluid_types, list( warnings += "mostly healthy" if(length(warnings)) - . += span_warning("[p_theyre(TRUE)] [english_list(warnings)].") + . += span_warning("[p_Theyre()] [english_list(warnings)].") return . @@ -1114,7 +1114,7 @@ GLOBAL_LIST_INIT(fish_compatible_fluid_types, list( balloon_alert(user, "invalid creature!") return if(status != FISH_DEAD) - balloon_alert(user, "[p_theyre(TRUE)] not dead!") + balloon_alert(user, "[p_theyre()] not dead!") return set_status(FISH_ALIVE) injector.expend(src, user) @@ -1580,7 +1580,7 @@ GLOBAL_LIST_INIT(fish_compatible_fluid_types, list( if(in_aquarium) to_chat(user, span_warning("[src] runs away from your finger as you dip it into the water!")) else - to_chat(user, span_warning("You try to pet [src] but [p_they()] squirms away!")) + to_chat(user, span_warning("You try to pet [src] but [p_they()] squirm[p_s()] away!")) return FALSE if(HAS_TRAIT(src, TRAIT_FISH_ELECTROGENESIS) && GET_FISH_ELECTROGENESIS(src) > 15 MEGA JOULES) user.electrocute_act(5, src) //was it all worth it? diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm index fba2cecc9124..75f7e304fad6 100644 --- a/code/modules/fishing/fishing_equipment.dm +++ b/code/modules/fishing/fishing_equipment.dm @@ -884,7 +884,7 @@ /obj/item/fish_gene/proc/inject_into_fish(obj/item/fish/fish, mob/living/user, obj/item/tool = src) var/datum/fish_trait/trait = GLOB.fish_traits[trait_type] if(!trait.apply_to_fish(fish, initial = FALSE)) - to_chat(user, span_warning("You can't inject the \"[trait_type::name]\" trait into [fish]. [fish.p_they(TRUE)] either [fish.p_have()] it or [fish.p_are()] incompatible with it.")) + to_chat(user, span_warning("You can't inject the \"[trait_type::name]\" trait into [fish]. [fish.p_They()] either [fish.p_have()] it or [fish.p_are()] incompatible with it.")) return ITEM_INTERACT_BLOCKING user.visible_message(span_notice("[user] injects [fish] with [tool]."), span_notice("You inject the \"[trait_type::name]\" trait into [fish].")) qdel(src) diff --git a/code/modules/mob/living/basic/farm_animals/rabbit.dm b/code/modules/mob/living/basic/farm_animals/rabbit.dm index 76d630000c0d..75caf1a6991c 100644 --- a/code/modules/mob/living/basic/farm_animals/rabbit.dm +++ b/code/modules/mob/living/basic/farm_animals/rabbit.dm @@ -90,7 +90,7 @@ . = ..() //passed to the egg_layer component as how many eggs it gets when it eats something. var/eggs_added_from_eating = rand(1, 4) - var/list/feed_messages = list("[p_they()] nibbles happily.", "[p_they()] noms happily.") + var/list/feed_messages = list("[p_they()] nibble[p_s()] happily.", "[p_they()] nom[p_s()] happily.") AddComponent(/datum/component/egg_layer,\ /obj/item/surprise_egg,\ list(/obj/item/food/grown/carrotlike/carrot),\ diff --git a/code/modules/mob/living/basic/pets/dog/corgi.dm b/code/modules/mob/living/basic/pets/dog/corgi.dm index 4109d22e6797..0296d6f7d992 100644 --- a/code/modules/mob/living/basic/pets/dog/corgi.dm +++ b/code/modules/mob/living/basic/pets/dog/corgi.dm @@ -115,7 +115,7 @@ /mob/living/basic/pet/dog/corgi/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) if(istype(attacking_item, /obj/item/razor)) if(shaved) - to_chat(user, span_warning("You can't shave this corgi, [p_they()] has already been shaved!")) + to_chat(user, span_warning("You can't shave this corgi, [p_they()] [p_have()] already been shaved!")) return if(!can_be_shaved) to_chat(user, span_warning("You can't shave this corgi, [p_they()] [p_do()]n't have a fur coat!")) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 51b6c3df9a57..f0c4e3c4057e 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -199,7 +199,7 @@ if(pin.pin_removable) . += span_info("[pin] looks like [pin.p_they()] could be removed with some tools.") else - . += span_info("[pin] looks like [pin.p_theyre()] firmly locked in, [pin.p_they()] looks impossible to remove.") + . += span_info("[pin] looks like [pin.p_theyre()] firmly locked in, [pin.p_they()] look[pin.p_s()] impossible to remove.") else . += "It doesn't have a firing pin installed, and won't fire." diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm index c2334b5c4c53..31d51d7b89ee 100644 --- a/code/modules/projectiles/guns/magic/wand.dm +++ b/code/modules/projectiles/guns/magic/wand.dm @@ -96,7 +96,7 @@ . = ..() if (user.stat == DEAD) return MANUAL_SUICIDE - user.visible_message(span_suicide("...but if anything [user.p_they()] look healthier than before.")) + user.visible_message(span_suicide("...but if anything [user.p_they()] look[user.p_s()] healthier than before.")) return SHAME /obj/item/gun/magic/wand/death/debug @@ -140,7 +140,7 @@ . = ..() if (user.stat == DEAD) return MANUAL_SUICIDE - user.visible_message(span_suicide("...but if anything [user.p_they()] look healthier than before.")) + user.visible_message(span_suicide("...but if anything [user.p_they()] look[user.p_s()] healthier than before.")) return SHAME /obj/item/gun/magic/wand/resurrection/debug //for testing diff --git a/code/modules/religion/dreams/dream_projection.dm b/code/modules/religion/dreams/dream_projection.dm index a08327b3e35c..22ff71b75916 100644 --- a/code/modules/religion/dreams/dream_projection.dm +++ b/code/modules/religion/dreams/dream_projection.dm @@ -70,7 +70,7 @@ return ..() /datum/status_effect/dream_projection/get_examine_text() - return "[owner.p_They()] are in a deep slumber, yet [owner.p_their()] eyes show a distant look, as if [owner.p_they()] are somewhere far away..." + return "[owner.p_They()] [owner.p_are()] in a deep slumber, yet [owner.p_their()] eyes show a distant look, as if [owner.p_they()] [owner.p_are()] somewhere far away..." /datum/status_effect/dream_projection/on_apply() if(!owner.SetSleeping(20 SECONDS)) diff --git a/code/modules/surgery/operations/operation_plastic_surgery.dm b/code/modules/surgery/operations/operation_plastic_surgery.dm index 1203702d1d61..63b130ff6c31 100644 --- a/code/modules/surgery/operations/operation_plastic_surgery.dm +++ b/code/modules/surgery/operations/operation_plastic_surgery.dm @@ -92,8 +92,8 @@ display_results( surgeon, limb.owner, - span_notice("You alter [oldname]'s appearance completely, [limb.owner.p_they()] is now [operation_args[OPERATION_NEW_NAME]]."), - span_notice("[surgeon] alters [oldname]'s appearance completely, [limb.owner.p_they()] is now [operation_args[OPERATION_NEW_NAME]]!"), + span_notice("You alter [oldname]'s appearance completely, [limb.owner.p_they()] [limb.owner.p_are()] now [operation_args[OPERATION_NEW_NAME]]."), + span_notice("[surgeon] alters [oldname]'s appearance completely, [limb.owner.p_they()] [limb.owner.p_are()] now [operation_args[OPERATION_NEW_NAME]]!"), span_notice("[surgeon] finishes the operation on [limb.owner ? "[limb.owner]'s face." : limb]."), ) display_pain(limb.owner, "The pain fades, your face feels new and unfamiliar!") diff --git a/code/modules/surgery/operations/operation_revival.dm b/code/modules/surgery/operations/operation_revival.dm index ea6808d8e443..6a9dec572586 100644 --- a/code/modules/surgery/operations/operation_revival.dm +++ b/code/modules/surgery/operations/operation_revival.dm @@ -103,9 +103,9 @@ display_results( surgeon, patient, - span_warning("You shock [patient]'s brain with [tool], but [patient.p_they()] don't react."), - span_warning("[surgeon] shocks [patient]'s brain with [tool], but [patient.p_they()] don't react."), - span_warning("[surgeon] shocks [patient]'s brain with [tool], but [patient.p_they()] don't react."), + span_warning("You shock [patient]'s brain with [tool], but [patient.p_they()] [patient.p_do()]n't react."), + span_warning("[surgeon] shocks [patient]'s brain with [tool], but [patient.p_they()] [patient.p_do()]n't react."), + span_warning("[surgeon] shocks [patient]'s brain with [tool], but [patient.p_they()] [patient.p_do()]n't react."), ) /datum/surgery_operation/basic/revival/mechanic diff --git a/code/modules/vehicles/vehicle_key.dm b/code/modules/vehicles/vehicle_key.dm index 7bc162c77ef7..e2572ea48af6 100644 --- a/code/modules/vehicles/vehicle_key.dm +++ b/code/modules/vehicles/vehicle_key.dm @@ -75,7 +75,7 @@ /obj/item/key/proc/manual_suicide(mob/living/user) if(user) user.remove_atom_colour(ADMIN_COLOUR_PRIORITY) - user.visible_message(span_suicide("[user] forgot [user.p_they()] isn't actually a janicart! That's a paddlin'!")) + user.visible_message(span_suicide("[user] forgot [user.p_they()] [user.p_are()]n't actually a janicart! That's a paddlin'!")) if(user.mind?.get_skill_level(/datum/skill/cleaning) >= SKILL_LEVEL_LEGENDARY) //Janny janny janny janny janny playsound(src, 'sound/effects/adminhelp.ogg', 50, TRUE, -1) user.adjust_oxy_loss(200) From 448dd13a1fad9ea74cd398f1467dfe68010bf165 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 23:02:44 +0000 Subject: [PATCH 063/445] Automatic changelog for PR #97052 [ci skip] --- html/changelogs/AutoChangeLog-pr-97052.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97052.yml diff --git a/html/changelogs/AutoChangeLog-pr-97052.yml b/html/changelogs/AutoChangeLog-pr-97052.yml new file mode 100644 index 000000000000..7b88ad53ffa9 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97052.yml @@ -0,0 +1,4 @@ +author: "PapaMichael" +delete-after: True +changes: + - spellcheck: "More verbs match their pronouns" \ No newline at end of file From 0512a8433fc71260628a09a69625d32436f4b43d Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 06:01:22 +0000 Subject: [PATCH 064/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97029.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97049.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97052.yml | 4 ---- html/changelogs/archive/2026-07.yml | 8 ++++++++ 4 files changed, 8 insertions(+), 12 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97029.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97049.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97052.yml diff --git a/html/changelogs/AutoChangeLog-pr-97029.yml b/html/changelogs/AutoChangeLog-pr-97029.yml deleted file mode 100644 index e846a5e21e55..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97029.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - admin: "Adding music as an admin rebuilds the list THAT round instead of having to wait for the next" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97049.yml b/html/changelogs/AutoChangeLog-pr-97049.yml deleted file mode 100644 index 3cc6ca6301e7..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97049.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Aliceee2ch" -delete-after: True -changes: - - spellcheck: "Fixed blastoff misspelling." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97052.yml b/html/changelogs/AutoChangeLog-pr-97052.yml deleted file mode 100644 index 7b88ad53ffa9..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97052.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "PapaMichael" -delete-after: True -changes: - - spellcheck: "More verbs match their pronouns" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index d78f123a180d..7c54a4f768b7 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -607,3 +607,11 @@ or invisible if they met certain criteria - admin: select equipment now just shows one direction of previews, greatly reducing lag +2026-07-20: + Aliceee2ch: + - spellcheck: Fixed blastoff misspelling. + FalloutFalcon: + - admin: Adding music as an admin rebuilds the list THAT round instead of having + to wait for the next + PapaMichael: + - spellcheck: More verbs match their pronouns From d62a8d47915c35daa0557b4e0b02ff8a5bd6d7a9 Mon Sep 17 00:00:00 2001 From: Roxy <75404941+TealSeer@users.noreply.github.com> Date: Mon, 20 Jul 2026 08:44:08 -0500 Subject: [PATCH 065/445] Fix runtime in monkey controllers (#97069) ## About The Pull Request Required argument wasn't being passed to the proc ## Why It's Good For The Game ``` RUNTIME: runtime error: Cannot read null.blackboard - proc name: is valid target (/datum/targeting_strategy/monkey_weapon_upgrade/is_valid_target) - source file: code/datums/ai/monkey/monkey_bt_nodes.dm,135 - usr: null - src: /datum/targeting_strategy/monk... (/datum/targeting_strategy/monkey_weapon_upgrade) - call stack: - /datum/targeting_strategy/monk... (/datum/targeting_strategy/monkey_weapon_upgrade): is valid target(John Doe (768) (/mob/living/carbon/human/consistent), the Makarov pistol (/obj/item/gun/ballistic/automatic/pistol), 12, null) - /datum/targeting_strategy/monk... (/datum/targeting_strategy/monkey_weapon_upgrade): can keep target(John Doe (768) (/mob/living/carbon/human/consistent), the Makarov pistol (/obj/item/gun/ballistic/automatic/pistol), 12, null) - /datum/bt_node/ai_behavior/acq... (/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon): on no candidates(/datum/ai_controller/monkey (/datum/ai_controller/monkey), the Makarov pistol (/obj/item/gun/ballistic/automatic/pistol), /datum/targeting_strategy/monk... (/datum/targeting_strategy/monkey_weapon_upgrade), 5) - /datum/bt_node/ai_behavior/acq... (/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon): find and set target(/datum/ai_controller/monkey (/datum/ai_controller/monkey), /datum/targeting_strategy/monk... (/datum/targeting_strategy/monkey_weapon_upgrade), 5) - /datum/bt_node/ai_behavior/acq... (/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon): perform(0.25, /datum/ai_controller/monkey (/datum/ai_controller/monkey)) - /datum/bt_node/ai_behavior/acq... (/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon): tick(/datum/ai_controller/monkey (/datum/ai_controller/monkey), 0.25) - /datum/bt_node/composite/paral... (/datum/bt_node/composite/parallel): tick(/datum/ai_controller/monkey (/datum/ai_controller/monkey), 0.25) - /datum/bt_node/decorator/bb_ke... (/datum/bt_node/decorator/bb_key_set): tick(/datum/ai_controller/monkey (/datum/ai_controller/monkey), 0.25) - /datum/bt_node/subtree/monkey_... (/datum/bt_node/subtree/monkey_combat): tick(/datum/ai_controller/monkey (/datum/ai_controller/monkey), 0.25) - /datum/bt_node/composite/selec... (/datum/bt_node/composite/selector): tick(/datum/ai_controller/monkey (/datum/ai_controller/monkey), 0.25) - /datum/bt_node/composite/paral... (/datum/bt_node/composite/parallel): tick(/datum/ai_controller/monkey (/datum/ai_controller/monkey), 0.25) - /datum/ai_controller/monkey (/datum/ai_controller/monkey): SelectBehaviors(0.25) ``` ## Changelog N/A --- code/datums/ai/generic_behaviors/acquire_target.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/datums/ai/generic_behaviors/acquire_target.dm b/code/datums/ai/generic_behaviors/acquire_target.dm index 73b1608f13db..c32c8c0a979c 100644 --- a/code/datums/ai/generic_behaviors/acquire_target.dm +++ b/code/datums/ai/generic_behaviors/acquire_target.dm @@ -130,7 +130,7 @@ /datum/bt_node/ai_behavior/acquire_target/proc/on_no_candidates(datum/ai_controller/controller, atom/current_target, datum/targeting_strategy/strategy, range) if(!target_loss_distance || !current_target) return list() - if(strategy.can_keep_target(controller.pawn, current_target, target_loss_distance)) + if(strategy.can_keep_target(controller.pawn, current_target, target_loss_distance, controller)) return list(current_target) return list() From d9c3c15b08455ec79d86f7e44daaae8efb0d881b Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:45:41 +0200 Subject: [PATCH 066/445] Fixes the Four Hands wizard perk (#97061) ## About The Pull Request Closes #96989 Our support for extra hands is awful but other maints want to keep it ## Changelog :cl: fix: Fixed the Four Hands wizard perk /:cl: --- code/_onclick/hud/hud.dm | 3 +++ code/_onclick/hud/screen_objects/screen_objects.dm | 3 +++ .../wizard/equipment/spellbook_entries/perks.dm | 7 ++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 4a033d4c201d..8d736120534b 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -474,6 +474,9 @@ GLOBAL_LIST_INIT(available_ui_styles, list( /// Rebuilds our mob's hand slot screen elements /datum/hud/proc/build_hand_slots(update_hud = FALSE) + // Clean up existing hand slot objects + for (var/atom/movable/screen/inventory/hand/hand in screen_groups[HUD_GROUP_STATIC]) + remove_screen_object(hand, FALSE) for(var/i in 1 to length(mymob.held_items)) var/atom/movable/screen/inventory/hand/hand_box = add_screen_object(/atom/movable/screen/inventory/hand, HUD_KEY_HAND_SLOT(i), HUD_GROUP_STATIC, ui_style, ui_hand_position(i)) diff --git a/code/_onclick/hud/screen_objects/screen_objects.dm b/code/_onclick/hud/screen_objects/screen_objects.dm index 00341f671881..0003d382060e 100644 --- a/code/_onclick/hud/screen_objects/screen_objects.dm +++ b/code/_onclick/hud/screen_objects/screen_objects.dm @@ -974,6 +974,9 @@ INITIALIZE_IMMEDIATE(/atom/movable/screen) // I hate this place var/list/current_animated = LAZYLISTDUPLICATE(animated_zones) for(var/part_zone, body_part_untyped in owner.get_bodyparts_by_zones()) + // I hate that we "allow" support for more than 2 hands in the codebase + if(!limbs[part_zone]) + continue var/icon_key = 0 var/obj/item/bodypart/body_part = body_part_untyped var/list/overridable_key = list(icon_key) diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/perks.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/perks.dm index 4d4063f6efda..33f15971c934 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/perks.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/perks.dm @@ -14,15 +14,16 @@ wizard_datum.perks += src RegisterSignal(user, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created)) if (user.hud_used) - on_hud_created() + on_hud_created(user) to_chat(user, span_notice("You got a new perk: [src.name].")) log_purchase(user.key) return TRUE -/datum/spellbook_entry/perks/proc/on_hud_created(mob/living/carbon/human/user, datum/antagonist/wizard/wizard_datum) +/datum/spellbook_entry/perks/proc/on_hud_created(mob/living/carbon/human/source) SIGNAL_HANDLER - var/datum/hud/user_hud = user.hud_used + var/datum/hud/user_hud = source.hud_used + var/datum/antagonist/wizard/wizard_datum = source.mind.has_antag_datum(/datum/antagonist/wizard) if(!user_hud.screen_objects[HUD_WIZARD_COMPACT_PERKS]) user_hud.add_screen_object(/atom/movable/screen/perk/more, HUD_WIZARD_COMPACT_PERKS, HUD_GROUP_INFO, ui_loc = ui_perk_position(0), update_screen = TRUE) From d6c3161a675db86fab93fa985c86094fded1a2c6 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:46:05 +0000 Subject: [PATCH 067/445] Automatic changelog for PR #97061 [ci skip] --- html/changelogs/AutoChangeLog-pr-97061.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97061.yml diff --git a/html/changelogs/AutoChangeLog-pr-97061.yml b/html/changelogs/AutoChangeLog-pr-97061.yml new file mode 100644 index 000000000000..5f1be2301c1c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97061.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed the Four Hands wizard perk" \ No newline at end of file From 84437ffe80e2f4defcfc007916edd706ae269907 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:46:15 +0200 Subject: [PATCH 068/445] Fixes quick tile replacement (#97060) ## About The Pull Request Closes #97057 ## Changelog :cl: fix: Fixed quick tile replacement /:cl: --- code/game/turfs/open/floor.dm | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/code/game/turfs/open/floor.dm b/code/game/turfs/open/floor.dm index 7fe3cb6a267c..b77791d9b6a4 100644 --- a/code/game/turfs/open/floor.dm +++ b/code/game/turfs/open/floor.dm @@ -145,16 +145,15 @@ if(overfloor_placed && pry_tile(I, user)) return TRUE -/turf/open/floor/proc/try_replace_tile(obj/item/stack/tile/T, mob/user, list/modifiers) - if(T.turf_type == type && T.turf_dir == dir) +/turf/open/floor/proc/try_replace_tile(obj/item/stack/tile/tile, mob/user, list/modifiers) + if(tile.turf_type == type && tile.turf_dir == dir) return - var/obj/item/crowbar/CB = user.is_holding_tool_quality(TOOL_CROWBAR) - if(!CB) + var/obj/item/crowbar/crowbar = user.is_holding_tool_quality(TOOL_CROWBAR) + if(!crowbar) return - var/turf/open/floor/plating/P = pry_tile(CB, user, TRUE) - if(!istype(P)) - return - P.attackby(T, user, modifiers) + var/turf/open/floor/plating/plating = pry_tile(crowbar, user, TRUE) + if(istype(plating)) + tile.melee_attack_chain(user, plating, modifiers) /turf/open/floor/proc/pry_tile(obj/item/I, mob/user, silent = FALSE) I.play_tool_sound(src, 80) From ba3e3041ea488b9e204a22e6d6dcbb70e8175585 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:46:36 +0000 Subject: [PATCH 069/445] Automatic changelog for PR #97060 [ci skip] --- html/changelogs/AutoChangeLog-pr-97060.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97060.yml diff --git a/html/changelogs/AutoChangeLog-pr-97060.yml b/html/changelogs/AutoChangeLog-pr-97060.yml new file mode 100644 index 000000000000..d8d04e35a4d8 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97060.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed quick tile replacement" \ No newline at end of file From e1cfe6a19d8b7386daa26c988f607b77dedd81d1 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:00:28 +0000 Subject: [PATCH 070/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97060.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97061.yml | 4 ---- html/changelogs/archive/2026-07.yml | 3 +++ 3 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97060.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97061.yml diff --git a/html/changelogs/AutoChangeLog-pr-97060.yml b/html/changelogs/AutoChangeLog-pr-97060.yml deleted file mode 100644 index d8d04e35a4d8..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97060.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed quick tile replacement" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97061.yml b/html/changelogs/AutoChangeLog-pr-97061.yml deleted file mode 100644 index 5f1be2301c1c..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97061.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed the Four Hands wizard perk" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index 7c54a4f768b7..5822084d2827 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -615,3 +615,6 @@ to wait for the next PapaMichael: - spellcheck: More verbs match their pronouns + SmArtKar: + - bugfix: Fixed the Four Hands wizard perk + - bugfix: Fixed quick tile replacement From 27dedf64bc171c4040126901b95039d3f389356a Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 20 Jul 2026 17:22:18 -0500 Subject: [PATCH 071/445] Add water bottles to booze-o-mat (#97032) ## About The Pull Request This adds the regular-sized (50u) and large-sized (100u) water bottles to the booze-o-mat non-alcoholic category. We also reorganize our list so that the cans, cartons, etc. are next to each other. ## Why It's Good For The Game There are several subtypes of water already available from this vendor (tonic water, soda water), so I was surprised to see it was missing regular bottled water. This was because bottled water had not been added to the game yet when these lists were created. --- code/modules/vending/boozeomat.dm | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/code/modules/vending/boozeomat.dm b/code/modules/vending/boozeomat.dm index a900111d7c70..fbbb9b4d7b4b 100644 --- a/code/modules/vending/boozeomat.dm +++ b/code/modules/vending/boozeomat.dm @@ -45,20 +45,22 @@ "name" = "Non-Alcoholic", "icon" = "bottle-water", "products" = list( - /obj/item/reagent_containers/cup/glass/bottle/hakka_mate = 5, /obj/item/reagent_containers/cup/glass/ice = 10, - /obj/item/reagent_containers/cup/glass/bottle/grenadine = 4, - /obj/item/reagent_containers/cup/soda_cans/melon_soda = 5, + /obj/item/reagent_containers/cup/glass/waterbottle = 8, + /obj/item/reagent_containers/cup/glass/waterbottle/large = 4, /obj/item/reagent_containers/cup/glass/bottle/juice/lemonjuice = 4, /obj/item/reagent_containers/cup/glass/bottle/juice/limejuice = 4, /obj/item/reagent_containers/cup/glass/bottle/juice/orangejuice = 4, - /obj/item/reagent_containers/cup/glass/bottle/juice/menthol = 4, /obj/item/reagent_containers/cup/glass/bottle/juice/cream = 4, /obj/item/reagent_containers/cup/glass/bottle/juice/tomatojuice = 4, + /obj/item/reagent_containers/cup/glass/bottle/grenadine = 4, + /obj/item/reagent_containers/cup/glass/bottle/juice/menthol = 4, + /obj/item/reagent_containers/cup/glass/bottle/hakka_mate = 5, /obj/item/reagent_containers/cup/soda_cans/sodawater = 15, /obj/item/reagent_containers/cup/soda_cans/sol_dry = 8, /obj/item/reagent_containers/cup/soda_cans/cola = 8, /obj/item/reagent_containers/cup/soda_cans/tonic = 8, + /obj/item/reagent_containers/cup/soda_cans/melon_soda = 5, ), ), From 2e5e59a0d1c75ef972bb5f16327ec7df1b62823c Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 22:30:07 +0000 Subject: [PATCH 072/445] Automatic changelog for PR #97032 [ci skip] --- html/changelogs/AutoChangeLog-pr-97032.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97032.yml diff --git a/html/changelogs/AutoChangeLog-pr-97032.yml b/html/changelogs/AutoChangeLog-pr-97032.yml new file mode 100644 index 000000000000..e4b902aa134b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97032.yml @@ -0,0 +1,4 @@ +author: "timothymtorres" +delete-after: True +changes: + - rscadd: "Add water bottles to booze-o-mat non-alcoholic category" \ No newline at end of file From fa1ce461460d95213bfdb3a63cb645a3b02be91c Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:00:23 +0000 Subject: [PATCH 073/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97032.yml | 4 ---- html/changelogs/archive/2026-07.yml | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97032.yml diff --git a/html/changelogs/AutoChangeLog-pr-97032.yml b/html/changelogs/AutoChangeLog-pr-97032.yml deleted file mode 100644 index e4b902aa134b..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97032.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothymtorres" -delete-after: True -changes: - - rscadd: "Add water bottles to booze-o-mat non-alcoholic category" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index 5822084d2827..b0528b51424a 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -618,3 +618,6 @@ SmArtKar: - bugfix: Fixed the Four Hands wizard perk - bugfix: Fixed quick tile replacement +2026-07-21: + timothymtorres: + - rscadd: Add water bottles to booze-o-mat non-alcoholic category From 6b80efc5a494409258ffabc642042a510ad67663 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Tue, 21 Jul 2026 02:50:47 +0200 Subject: [PATCH 074/445] Removes unnecessary click delay from window placement (#97059) ## About The Pull Request Lets players place multiple windows at once again. Closes #97058 Handled in the click chain proper, technically not a bug but construction is a slog and we are all for making it convenient. ## Changelog :cl: fix: Removed unnecessary click delay from window placement /:cl: --- code/game/objects/structures/grille.dm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index 84caad3e7802..a83946392d6e 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -232,7 +232,6 @@ return ITEM_INTERACT_SUCCESS /obj/structure/grille/item_interaction(mob/living/user, obj/item/tool, list/modifiers) - user.changeNext_move(CLICK_CD_MELEE) if(istype(tool, /obj/item/stack/rods) && broken) if(!do_after(user, 1 SECONDS, target = src)) return ITEM_INTERACT_BLOCKING @@ -265,7 +264,7 @@ return ITEM_INTERACT_BLOCKING to_chat(user, span_notice("You start placing the window...")) - if(!do_after(user,20, target = src)) + if(!do_after(user, 2 SECONDS, target = src)) return ITEM_INTERACT_BLOCKING if(!src.loc || !anchored) //Grille broken or unanchored while waiting From f86803f29cc571a3ae75781b689eb3254a256ba6 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:51:05 +0000 Subject: [PATCH 075/445] Automatic changelog for PR #97059 [ci skip] --- html/changelogs/AutoChangeLog-pr-97059.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97059.yml diff --git a/html/changelogs/AutoChangeLog-pr-97059.yml b/html/changelogs/AutoChangeLog-pr-97059.yml new file mode 100644 index 000000000000..e248020b3000 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97059.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Removed unnecessary click delay from window placement" \ No newline at end of file From 392a09595b38c2fd3f94fe95040c641d27d6d295 Mon Sep 17 00:00:00 2001 From: CabinetOnFire Date: Tue, 21 Jul 2026 03:53:12 +0200 Subject: [PATCH 076/445] Fixes cats getting permanent zoomies after being chased away by a superior male (#97072) ## About The Pull Request Cats would flee from a target after losing a turf warf with a male cat, but they'd never stop fleeing the target. I've added a 3 second true_for_time node and made it so true_for_time nodes can count as a success when ending so the clear target node after it gets called. I also made true_for_time always ABORT_SELF by default, and removed it from random_walk as random_walk didnt even use it right and did nothing to begin with (It shouldnt be necesary to have anyways) I originally tried fixing this with an optional decorator, which succeeds even if children fail, but since true_for_time was aborting the behaviors before that did nothing. an optional decorator is still useful though (for example, a sequence where you walk somewhere, do a backflip, and then say something, we might want the backflip to be optional so we still say something if we cant backflip) ## Why It's Good For The Game fix good ## Changelog :cl: fix: Fixes cats getting permanent zoomies after being chased away by a superior male /:cl: --- .../random_walk.bt.compiled.json | 2 +- .../basic/pets/cat/cat.bt.compiled.json | 2 +- code/datums/ai/generic_decorators/optional.dm | 7 +++++ .../ai/generic_decorators/true_for_time.dm | 22 ++++++++++--- .../ai/generic_subtrees/random_walk.bt.json | 31 +++++++------------ .../mob/living/basic/pets/cat/cat.bt.json | 16 +++++++--- tgstation.dme | 1 + 7 files changed, 51 insertions(+), 30 deletions(-) create mode 100644 code/datums/ai/generic_decorators/optional.dm diff --git a/build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json index 82e2292b6d1c..4e754abf9f0a 100644 --- a/build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json +++ b/build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json @@ -1 +1 @@ -{"type":"/datum/bt_node/decorator/true_for_time","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk","walk_chance":"$bf07i8ep"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}],"duration":50,"__bindings":{"bf07i8ep":{"label":"walk_chance","default":25}}} +{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk","walk_chance":"$bf07i8ep"}],"__bindings":{"bf07i8ep":{"label":"walk_chance","default":25}},"success_policy":1,"failure_policy":0,"loop_delay":15.0} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json index b13bced69f26..555b1850e6d0 100644 --- a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json @@ -1 +1 @@ -{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_basic_flee_target"}]}],"key":"BB_basic_flee_target","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/cat_reside_in_home"},{"type":"/datum/bt_node/subtree/cat_haul_food"},{"type":"/datum/bt_node/subtree/cat_hunt_mice"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"tresspasser_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/territorial_struggle","target_key":"tresspasser_target","cries_key":"hostile_meows"}]}],"key":"tresspasser_target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3}],"observer_abort":0,"invert":true,"key":"mouse_target"},{"type":"/datum/bt_node/subtree/cat_find_food"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/cats"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_home","target_source":"/datum/target_source/oview_single_type/cat_house","targeting_strategy":"/datum/targeting_strategy/valid_cat_home"}],"chance":0.05},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/find_cat_tresspasser","target_key":"tresspasser_target"}],"chance":0.05},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mouse_target","target_source":"/datum/target_source/oview_single_type/mouse","targeting_strategy":"/datum/targeting_strategy/huntable_mouse","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_food_target","target_source":"/datum/target_source/oview_typed/from_bb_key/huntable_prey","targeting_strategy":"/datum/targeting_strategy/cat_food","vision_range":9}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/true_for_time","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}}],"duration":30,"succeed_on_timeout":true},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_basic_flee_target"}]}],"key":"BB_basic_flee_target","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/cat_reside_in_home"},{"type":"/datum/bt_node/subtree/cat_haul_food"},{"type":"/datum/bt_node/subtree/cat_hunt_mice"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"tresspasser_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/territorial_struggle","target_key":"tresspasser_target","cries_key":"hostile_meows"}]}],"key":"tresspasser_target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3}],"observer_abort":0,"invert":true,"key":"mouse_target"},{"type":"/datum/bt_node/subtree/cat_find_food"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/cats"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_home","target_source":"/datum/target_source/oview_single_type/cat_house","targeting_strategy":"/datum/targeting_strategy/valid_cat_home"}],"chance":0.05},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/find_cat_tresspasser","target_key":"tresspasser_target"}],"chance":0.05},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mouse_target","target_source":"/datum/target_source/oview_single_type/mouse","targeting_strategy":"/datum/targeting_strategy/huntable_mouse","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_food_target","target_source":"/datum/target_source/oview_typed/from_bb_key/huntable_prey","targeting_strategy":"/datum/targeting_strategy/cat_food","vision_range":9}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/code/datums/ai/generic_decorators/optional.dm b/code/datums/ai/generic_decorators/optional.dm new file mode 100644 index 000000000000..cc04b8547f15 --- /dev/null +++ b/code/datums/ai/generic_decorators/optional.dm @@ -0,0 +1,7 @@ +/// Always reports SUCCESS to the parent, even if the child fails. Lets a sequence continue past a step that's allowed to fail. RUNNING still passes through unchanged. +/datum/bt_node/decorator/optional + +/datum/bt_node/decorator/optional/tick(datum/ai_controller/controller, seconds_per_tick) + . = ..() + if(. == BT_FAILURE) + return BT_SUCCESS diff --git a/code/datums/ai/generic_decorators/true_for_time.dm b/code/datums/ai/generic_decorators/true_for_time.dm index c04383a1835c..e70126b6a70f 100644 --- a/code/datums/ai/generic_decorators/true_for_time.dm +++ b/code/datums/ai/generic_decorators/true_for_time.dm @@ -1,14 +1,16 @@ /** - * Passes while its duration has not elapsed, then fails reactively via observer abort. + * Passes while its duration has not elapsed. * Starts timing on the first tick; resets when the tree resets. * * duration: how long to pass, in deciseconds (e.g. "10 SECONDS"). - * Set observer_abort to BT_ABORT_BOTH (or BT_ABORT_SELF) to abort the child when time expires. - */ + * succeed_on_timeout FALSE (default): counts as succeed and lets lower nodes pass +*/ /datum/bt_node/decorator/true_for_time + observer_abort = BT_ABORT_SELF var/duration = 0 - var/timer_id = null - var/timed_out = FALSE + var/succeed_on_timeout = FALSE + VAR_PRIVATE/timer_id = null + VAR_PRIVATE/timed_out = FALSE /datum/bt_node/decorator/true_for_time/register_observe_signals(atom/pawn) timer_id = addtimer(CALLBACK(src, PROC_REF(on_timeout)), duration, TIMER_STOPPABLE|TIMER_DELETE_ME) @@ -24,8 +26,18 @@ SIGNAL_HANDLER timer_id = null timed_out = TRUE + if(succeed_on_timeout) + return // handled locally by tick(), no plan-wide cancel needed if(owning_controller) on_observed_change(owning_controller, null) /datum/bt_node/decorator/true_for_time/check_condition(datum/ai_controller/controller) return !timed_out + +/datum/bt_node/decorator/true_for_time/tick(datum/ai_controller/controller, seconds_per_tick) + if(succeed_on_timeout && timed_out) + if(child_active && child) + child.reset_subtree_tick_states() + child_active = FALSE + return BT_SUCCESS + return ..() diff --git a/code/datums/ai/generic_subtrees/random_walk.bt.json b/code/datums/ai/generic_subtrees/random_walk.bt.json index fb0446306c38..1ff4af0b089e 100644 --- a/code/datums/ai/generic_subtrees/random_walk.bt.json +++ b/code/datums/ai/generic_subtrees/random_walk.bt.json @@ -6,24 +6,17 @@ "default": "25" } }, - "type": "decorator", - "decorator": "/datum/bt_node/decorator/true_for_time", - "vars": { - "duration": "5 SECONDS" - }, - "child": { - "type": "subplan", - "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", - "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", - "loop_delay": "IDLE_BEHAVIOR_RATE", - "children": [ - { - "type": "leaf", - "behavior": "/datum/bt_node/ai_behavior/idle_random_walk", - "vars": { - "walk_chance": "$bf07i8ep" - } + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk", + "vars": { + "walk_chance": "$bf07i8ep" } - ] - } + } + ] } diff --git a/code/modules/mob/living/basic/pets/cat/cat.bt.json b/code/modules/mob/living/basic/pets/cat/cat.bt.json index b8bb0004a3a6..07bd81e876df 100644 --- a/code/modules/mob/living/basic/pets/cat/cat.bt.json +++ b/code/modules/mob/living/basic/pets/cat/cat.bt.json @@ -56,10 +56,18 @@ "type": "sequence", "children": [ { - "type": "subtree", - "subtype": "/datum/bt_node/subtree/run_away_from_target", - "bindings": { - "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + "type": "decorator", + "decorator": "/datum/bt_node/decorator/true_for_time", + "vars": { + "duration": "3 SECONDS", + "succeed_on_timeout": true + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } } }, { diff --git a/tgstation.dme b/tgstation.dme index 1abcd92bd34d..bd5c2afc87b2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1145,6 +1145,7 @@ #include "code\datums\ai\generic_decorators\keys_different_gender.dm" #include "code\datums\ai\generic_decorators\mob_stat_at_least.dm" #include "code\datums\ai\generic_decorators\no_humans_watching.dm" +#include "code\datums\ai\generic_decorators\optional.dm" #include "code\datums\ai\generic_decorators\pawn_buckled_to_obj.dm" #include "code\datums\ai\generic_decorators\pawn_contained_in_obj.dm" #include "code\datums\ai\generic_decorators\pawn_farther_than_from_key.dm" From 399828e1b2796952e73f92dfa806eab94ddb77d2 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 01:53:32 +0000 Subject: [PATCH 077/445] Automatic changelog for PR #97072 [ci skip] --- html/changelogs/AutoChangeLog-pr-97072.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97072.yml diff --git a/html/changelogs/AutoChangeLog-pr-97072.yml b/html/changelogs/AutoChangeLog-pr-97072.yml new file mode 100644 index 000000000000..dc02ae32b160 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97072.yml @@ -0,0 +1,4 @@ +author: "CabinetOnFire" +delete-after: True +changes: + - bugfix: "Fixes cats getting permanent zoomies after being chased away by a superior male" \ No newline at end of file From aa37826768cba9f04fa8346ae8fa1d0e4019e155 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Mon, 20 Jul 2026 21:54:15 -0400 Subject: [PATCH 078/445] Hilberts hotel will properly kick people out who are peeking when the room is deleted & related bugfixes (#97053) ## About The Pull Request image If a room gets deleted while someone is looking at (or the user) things don't get cleaned up properly. This PR just fixes that Fixes some other associated runtime log spams. (one adding an override to a signal that should be an override, another fixing the lights starting turned off if you reload the same room, caused by the wallmount changes and needing some special consideration) ## Why It's Good For The Game Bugfix ## Changelog :cl: fix: peeking into a hilberts hotel room while it gets deleted will now kick you out properly fix: fixed an issue where the lights would stay dark after reloading the same hilberts hotel room /:cl: --- code/game/machinery/doors/door.dm | 2 +- .../ruins/spaceruin_code/hilbertshotel.dm | 172 +++++++++++------- code/modules/power/lighting/light.dm | 4 +- 3 files changed, 113 insertions(+), 65 deletions(-) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 2fd10a28715b..e61886f7266a 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -376,7 +376,7 @@ stoplag(1) // allow the door to process any allow/deny responses first var/do_after_time = rand(delayed_unres_time_lower, delayed_unres_time_upper) ADD_TRAIT(opener, TRAIT_UNRESTRICTED_AIRLOCK_OPENING, REF(src)) - RegisterSignal(opener, COMSIG_ATOM_PRE_PRESSURE_PUSH, PROC_REF(stop_pressure_during_unres_open)) + RegisterSignal(opener, COMSIG_ATOM_PRE_PRESSURE_PUSH, PROC_REF(stop_pressure_during_unres_open), override = TRUE) addtimer(CALLBACK(src, PROC_REF(deregister_pressure_push_signal), opener), do_after_time + 0.5 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) // extra half-second to be safe, else this is just a guarantee we remove the signal. SSblackbox.record_feedback("tally", "unrestricted_airlock_usage", 1, "open attempt ([type])") // statcollecting on how often people try to use this. diff --git a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm index 995b789456e7..b96df88a3917 100644 --- a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm +++ b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm @@ -127,20 +127,27 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) var/datum/turf_reservation/roomReservation = SSmapping.request_turf_block_reservation(hotelRoomTemp.width, hotelRoomTemp.height, 1) var/turf/room_turf = roomReservation.bottom_left_turfs[1] hotelRoomTempEmpty.load(room_turf) + var/obj/item/abstracthotelstorage/storage_obj + for(var/obj/item/abstracthotelstorage/hotel_storage in storageTurf) + if((hotel_storage.roomNumber == roomNumber) && (hotel_storage.parentSphere == src)) + storage_obj = hotel_storage + break var/turfNumber = 1 for(var/x in 0 to hotelRoomTemp.width-1) for(var/y in 0 to hotelRoomTemp.height-1) - for(var/atom/movable/A in storedRooms["[roomNumber]"][turfNumber]) - if(istype(A.loc, /obj/item/abstracthotelstorage))//Don't want to recall something thats been moved - A.forceMove(locate( + for(var/atom/movable/atom in storedRooms["[roomNumber]"][turfNumber]) + if(istype(atom.loc, /obj/item/abstracthotelstorage))//Don't want to recall something thats been moved + atom.forceMove(locate( room_turf.x + x, room_turf.y + y, room_turf.z, )) + if(storage_obj && (atom in storage_obj.wallmounted_contents)) + var/obj/stored_in = atom + if(istype(stored_in)) + stored_in.find_and_mount_on_atom() turfNumber++ - for(var/obj/item/abstracthotelstorage/S in storageTurf) - if((S.roomNumber == roomNumber) && (S.parentSphere == src)) - qdel(S) + qdel(storage_obj) storedRooms -= "[roomNumber]" activeRooms["[roomNumber]"] = roomReservation linkTurfs(roomReservation, roomNumber) @@ -191,47 +198,47 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) BSturf.parentSphere = src /obj/item/hilbertshotel/proc/ejectRooms() - if(activeRooms.len) - for(var/x in activeRooms) - var/datum/turf_reservation/room = activeRooms[x] + if(length(activeRooms)) + for(var/active_room, turf_reservation in activeRooms) + var/datum/turf_reservation/room = turf_reservation var/turf/room_bottom_left = room.bottom_left_turfs[1] for(var/i in 0 to hotelRoomTemp.width-1) for(var/j in 0 to hotelRoomTemp.height-1) - for(var/atom/movable/A in locate(room_bottom_left.x + i, room_bottom_left.y + j, room_bottom_left.z)) - if(ismob(A)) - var/mob/M = A - if(M.mind) - to_chat(M, span_warning("As the sphere breaks apart, you're suddenly ejected into the depths of space!")) + for(var/atom/movable/atom in locate(room_bottom_left.x + i, room_bottom_left.y + j, room_bottom_left.z)) + if(ismob(atom)) + var/mob/mob_atom = atom + if(mob_atom.mind) + to_chat(mob_atom, span_warning("As the sphere breaks apart, you're suddenly ejected into the depths of space!")) var/max = world.maxx-TRANSITIONEDGE var/min = 1+TRANSITIONEDGE var/list/possible_transtitons = list() - for(var/AZ in SSmapping.z_list) - var/datum/space_level/D = AZ - if (D.linkage == CROSSLINKED) - possible_transtitons += D.z_value + for(var/z_level in SSmapping.z_list) + var/datum/space_level/space_level = z_level + if (space_level.linkage == CROSSLINKED) + possible_transtitons += space_level.z_value var/_z = pick(possible_transtitons) var/_x = rand(min,max) var/_y = rand(min,max) - var/turf/T = locate(_x, _y, _z) - A.forceMove(T) + var/turf/turf_to_place_at = locate(_x, _y, _z) + atom.forceMove(turf_to_place_at) qdel(room) - if(storedRooms.len) - for(var/x in storedRooms) - var/list/atomList = storedRooms[x] - for(var/atom/movable/A in atomList) + if(length(storedRooms)) + for(var/room in storedRooms) + var/list/atom_list = storedRooms[room] + for(var/atom/movable/atom in atom_list) var/max = world.maxx-TRANSITIONEDGE var/min = 1+TRANSITIONEDGE var/list/possible_transtitons = list() - for(var/AZ in SSmapping.z_list) - var/datum/space_level/D = AZ - if (D.linkage == CROSSLINKED) - possible_transtitons += D.z_value + for(var/z_level in SSmapping.z_list) + var/datum/space_level/space_level = z_level + if (space_level.linkage == CROSSLINKED) + possible_transtitons += space_level.z_value var/_z = pick(possible_transtitons) var/_x = rand(min,max) var/_y = rand(min,max) - var/turf/T = locate(_x, _y, _z) - A.forceMove(T) + var/turf/turf_to_place_at = locate(_x, _y, _z) + atom.forceMove(turf_to_place_at) //Template Stuff /datum/map_template/hilbertshotel @@ -301,16 +308,38 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) icon_state = "hoteldoor" explosive_resistance = INFINITY var/obj/item/hilbertshotel/parentSphere + /// Mobs currently peeking through this door - needs cleanup if this turf changes type while they're still registered to it. + var/list/mob/peeking_users /turf/closed/indestructible/hoteldoor/Initialize(mapload) . = ..() register_context() +/turf/closed/indestructible/hoteldoor/Destroy(force) + LAZYNULL(peeking_users) + return ..() + /turf/closed/indestructible/hoteldoor/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = ..() context[SCREENTIP_CONTEXT_ALT_LMB] = "Peek through" return CONTEXTUAL_SCREENTIP_SET +// Cancel the peeking of anyone peeking out of this door when the turf changes +/turf/closed/indestructible/hoteldoor/ChangeTurf(path, list/new_baseturfs, flags) + for(var/mob/user in peeking_users) + cancel_peek(user) + return ..() + +/// Cancel the peeking of anyone peeking out of this door while they are deleted +/turf/closed/indestructible/hoteldoor/proc/on_peeker_qdeleted(datum/source, force) + SIGNAL_HANDLER + LAZYREMOVE(peeking_users, source) + +/// Cancels a user's peeking +/turf/closed/indestructible/hoteldoor/proc/cancel_peek(mob/user) + for(var/datum/action/peephole_cancel/cancel_action in user.actions) + INVOKE_ASYNC(cancel_action, TYPE_PROC_REF(/datum/action/peephole_cancel, Trigger)) + /turf/closed/indestructible/hoteldoor/proc/promptExit(mob/living/user) if(!isliving(user)) return @@ -360,23 +389,28 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) to_chat(user, span_notice("You peek through the door's bluespace peephole...")) user.reset_perspective(parentSphere) - var/datum/action/peephole_cancel/PHC = new + var/datum/action/peephole_cancel/peephole_cancel_action = new + peephole_cancel_action.door = src user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1) - PHC.Grant(user) + peephole_cancel_action.Grant(user) + LAZYADD(peeking_users, user) RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(check_eye)) + RegisterSignal(user, COMSIG_QDELETING, PROC_REF(on_peeker_qdeleted)) return CLICK_ACTION_SUCCESS /turf/closed/indestructible/hoteldoor/proc/check_eye(mob/user, atom/oldloc, direction) SIGNAL_HANDLER if(get_dist(get_turf(src), get_turf(user)) < 2) return - for(var/datum/action/peephole_cancel/PHC in user.actions) - INVOKE_ASYNC(PHC, TYPE_PROC_REF(/datum/action/peephole_cancel, Trigger)) + cancel_peek(user) + UnregisterSignal(user, COMSIG_QDELETING) /datum/action/peephole_cancel name = "Cancel View" desc = "Stop looking through the bluespace peephole." button_icon_state = "cancel_peephole" + /// The door this peephole view is looking through + var/turf/closed/indestructible/hoteldoor/door /datum/action/peephole_cancel/Trigger(mob/clicker, trigger_flags) . = ..() @@ -385,9 +419,14 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) to_chat(owner, span_warning("You move away from the peephole.")) owner.reset_perspective() owner.clear_fullscreen("remote_view", 0) - UnregisterSignal(owner, COMSIG_MOVABLE_MOVED) + door?.UnregisterSignal(owner, COMSIG_MOVABLE_MOVED) + LAZYREMOVE(door?.peeking_users, owner) qdel(src) +/datum/action/peephole_cancel/Destroy(force) + door = null + return ..() + // Despite using the ruins.dmi, hilbertshotel is not a ruin /area/misc/hilbertshotel name = "Hilbert's Hotel Room" @@ -409,49 +448,49 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) if(istype(arrived, /obj/item/hilbertshotel)) relocate(arrived) var/list/obj/item/hilbertshotel/hotels = arrived.get_all_contents_type(/obj/item/hilbertshotel) - for(var/obj/item/hilbertshotel/H in hotels) - if(parentSphere == H) - relocate(H) + for(var/obj/item/hilbertshotel/hotel_item in hotels) + if(parentSphere == hotel_item) + relocate(hotel_item) -/area/misc/hilbertshotel/proc/relocate(obj/item/hilbertshotel/H) +/area/misc/hilbertshotel/proc/relocate(obj/item/hilbertshotel/hotel_item) if(prob(0.135685)) //Because screw you - qdel(H) + qdel(hotel_item) return // Prepare for... - var/mob/living/unforeseen_consequences = get_atom_on_turf(H, /mob/living) + var/mob/living/unforeseen_consequences = get_atom_on_turf(hotel_item, /mob/living) // Turns out giving anyone who grabs a Hilbert's Hotel a free, complementary warp whistle is probably bad. // Let's gib the last person to have selected a room number in it. if(unforeseen_consequences) - to_chat(unforeseen_consequences, span_warning("\The [H] starts to resonate. Forcing it to enter itself induces a bluespace paradox, violently tearing your body apart.")) - unforeseen_consequences.investigate_log("has been gibbed by using [H] while inside of it.", INVESTIGATE_DEATHS) + to_chat(unforeseen_consequences, span_warning("\The [hotel_item] starts to resonate. Forcing it to enter itself induces a bluespace paradox, violently tearing your body apart.")) + unforeseen_consequences.investigate_log("has been gibbed by using [hotel_item] while inside of it.", INVESTIGATE_DEATHS) unforeseen_consequences.gib(DROP_ALL_REMAINS) var/turf/targetturf = find_safe_turf() if(!targetturf) - if(GLOB.blobstart.len > 0) + if(length(GLOB.blobstart) > 0) targetturf = get_turf(pick(GLOB.blobstart)) else CRASH("Unable to find a blobstart landmark") - log_game("[H] entered itself. Moving it to [loc_name(targetturf)].") - message_admins("[H] entered itself. Moving it to [ADMIN_VERBOSEJMP(targetturf)].") - H.visible_message(span_danger("[H] almost implodes in upon itself, but quickly rebounds, shooting off into a random point in space!")) - H.forceMove(targetturf) + log_game("[hotel_item] entered itself. Moving it to [loc_name(targetturf)].") + message_admins("[hotel_item] entered itself. Moving it to [ADMIN_VERBOSEJMP(targetturf)].") + hotel_item.visible_message(span_danger("[hotel_item] almost implodes in upon itself, but quickly rebounds, shooting off into a random point in space!")) + hotel_item.forceMove(targetturf) /area/misc/hilbertshotel/Exited(atom/movable/gone, direction) . = ..() if(ismob(gone)) - var/mob/M = gone - if(M.mind) - var/stillPopulated = FALSE - var/list/currentLivingMobs = get_all_contents_type(/mob/living) //Got to catch anyone hiding in anything - for(var/mob/living/L in currentLivingMobs) //Check to see if theres any sentient mobs left. - if(L.mind) - stillPopulated = TRUE + var/mob/gone_mob = gone + if(gone_mob.mind) + var/still_populated = FALSE + var/list/current_living_mobs = get_all_contents_type(/mob/living) //Got to catch anyone hiding in anything + for(var/mob/living/living_mob as anything in current_living_mobs) //Check to see if theres any sentient mobs left. + if(living_mob.mind) + still_populated = TRUE break - if(!stillPopulated) + if(!still_populated) storeRoom() /area/misc/hilbertshotel/proc/storeRoom() @@ -469,16 +508,19 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) for(var/x in 0 to parentSphere.hotelRoomTemp.width-1) for(var/y in 0 to parentSphere.hotelRoomTemp.height-1) var/list/turfContents = list() - for(var/atom/movable/A in locate(room_bottom_left.x + x, room_bottom_left.y + y, room_bottom_left.z)) - if(ismob(A) && !isliving(A)) + for(var/atom/movable/atom in locate(room_bottom_left.x + x, room_bottom_left.y + y, room_bottom_left.z)) + if(ismob(atom) && !isliving(atom)) continue //Don't want to store ghosts - turfContents += A - A.forceMove(storageObj) + turfContents += atom + if(HAS_TRAIT(atom, TRAIT_WALLMOUNTED)) + LAZYADD(storageObj.wallmounted_contents, atom) + qdel(atom.GetComponent(/datum/component/atom_mounted)) + atom.forceMove(storageObj) storage[turfNumber] = turfContents turfNumber++ parentSphere.storedRooms["[roomnumber]"] = storage parentSphere.activeRooms -= "[roomnumber]" - qdel(reservation) + QDEL_NULL(reservation) /area/misc/hilbertshotelstorage name = "Hilbert's Hotel Storage Room" @@ -496,6 +538,8 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) item_flags = ABSTRACT var/roomNumber var/obj/item/hilbertshotel/parentSphere + /// Movables that had their atom_mounted component stripped so they wouldn't deconstruct while stored - remounted when the room is restored. + var/list/wallmounted_contents /obj/item/abstracthotelstorage/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) if(istype(arrived, /obj/machinery/light)) @@ -506,6 +550,10 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) var/mob/target = arrived ADD_TRAIT(target, TRAIT_NO_TRANSFORM, REF(src)) +/obj/item/abstracthotelstorage/Destroy(force) + LAZYNULL(wallmounted_contents) + return ..() + /obj/item/abstracthotelstorage/Exited(atom/movable/gone, direction) . = ..() if(ismob(gone)) @@ -535,7 +583,7 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999)) to_chat(user, span_warning("It's to far away to scan!")) return ITEM_INTERACT_BLOCKING var/obj/item/hilbertshotel/sphere = interacting_with - if(sphere.activeRooms.len) + if(length(sphere.activeRooms)) to_chat(user, "Currently Occupied Rooms:") for(var/roomnumber in sphere.activeRooms) to_chat(user, roomnumber) diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm index b77589db2df8..1d5fed407a91 100644 --- a/code/modules/power/lighting/light.dm +++ b/code/modules/power/lighting/light.dm @@ -502,13 +502,13 @@ // if a light is turned off, it won't activate emergency power /obj/machinery/light/proc/turned_off() var/area/local_area = get_room_area() - return !local_area.lightswitch && local_area.power_light || flickering + return (local_area && !local_area.lightswitch && local_area.power_light) || flickering // returns whether this light has power // true if area has power and lightswitch is on /obj/machinery/light/proc/has_power() var/area/local_area = get_room_area() - return local_area.lightswitch && local_area.power_light + return local_area && local_area.lightswitch && local_area.power_light // returns whether this light has emergency power // can also return if it has access to a certain amount of that power From 5699e94e8c2b48bdb47d2060c3b56a1ae0700bdb Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 01:54:37 +0000 Subject: [PATCH 079/445] Automatic changelog for PR #97053 [ci skip] --- html/changelogs/AutoChangeLog-pr-97053.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97053.yml diff --git a/html/changelogs/AutoChangeLog-pr-97053.yml b/html/changelogs/AutoChangeLog-pr-97053.yml new file mode 100644 index 000000000000..db698b6c48db --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97053.yml @@ -0,0 +1,5 @@ +author: "vinylspiders" +delete-after: True +changes: + - bugfix: "peeking into a hilberts hotel room while it gets deleted will now kick you out properly" + - bugfix: "fixed an issue where the lights would stay dark after reloading the same hilberts hotel room" \ No newline at end of file From 00e055b8e7a9f69f73dba18662abe65f9b8e95cd Mon Sep 17 00:00:00 2001 From: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Date: Mon, 20 Jul 2026 21:55:04 -0400 Subject: [PATCH 080/445] No seeds for abstract food, unit test for edible food only checks valid types (#97076) ## About The Pull Request in #96984 Ghommie spoke of how there's not supposed to be seeds in abstract plants, but when you take out the seed it runtimes in the edible food unit test because they're not removing abstract types. This is the same reason that I snuck by seeds in abstract melons and carrots. This pr removes seeds in already-existing abstract plants, and makes the unit test only check `valid_typesof()`. ## Why It's Good For The Game Damned if you do, damned if you don't. ## Changelog :cl: code: Edible food unit test doesn't check abstract type food, abstract type food doesn't have seeds. /:cl: --- code/modules/hydroponics/grown/citrus.dm | 1 - code/modules/hydroponics/grown/melon.dm | 1 - code/modules/hydroponics/grown/root.dm | 1 - code/modules/mining/lavaland/ash_flora.dm | 12 ++++++++---- code/modules/unit_tests/food_edibility_check.dm | 5 +---- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/code/modules/hydroponics/grown/citrus.dm b/code/modules/hydroponics/grown/citrus.dm index 9b58432ec445..d5533c0ec0dd 100644 --- a/code/modules/hydroponics/grown/citrus.dm +++ b/code/modules/hydroponics/grown/citrus.dm @@ -6,7 +6,6 @@ abstract_type = /obj/item/food/grown/citrus foodtypes = FRUIT wine_power = 30 - seed = /obj/item/seeds/lime // Lime /obj/item/seeds/lime diff --git a/code/modules/hydroponics/grown/melon.dm b/code/modules/hydroponics/grown/melon.dm index 66348d6a3a72..1c6cd400154d 100644 --- a/code/modules/hydroponics/grown/melon.dm +++ b/code/modules/hydroponics/grown/melon.dm @@ -1,5 +1,4 @@ /obj/item/food/grown/melonlike - seed = /obj/item/seeds/watermelon name = "melon?" desc = "You felt like this was a melon, but it definitely isn't. You should tell somebody about this." icon_state = "watermelon" diff --git a/code/modules/hydroponics/grown/root.dm b/code/modules/hydroponics/grown/root.dm index fd5de5d0dfcd..638896d895dc 100644 --- a/code/modules/hydroponics/grown/root.dm +++ b/code/modules/hydroponics/grown/root.dm @@ -1,5 +1,4 @@ /obj/item/food/grown/carrotlike - seed = /obj/item/seeds/carrot name = "carrot?" desc = "What is this? It's not a carrot, that's for sure. You should tell someone about this." icon_state = "carrot" diff --git a/code/modules/mining/lavaland/ash_flora.dm b/code/modules/mining/lavaland/ash_flora.dm index c9da29aad6ee..bb9f860a258f 100644 --- a/code/modules/mining/lavaland/ash_flora.dm +++ b/code/modules/mining/lavaland/ash_flora.dm @@ -241,16 +241,14 @@ //SNACKS /obj/item/food/grown/ash_flora - name = "mushroom shavings" - desc = "Some shavings from a tall mushroom. With enough, might serve as a bowl." + name = "unidentified bits of a wierd plant" + desc = "What's this? An intruder, is what it is. Tell somebody about this." icon = 'icons/obj/mining_zones/ash_flora.dmi' icon_state = "mushroom_shavings" abstract_type = /obj/item/food/grown/ash_flora w_class = WEIGHT_CLASS_TINY resistance_flags = FLAMMABLE max_integrity = 100 - seed = /obj/item/seeds/lavaland/polypore - wine_power = 20 foodtypes = VEGETABLES /obj/item/food/grown/ash_flora/Initialize(mapload) @@ -258,6 +256,12 @@ pixel_x = base_pixel_x + rand(-4, 4) pixel_y = base_pixel_y + rand(-4, 4) +/obj/item/food/grown/ash_flora/shavings + name = "mushroom shavings" + desc = "Some shavings from a tall mushroom. With enough, might serve as a bowl." + seed = /obj/item/seeds/lavaland/polypore + wine_power = 20 + /obj/item/food/grown/ash_flora/shavings/grind_results() return list(/datum/reagent/toxin/mushroom_powder = 5) diff --git a/code/modules/unit_tests/food_edibility_check.dm b/code/modules/unit_tests/food_edibility_check.dm index f8360e2c6e9a..9cbb3f65b803 100644 --- a/code/modules/unit_tests/food_edibility_check.dm +++ b/code/modules/unit_tests/food_edibility_check.dm @@ -3,13 +3,10 @@ /datum/unit_test/food_edibility_check/Run() var/list/not_food = list( - /obj/item/food/grown, - /obj/item/food/grown/mushroom, /obj/item/food/clothing, - /obj/item/food/meat/slab/human/mutant, ) - var/list/food_paths = subtypesof(/obj/item/food) - not_food + var/list/food_paths = valid_subtypesof(/obj/item/food) - not_food for(var/food_path in food_paths) var/obj/item/food/spawned_food = allocate(food_path) From 91e40331e6f21a42a326336b59815661d5aeb285 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 01:55:22 +0000 Subject: [PATCH 081/445] Automatic changelog for PR #97076 [ci skip] --- html/changelogs/AutoChangeLog-pr-97076.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97076.yml diff --git a/html/changelogs/AutoChangeLog-pr-97076.yml b/html/changelogs/AutoChangeLog-pr-97076.yml new file mode 100644 index 000000000000..ee6b96ff08c8 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97076.yml @@ -0,0 +1,4 @@ +author: "lelandkemble" +delete-after: True +changes: + - code_imp: "Edible food unit test doesn't check abstract type food, abstract type food doesn't have seeds." \ No newline at end of file From 26903d588684f04db6e0f888534558f907fb5336 Mon Sep 17 00:00:00 2001 From: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:56:40 -0500 Subject: [PATCH 082/445] Character setup prefs for jobs sorts the same as Latejoin (#96836) ## About The Pull Request This: image\ To this: image I didn't sort the departments programmatically as well because I dont know how i want to structure the code because rn its hard-coded and i dont know if i want to keep it like that or not.. I could totally just replicate the late-join menu for this if its desired tho. image ## Why It's Good For The Game Stuff like the atmos tech appearing above the engineer- or the food roles or clown+mime not being group- is bad. ## Changelog :cl: qol: per department sorting for character setup matches latejoin menu /:cl: --- .../client/preferences/middleware/jobs.dm | 5 +++++ .../CharacterPreferences/JobsPage.tsx | 16 ++++------------ .../tgui/interfaces/PreferencesMenu/types.ts | 1 + 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/code/modules/client/preferences/middleware/jobs.dm b/code/modules/client/preferences/middleware/jobs.dm index fcca02e5e978..54629ff1ae8c 100644 --- a/code/modules/client/preferences/middleware/jobs.dm +++ b/code/modules/client/preferences/middleware/jobs.dm @@ -42,6 +42,7 @@ var/list/departments = list() var/list/jobs = list() + var/list/jobs_sorted = list() for (var/datum/job/job as anything in SSjob.joinable_occupations) if (job.job_flags & JOB_LATEJOIN_ONLY) @@ -64,6 +65,9 @@ "color" = department_type.ui_color, // Prob shouldnt be here. ) + // Use the built in sorting of the main occupation list to keep it sorted how we want instead of asc + jobs_sorted += job.title + jobs[job.title] = list( "description" = job.description, "department" = department_name, @@ -71,6 +75,7 @@ data["departments"] = departments data["jobs"] = jobs + data["jobs_sorted"] = jobs_sorted return data diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/JobsPage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/JobsPage.tsx index 5a614c35f45c..a4ee84d31456 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/JobsPage.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/JobsPage.tsx @@ -20,13 +20,6 @@ import { } from '../types'; import { useServerPrefs } from '../useServerPrefs'; -function sortJobs(entries: [string, Job][], head?: string) { - return sortBy(entries, [ - ([key, _]) => (key === head ? -1 : 1), - ([key, _]) => key, - ]); -} - const PRIORITY_BUTTON_SIZE = '18px'; type PriorityButtonProps = { @@ -355,7 +348,7 @@ function Department(props: DepartmentProps) { const data = useServerPrefs(); if (!data) return; - const { departments, jobs } = data.jobs; + const { departments, jobs, jobs_sorted} = data.jobs; const department = departments[name]; // This isn't necessarily a bug, it's like this @@ -366,10 +359,9 @@ function Department(props: DepartmentProps) { return null; } - const jobsForDepartment = sortJobs( - Object.entries(jobs).filter(([_, job]) => job.department === name), - department.head, - ); + const jobsForDepartment = jobs_sorted + .map((jobName) => [jobName, jobs[jobName]] as const) + .filter(([, job]) => job.department === name); return ( ; jobs: Record; + jobs_sorted: string[]; }; names: { types: Record; From 0646525ec11c5084069dd89048aa06e8765f75c5 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 04:56:59 +0000 Subject: [PATCH 083/445] Automatic changelog for PR #96836 [ci skip] --- html/changelogs/AutoChangeLog-pr-96836.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96836.yml diff --git a/html/changelogs/AutoChangeLog-pr-96836.yml b/html/changelogs/AutoChangeLog-pr-96836.yml new file mode 100644 index 000000000000..5202025c25ec --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96836.yml @@ -0,0 +1,4 @@ +author: "FalloutFalcon" +delete-after: True +changes: + - qol: "per department sorting for character setup matches latejoin menu" \ No newline at end of file From f1e2b927be3ba99bb6c201bf7775f573a8433bce Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 06:00:22 +0000 Subject: [PATCH 084/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-96836.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97053.yml | 5 ----- html/changelogs/AutoChangeLog-pr-97059.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97072.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97076.yml | 4 ---- html/changelogs/archive/2026-07.yml | 15 +++++++++++++++ 6 files changed, 15 insertions(+), 21 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-96836.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97053.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97059.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97072.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97076.yml diff --git a/html/changelogs/AutoChangeLog-pr-96836.yml b/html/changelogs/AutoChangeLog-pr-96836.yml deleted file mode 100644 index 5202025c25ec..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96836.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - qol: "per department sorting for character setup matches latejoin menu" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97053.yml b/html/changelogs/AutoChangeLog-pr-97053.yml deleted file mode 100644 index db698b6c48db..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97053.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "vinylspiders" -delete-after: True -changes: - - bugfix: "peeking into a hilberts hotel room while it gets deleted will now kick you out properly" - - bugfix: "fixed an issue where the lights would stay dark after reloading the same hilberts hotel room" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97059.yml b/html/changelogs/AutoChangeLog-pr-97059.yml deleted file mode 100644 index e248020b3000..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97059.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Removed unnecessary click delay from window placement" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97072.yml b/html/changelogs/AutoChangeLog-pr-97072.yml deleted file mode 100644 index dc02ae32b160..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97072.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "CabinetOnFire" -delete-after: True -changes: - - bugfix: "Fixes cats getting permanent zoomies after being chased away by a superior male" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97076.yml b/html/changelogs/AutoChangeLog-pr-97076.yml deleted file mode 100644 index ee6b96ff08c8..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97076.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "lelandkemble" -delete-after: True -changes: - - code_imp: "Edible food unit test doesn't check abstract type food, abstract type food doesn't have seeds." \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index b0528b51424a..0080bd4f7653 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -619,5 +619,20 @@ - bugfix: Fixed the Four Hands wizard perk - bugfix: Fixed quick tile replacement 2026-07-21: + CabinetOnFire: + - bugfix: Fixes cats getting permanent zoomies after being chased away by a superior + male + FalloutFalcon: + - qol: per department sorting for character setup matches latejoin menu + SmArtKar: + - bugfix: Removed unnecessary click delay from window placement + lelandkemble: + - code_imp: Edible food unit test doesn't check abstract type food, abstract type + food doesn't have seeds. timothymtorres: - rscadd: Add water bottles to booze-o-mat non-alcoholic category + vinylspiders: + - bugfix: peeking into a hilberts hotel room while it gets deleted will now kick + you out properly + - bugfix: fixed an issue where the lights would stay dark after reloading the same + hilberts hotel room From e1b36878af6bb357c3dcd536a4f6a5cfecd9b9d1 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Tue, 21 Jul 2026 10:01:46 -0400 Subject: [PATCH 085/445] Fixes tgui linter errors (#97101) --- tgui/packages/tgui-dev-server/link/client.ts | 2 +- tgui/packages/tgui/interfaces/AtmosControlConsole.tsx | 2 +- tgui/packages/tgui/interfaces/Cargo/CargoCart.tsx | 2 +- tgui/packages/tgui/interfaces/Cargo/CargoStatus.tsx | 2 +- tgui/packages/tgui/interfaces/Flatpacker.tsx | 2 +- tgui/packages/tgui/interfaces/NtosBotanicalEncyclopedia.tsx | 6 +++--- tgui/packages/tgui/interfaces/NtosRadar.tsx | 2 +- tgui/packages/tgui/interfaces/Orbit/OrbitBlade.tsx | 2 +- tgui/packages/tgui/interfaces/Orbit/OrbitTooltip.tsx | 2 +- .../PreferencesMenu/CharacterPreferences/JobsPage.tsx | 3 +-- .../tgui/interfaces/PreferencesMenu/useServerPrefs.ts | 1 + tgui/packages/tgui/interfaces/PrisonerManagement.tsx | 2 +- .../tgui/interfaces/SecurityRecords/CrimeWatcher.tsx | 2 +- tgui/packages/tgui/interfaces/StationAlertConsole.jsx | 2 +- tgui/packages/tgui/interfaces/Techweb/nodes/TechNode.tsx | 2 +- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tgui/packages/tgui-dev-server/link/client.ts b/tgui/packages/tgui-dev-server/link/client.ts index eb8d542a6875..be283a676018 100644 --- a/tgui/packages/tgui-dev-server/link/client.ts +++ b/tgui/packages/tgui-dev-server/link/client.ts @@ -106,7 +106,7 @@ function serializeObject(obj: Record): string { // Error object const isError = value instanceof Error || - (value.code && value.message && value.message.includes('Error')); + (value.code && value.message?.includes('Error')); if (isError) { return { __error__: true, diff --git a/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx b/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx index 749c9c87e957..3f97051ee630 100644 --- a/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx +++ b/tgui/packages/tgui/interfaces/AtmosControlConsole.tsx @@ -70,7 +70,7 @@ export const AtmosControlConsole = (props) => { ) } > - {!!selectedChamber && !!selectedChamber.gasmix ? ( + {selectedChamber?.gasmix ? ( {entry.object} - {!!can_send && !!entry.can_be_cancelled ? ( + {can_send && entry.can_be_cancelled ? ( <>
+ )} + {slot.security !== SlotSecurity.WELDED && !!holding_multitool && ( + + )} + {slot.security !== SlotSecurity.WELDED && + !holding_multitool && + !holding_welder && ( + + )} + + + act('screw_module', { slot: index + 1 })} + /> + + + + ); +}; + +const LinkableFloating = (props: { + show_floating: boolean; + linkable: Linkable[]; + link_act: (ref: string, name: string) => void; + hide_floating: () => void; + button: React.ReactNode; +}) => { + const { show_floating, linkable, link_act, button, hide_floating } = props; + + if (!show_floating) { + return button; + } + + return ( + + {linkable.map((item) => ( + + + + ))} + + } + > + {button} + + ); +}; + +const UnlinkableFloating = (props: { + show_floating: boolean; + unlinkable: string[]; + unlink_act: (index: number, name: string) => void; + hide_floating: () => void; + button: React.ReactNode; + can_unlink: BooleanLike; + unlink_tooltip?: string; +}) => { + const { + button, + can_unlink, + hide_floating, + show_floating, + unlink_act, + unlink_tooltip, + unlinkable, + } = props; + + if (!show_floating) { + return button; + } + + return ( + + {unlinkable.map((name, list_index) => ( + + + + ))} + + } + > + {button} + + ); +}; + +const AiFlavorDisplay = (props: { show_face: string }) => { + return ( + + + {!!props.show_face && ( + + )} + + ); +}; + +const BroadcasterDisplay = () => { + const { data, act } = useBackend(); + const { depowered, parent_rack, linkable_racks } = data; + + const [rackLink, setRackLink] = useState(true); + return ( + + + + + > Linked Rack: + + + {parent_rack ? capitalize(parent_rack.name) : 'N/A'} + + + + + + + + + + + act('link_rack', { + rack_ref: ref, + rack_name: name, + }) + } + hide_floating={() => setRackLink(false)} + button={ + + } + /> + + + + + ); +}; + +const BaseDisplay = () => { + const { data, act } = useBackend(); + const { depowered, linkable_silicons, linked_mobs, parent_rack } = data; + + const [siliconLink, setSiliconLink] = useState(true); + const [siliconUnlink, setSiliconUnlink] = useState(true); + + return ( + + + + + > Linked Silicons: + + + {linked_mobs?.length ? linked_mobs.join(', ') : 'N/A'} + + + + + + + act('unlink_silicon', { + silicon_index: index, + silicon_name: name, + }) + } + hide_floating={() => setSiliconUnlink(false)} + button={ + + } + /> + + + act('link_silicon', { + silicon_ref: ref, + silicon_name: name, + }) + } + hide_floating={() => setSiliconLink(false)} + button={ + + } + /> + + + + + ); +}; +export const LawRack = () => { + const { data, act } = useBackend(); + const { + depowered, + linkable_racks, + linked_mobs, + parent_rack, + slots, + refresh_cooldown, + has_core_slot, + linked_rack_module_count, + } = data; + + const screen_display = () => { + if (depowered) { + return 'off'; + } + if (parent_rack || linked_mobs?.length) { + return Math.round(Math.random() * 100) === 1 ? 'ai_hal' : 'ai_sal'; + } + return 'ai_bsod'; + }; + + return ( + + +
+ + {slots.map((slot, index) => ( + + + + ))} + {linked_rack_module_count !== undefined && ( + + {linked_rack_module_count} other module(s) are being + loaded remotely. + + )} + +
+
0} + tooltipPosition="left" + tooltip={ + depowered + ? 'No power!' + : refresh_cooldown > 0 + ? `Cooldown: ${Math.round(refresh_cooldown / 10)}s` + : 'Refreshes module and link data.' + } + onClick={() => act('refresh')} + /> + } + > + + + + {!!has_core_slot && ( + + + + )} + {linkable_racks && ( + + + + )} + {linked_mobs && ( + + + + )} + + + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Lawpanel.tsx b/tgui/packages/tgui/interfaces/Lawpanel.tsx index b0670ffcaaaf..15266b91e7e2 100644 --- a/tgui/packages/tgui/interfaces/Lawpanel.tsx +++ b/tgui/packages/tgui/interfaces/Lawpanel.tsx @@ -19,31 +19,10 @@ import { Window } from '../layouts'; const lawtype_to_color = { inherent: 'white', supplied: 'purple', - ion: 'green', hacked: 'orange', zeroth: 'red', } as const; -const lawtype_to_tooltip = { - inherent: `Core laws. - Inherent laws are the "core" laws of the AI. - Resetting the AI will not remove these, - these are intrinsic to whatever lawset they are running.`, - supplied: `Supplied laws. - Supplied laws are, well, supplied in addition to the inherent laws. - These laws will go away when an AI is reset.`, - ion: `Ion laws. - Special, (usually) randomized laws which are above all over laws. - These laws will go away when an AI is reset.`, - hacked: `Hacked laws. - Syndicate uploaded laws which are above all other laws. - These laws will go away when an AI is reset.`, - zeroth: `Zeroth law. - A lawset can only have 1 zeroth law, it's the top dog. - Given out by malfunctioning AIs to allow them to do whatever. - Nothing will remove this, allegedly, unless it's admin forced.`, -} as const; - type Law = { lawtype: string; // The actual law text @@ -106,18 +85,6 @@ export const LawPrintout = (props: { cyborg_ref: string; lawset: Law[] }) => { color={lawtype_to_color[law.lawtype] || 'pink'} buttons={ - - {law.lawtype === 'inherent' && ( <> @@ -183,21 +150,6 @@ export const LawPrintout = (props: { cyborg_ref: string; lawset: Law[] }) => { )} - {law.lawtype === 'supplied' && ( - - + ); +} + +type MenuButtonProps = { + children: React.ReactNode; + onClick: () => void; + disabled?: boolean; + blinking?: boolean; + tooltip?: string; +}; + +function MenuButton({ + children, + onClick, + disabled, + blinking, + tooltip, +}: MenuButtonProps) { + const button = ( + + ); + + if (tooltip) { + return {button}; + } + + return button; +} diff --git a/tgui/packages/tgui-escape-menu/pages/HomePage.tsx b/tgui/packages/tgui-escape-menu/pages/HomePage.tsx new file mode 100644 index 000000000000..0ca6d2d85fa8 --- /dev/null +++ b/tgui/packages/tgui-escape-menu/pages/HomePage.tsx @@ -0,0 +1,181 @@ +import { useEffect, useState } from 'react'; +import { Tooltip } from 'tgui-core/components'; + +import type { ResourceLink } from '../EscapeMenu'; + +type ServerState = { + stationName: string; + canLeaveBody: boolean; + canAdminHelp: boolean; + hasTicketNotification: boolean; + resources: ResourceLink[]; +}; + +type Props = { + serverState: ServerState; + onNavigate: (page: 'admin' | 'players' | 'leave_body' | 'quit') => void; + onAction: (action: string) => void; + onClose: () => void; + showResources: boolean; + onToggleResources: () => void; +}; + +const COLLAPSE_DURATION = 400; + +export function HomePage({ + serverState, + onNavigate, + onAction, + onClose, + showResources, + onToggleResources, +}: Props) { + const [mounted, setMounted] = useState(false); + const [collapsing, setCollapsing] = useState(false); + + useEffect(() => { + if (showResources) { + setMounted(true); + setCollapsing(false); + } else if (mounted) { + setCollapsing(true); + const timer = setTimeout(() => { + setMounted(false); + setCollapsing(false); + }, COLLAPSE_DURATION); + return () => clearTimeout(timer); + } + }, [showResources]); + + return ( + <> +
+
+
Another day on...
+
+ {serverState.stationName} +
+
+
+ Resume + { + onAction('character'); + onClose(); + }} + > + Character + + { + onAction('settings'); + onClose(); + }} + > + Settings + + onNavigate('players')}>Players + onNavigate('admin')} + blinking={serverState.hasTicketNotification} + tooltip={ + serverState.hasTicketNotification + ? 'An admin is trying to talk to you!' + : undefined + } + > + Admin Help + + onNavigate('leave_body')} + disabled={!serverState.canLeaveBody} + > + Leave Body + + onNavigate('quit')}>Quit +
+
+
+ {mounted && ( +
+ {serverState.resources.map((resource) => ( + + + + ))} +
+ )} + +
+ + ); +} + +function IconButton({ iconClass }: { iconClass: string }) { + return ( +
+ + +
+ ); +} + +type MenuButtonProps = { + children: React.ReactNode; + onClick: () => void; + disabled?: boolean; + blinking?: boolean; + tooltip?: string; +}; + +function MenuButton({ + children, + onClick, + disabled, + blinking, + tooltip, +}: MenuButtonProps) { + const button = ( + + ); + + if (tooltip) { + return {button}; + } + + return button; +} diff --git a/tgui/packages/tgui-escape-menu/pages/LeaveBodyPage.tsx b/tgui/packages/tgui-escape-menu/pages/LeaveBodyPage.tsx new file mode 100644 index 000000000000..07eea97d1405 --- /dev/null +++ b/tgui/packages/tgui-escape-menu/pages/LeaveBodyPage.tsx @@ -0,0 +1,67 @@ +import type { ServerState } from '../EscapeMenu'; + +type Props = { + serverState: ServerState; + onNavigate: (page: 'home') => void; + onAction: (action: string) => void; + onClose: () => void; +}; + +export function LeaveBodyPage({ + serverState, + onNavigate, + onAction, + onClose, +}: Props) { + return ( + <> + onNavigate('home')} /> +
+ + +
+ + ); +} + +function BackButton({ onClick }: { onClick: () => void }) { + return ( + + ); +} diff --git a/tgui/packages/tgui-escape-menu/pages/PlayersPage.tsx b/tgui/packages/tgui-escape-menu/pages/PlayersPage.tsx new file mode 100644 index 000000000000..3b092b700f5d --- /dev/null +++ b/tgui/packages/tgui-escape-menu/pages/PlayersPage.tsx @@ -0,0 +1,131 @@ +import type { PlayerInfo, ServerState } from '../EscapeMenu'; + +type Props = { + serverState: ServerState; + onNavigate: (page: 'home') => void; + onAction: (action: string, payload?: Record) => void; +}; + +export function PlayersPage({ serverState, onNavigate, onAction }: Props) { + return ( + <> + onNavigate('home')} /> +
+ {serverState.admins.length > 0 ? ( + + {serverState.admins.map((admin) => ( + + onAction('toggle_ignore', { ckey: admin.ckey }) + } + /> + ))} + + ) : ( +
+ No Admins Online! +
+ )} + + {serverState.players.map((player) => ( + + onAction('toggle_ignore', { ckey: player.ckey }) + } + /> + ))} + + {serverState.ignoredOffline.length > 0 && ( + + {serverState.ignoredOffline.map((ckey) => ( +
+ onAction('toggle_ignore', { ckey })} + > + {ckey} + +
+ ))} +
+ )} +
+ + ); +} + +function BackButton({ onClick }: { onClick: () => void }) { + return ( + + ); +} + +function PlayerSection({ + title, + children, +}: { + title: string; + children: React.ReactNode; +}) { + return ( +
+
{title}
+
{children}
+
+ ); +} + +function PlayerEntry({ + player, + onToggleIgnore, +}: { + player: PlayerInfo; + onToggleIgnore: () => void; +}) { + return ( +
+ + {player.displayName} + {player.ping !== undefined && ( + ({player.ping}ms) + )} + + {player.rank && ( + Byond.command(`.url ${player.feedbackLink}`) + : undefined + } + > + {player.rank} + + )} +
+ ); +} diff --git a/tgui/packages/tgui-escape-menu/pages/QuitPage.tsx b/tgui/packages/tgui-escape-menu/pages/QuitPage.tsx new file mode 100644 index 000000000000..c5ae9c0ca3e8 --- /dev/null +++ b/tgui/packages/tgui-escape-menu/pages/QuitPage.tsx @@ -0,0 +1,28 @@ +type Props = { + onNavigate: (page: 'home') => void; + onAction: (action: string) => void; +}; + +export function QuitPage({ onNavigate, onAction }: Props) { + return ( +
+
+ Are you sure you wish to quit? +
+
+ + +
+
+ ); +} diff --git a/tgui/packages/tgui-escape-menu/styles/fonts.css b/tgui/packages/tgui-escape-menu/styles/fonts.css new file mode 100644 index 000000000000..bb4eddfd2ade --- /dev/null +++ b/tgui/packages/tgui-escape-menu/styles/fonts.css @@ -0,0 +1,9 @@ +@font-face { + font-family: "Pixellari"; + src: url("Pixellari.ttf") format("truetype"); +} + +@font-face { + font-family: "Grand9K Pixel"; + src: url("Grand9K_Pixel.ttf") format("truetype"); +} diff --git a/tgui/packages/tgui-escape-menu/styles/main.scss b/tgui/packages/tgui-escape-menu/styles/main.scss new file mode 100644 index 000000000000..68ca84478177 --- /dev/null +++ b/tgui/packages/tgui-escape-menu/styles/main.scss @@ -0,0 +1,400 @@ +@use 'sass:meta'; + +@include meta.load-css('~tgui/styles/reset'); +@include meta.load-css('~tgui-core/styles/base.scss'); +@include meta.load-css('~tgui-core/styles/colors.scss'); +@include meta.load-css('~tgui-core/styles/vars-components.scss'); +@include meta.load-css('~tgui-core/styles/components/Tooltip.scss'); + +html, +body, +#react-root { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; + background: transparent !important; + background-color: transparent !important; + cursor: default !important; +} + +* { + cursor: default !important; + user-select: none; +} + +.Tooltip { + font-family: 'Pixellari', monospace; + transform: translateY(-1.5rem); +} + +.escape-menu { + --details-width: 25rem; + --content-margin: 4rem; + --details-gap: 3rem; + + position: relative; + width: 100%; + height: 100%; + font-family: 'Pixellari', 'Grand9K Pixel', monospace; + color: white; + -webkit-text-stroke: 0.2rem black; + paint-order: stroke fill; + -webkit-font-smoothing: none; + -moz-osx-font-smoothing: unset; + text-rendering: optimizeSpeed; + + &__overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.65); + z-index: 0; + } + + &__content { + position: relative; + z-index: 1; + width: 100%; + height: 100%; + } + + &__home-column { + position: absolute; + top: var(--content-margin); + left: var(--content-margin); + max-width: calc( + 100% - var(--content-margin) - var(--details-width) - var(--details-gap) + ); + display: flex; + flex-direction: column; + } + + &__title { + margin-bottom: 2rem; + } + + &__subtitle { + font-size: 1.8rem; + color: #ccc; + margin-bottom: 0.4rem; + } + + &__station-name { + font-size: 5rem; + font-weight: bold; + -webkit-text-stroke: 0.3rem black; + } + + &__details { + position: absolute; + top: 2rem; + right: 2rem; + width: var(--details-width); + text-align: right; + font-size: 1.8rem; + line-height: 1.5; + color: #ddd; + z-index: 2; + } + + &__buttons { + display: flex; + flex-direction: column; + gap: 0.3rem; + + &--page { + position: absolute; + top: 12rem; + left: var(--content-margin); + } + } + + &__menu-button { + background: none; + border: none; + color: white; + font-family: 'Pixellari', monospace; + font-size: 4.5rem; + font-weight: bold; + text-align: left; + padding: 0.2rem 0.5rem; + cursor: pointer; + -webkit-text-stroke: 0.3rem black; + + &:hover { + text-decoration: underline; + -webkit-text-stroke: 0; + } + + &--disabled { + color: #666; + cursor: default; + + &:hover { + text-decoration: none; + } + } + + &--blinking { + animation: blink-red 0.8s infinite; + } + } + + &__back-button { + position: absolute; + top: 3rem; + left: 2rem; + background: none; + border: none; + color: white; + font-family: 'Grand9K Pixel', monospace; + font-size: 1.4rem; + cursor: pointer; + padding: 0.8rem 1.5rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.3rem; + + .escape-menu__icon-button { + transform: scale(var(--icon-scale, 1.5)); + } + + &:hover { + filter: brightness(1.1); + } + } + + &__leave-body { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + gap: 6rem; + } + + &__large-button { + background: none; + border: none; + color: white; + font-family: 'Pixellari', monospace; + padding: 0; + cursor: pointer; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + + &:hover { + filter: brightness(1.1); + } + } + + &__large-button-label { + font-size: 3rem; + font-weight: bold; + } + + &__quit { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + } + + &__quit-prompt { + font-size: 4.5rem; + margin-bottom: 3rem; + } + + &__quit-buttons { + display: flex; + gap: 4rem; + justify-content: center; + } + + &__resources { + position: absolute; + bottom: 2rem; + right: 2rem; + } + + &__resource-toggle, + &__resource-button { + background: none; + border: none; + color: white; + padding: 0; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + width: calc(40px * var(--icon-scale, 1.5) + 2rem); + + &:hover { + filter: brightness(1.1); + } + } + + &__resource-button { + animation: resource-slide-in 0.4s cubic-bezier(0.33, 1, 0.68, 1) both; + + @for $i from 1 through 8 { + &:nth-child(#{$i}) { + animation-delay: #{$i * 0.04}s; + } + } + } + + &__resource-label, + &__resource-button-label { + font-family: 'Grand9K Pixel', monospace; + font-size: 1.2rem; + text-align: center; + } + + &__resource-list { + position: absolute; + top: 0; + right: calc(40px * var(--icon-scale, 1.5) + 3rem); + display: flex; + flex-direction: row-reverse; + align-items: flex-start; + + &--collapsing .escape-menu__resource-button { + animation: resource-slide-out 0.4s cubic-bezier(0.33, 1, 0.68, 1) both; + } + } + + &__icon-button { + position: relative; + display: inline-block; + transform: scale(var(--icon-scale, 1.5)); + transform-origin: center bottom; + image-rendering: pixelated; + } + + &__icon-overlay { + position: absolute; + top: 0; + left: 0; + } + + &__suicide-icon-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + image-rendering: pixelated; + } + + &__player-list { + position: absolute; + top: 12rem; + left: var(--content-margin); + right: calc(var(--details-width) + var(--details-gap)); + bottom: var(--content-margin); + overflow-y: auto; + scrollbar-color: rgba(255, 255, 255, 0.3) transparent; + } + + &__player-section { + margin-bottom: 2rem; + } + + &__player-section-title { + font-size: 3rem; + font-weight: bold; + margin-bottom: 1rem; + padding-bottom: 0.4rem; + border-bottom: 2px solid rgba(255, 255, 255, 0.15); + } + + &__player-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); + gap: 0.4rem 0.8rem; + } + + &__player-entry { + color: white; + font-family: 'Pixellari', monospace; + padding: 0.4rem 0.8rem; + display: flex; + flex-direction: column; + + &--ignored { + color: #666; + } + } + + &__player-name { + font-size: 2rem; + overflow-wrap: break-word; + + &:hover { + text-decoration: underline; + -webkit-text-stroke: 0; + } + } + + &__player-ping { + font-size: 1.4rem; + color: #777; + font-family: 'Grand9K Pixel', monospace; + } + + &__player-rank { + font-size: 1.4rem; + color: #aaa; + font-family: 'Grand9K Pixel', monospace; + + &--link { + color: #5599ff; + text-decoration: none; + + &:hover { + text-decoration: underline; + -webkit-text-stroke: 0; + } + } + } +} + +@keyframes blink-red { + 0%, + 100% { + color: white; + } + 50% { + color: red; + } +} + +@keyframes resource-slide-in { + from { + opacity: 0; + transform: translateX(6rem); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes resource-slide-out { + from { + opacity: 1; + transform: translateX(0); + } + to { + opacity: 0; + transform: translateX(6rem); + } +} diff --git a/tgui/rspack.config.ts b/tgui/rspack.config.ts index 6c5f65b9690f..7de38f18a420 100644 --- a/tgui/rspack.config.ts +++ b/tgui/rspack.config.ts @@ -30,6 +30,7 @@ export default defineConfig({ 'tgui-panel': './packages/tgui-panel', 'tgui-say': './packages/tgui-say', 'tgui-chat-dark': './packages/tgui-chat-dark', + 'tgui-escape-menu': './packages/tgui-escape-menu', }, mode: 'production', module: { @@ -133,6 +134,7 @@ export default defineConfig({ tgui: path.resolve(dirname, './packages/tgui'), 'tgui-panel': path.resolve(dirname, './packages/tgui-panel'), 'tgui-say': path.resolve(dirname, './packages/tgui-say'), + 'tgui-escape-menu': path.resolve(dirname, './packages/tgui-escape-menu'), 'tgui-dev-server': path.resolve(dirname, './packages/tgui-dev-server'), }, }, diff --git a/tgui/tsconfig.json b/tgui/tsconfig.json index 0c7a714eebe6..4ae5cfc47aee 100644 --- a/tgui/tsconfig.json +++ b/tgui/tsconfig.json @@ -18,6 +18,8 @@ "tgui-panel/*": ["./packages/tgui-panel/*"], "tgui-say": ["./packages/tgui-say"], "tgui-say/*": ["./packages/tgui-say/*"], + "tgui-escape-menu": ["./packages/tgui-escape-menu"], + "tgui-escape-menu/*": ["./packages/tgui-escape-menu/*"], "tgui-dev-server": ["./packages/tgui-dev-server"], "tgui-dev-server/*": ["./packages/tgui-dev-server/*"] }, From 1ccbd331eaa7e10f849a1c1b802e34ebedce7070 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 05:39:39 +0000 Subject: [PATCH 231/445] Automatic changelog for PR #97065 [ci skip] --- html/changelogs/AutoChangeLog-pr-97065.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97065.yml diff --git a/html/changelogs/AutoChangeLog-pr-97065.yml b/html/changelogs/AutoChangeLog-pr-97065.yml new file mode 100644 index 000000000000..96086a16edad --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97065.yml @@ -0,0 +1,4 @@ +author: "hry-gh" +delete-after: True +changes: + - qol: "the escape menu now runs clientside, and should be considerably more responsive" \ No newline at end of file From 6133384f9346230253ebaff2734b446012fba660 Mon Sep 17 00:00:00 2001 From: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Date: Sun, 26 Jul 2026 01:45:41 -0400 Subject: [PATCH 232/445] I'm out of titles attackby() to item_interaction() (#96785) ## About The Pull Request 50 again, but a soft(er) 50. We're on the home stretch, probably only two more prs to go before cleanup begins. changes beyond conversion: the firepit structure that I think is mapped in in like one single place and can't be created can now be lit successfully you can hit a griddle with something that isnt stuck to your arm you now fail to dig up a hydroponics tray if you fail the `do_after()`, or if there's no longer a plant there. ## Why It's Good For The Game Two have already been merged. You're stuck here until we're through. You can't leave. Events are in motion. ## Changelog :cl: fix: the firepit structure that spawns somewhere(probably on icebox) can now be lit fix: you can hit a griddle on combat mode fix: you no longer succeed at digging up a hydroponics tray after failing the do_after() code: 50 files have been converted from attackby() to item_interaction /:cl: --- .../equipment/gear/abductor_posters.dm | 4 +- .../antagonists/abductor/machinery/console.dm | 13 +- .../abductor/machinery/dispenser.dm | 20 +- .../antagonists/blob/structures/_blob.dm | 22 +- .../antagonists/clown_ops/clown_weapons.dm | 18 +- .../modules/antagonists/heretic/influences.dm | 12 +- .../equipment/nuclear_authentication_disk.dm | 36 +-- .../equipment/nuclear_bomb/_nuclear_bomb.dm | 188 +++++++------ .../equipment/nuclear_bomb/beer_nuke.dm | 11 +- .../antagonists/wizard/equipment/soulstone.dm | 31 +-- code/modules/art/statues.dm | 20 +- code/modules/assembly/assembly.dm | 30 +-- code/modules/assembly/flash.dm | 6 +- code/modules/assembly/holder.dm | 10 +- code/modules/assembly/signaler.dm | 33 ++- .../machinery/air_alarm/air_alarm_interact.dm | 94 ++++--- .../atmospherics/machinery/atmosmachinery.dm | 18 +- .../components/electrolyzer/electrolyzer.dm | 35 +-- .../atmospherics/machinery/components/tank.dm | 12 +- .../atmospherics/machinery/pipes/pipes.dm | 13 +- .../machinery/portable/canister.dm | 35 +-- .../portable/portable_atmospherics.dm | 12 +- .../awaymissions/mission_code/Cabin.dm | 24 +- .../modules/awaymissions/super_secret_room.dm | 5 +- code/modules/basketball/hoop.dm | 25 +- code/modules/capture_the_flag/ctf_game.dm | 37 ++- code/modules/cargo/universal_scanner.dm | 54 ++-- code/modules/clothing/gloves/_gloves.dm | 18 +- code/modules/clothing/head/helmet.dm | 48 ++-- code/modules/clothing/head/jobs.dm | 23 +- code/modules/clothing/head/tophat.dm | 10 +- code/modules/clothing/masks/gasmask.dm | 15 +- code/modules/clothing/neck/collar_bomb.dm | 9 +- code/modules/clothing/shoes/sneakers.dm | 13 +- .../clothing/spacesuits/_spacesuits.dm | 19 +- code/modules/clothing/spacesuits/softsuit.dm | 16 +- code/modules/explorer_drone/exodrone.dm | 23 +- code/modules/fishing/fish/_fish.dm | 21 +- code/modules/fishing/fishing_rod.dm | 25 +- code/modules/food_and_drinks/food/egg.dm | 69 ++--- code/modules/food_and_drinks/food/misc.dm | 14 +- code/modules/food_and_drinks/food/pancakes.dm | 70 ++--- code/modules/food_and_drinks/food/pizza.dm | 23 +- .../food_and_drinks/machinery/griddle.dm | 83 +++--- .../food_and_drinks/machinery/icecream_vat.dm | 52 ++-- .../modules/food_and_drinks/machinery/oven.dm | 21 +- code/modules/holodeck/items.dm | 6 +- code/modules/hydroponics/fermenting_barrel.dm | 23 +- code/modules/hydroponics/hydroponics.dm | 249 ++++++++++-------- code/modules/hydroponics/soil.dm | 16 +- 50 files changed, 931 insertions(+), 753 deletions(-) diff --git a/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm b/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm index 96ba94170c89..33224d543809 100644 --- a/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm +++ b/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm @@ -17,10 +17,10 @@ return return ..() -/obj/structure/sign/poster/abductor/attackby(obj/item/tool, mob/user, list/modifiers, list/attack_modifiers) +/obj/structure/sign/poster/abductor/wirecutter_act(mob/living/user, obj/item/tool) if(tool.toolspeed >= 0.2) balloon_alert(user, "tool too weak!") - return FALSE + return ITEM_INTERACT_BLOCKING return ..() /obj/structure/sign/poster/abductor/random diff --git a/code/modules/antagonists/abductor/machinery/console.dm b/code/modules/antagonists/abductor/machinery/console.dm index dd3abe6994ac..88e8da2be45d 100644 --- a/code/modules/antagonists/abductor/machinery/console.dm +++ b/code/modules/antagonists/abductor/machinery/console.dm @@ -266,13 +266,16 @@ vest = V return TRUE -/obj/machinery/abductor/console/attackby(obj/O, mob/user, list/modifiers, list/attack_modifiers) - if(istype(O, /obj/item/abductor/gizmo) && AddGizmo(O)) +/obj/machinery/abductor/console/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/abductor/gizmo) && AddGizmo(tool)) to_chat(user, span_notice("You link the tool to the console.")) - else if(istype(O, /obj/item/clothing/suit/armor/abductor/vest) && AddVest(O)) + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/clothing/suit/armor/abductor/vest) && AddVest(tool)) to_chat(user, span_notice("You link the vest to the console.")) - else - return ..() + return ITEM_INTERACT_SUCCESS + + return NONE /obj/machinery/abductor/console/proc/Dispense(items_list, cost=1) if(experiment && experiment.credits >= cost) diff --git a/code/modules/antagonists/abductor/machinery/dispenser.dm b/code/modules/antagonists/abductor/machinery/dispenser.dm index bd6cc36e5ba3..b4775b08ba82 100644 --- a/code/modules/antagonists/abductor/machinery/dispenser.dm +++ b/code/modules/antagonists/abductor/machinery/dispenser.dm @@ -61,15 +61,17 @@ Dispense(gland_id) return TRUE -/obj/machinery/abductor/gland_dispenser/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers) - if(istype(W, /obj/item/organ/heart/gland)) - if(!user.transferItemToLoc(W, src)) - return - for(var/i in 1 to gland_colors.len) - if(gland_types[i] == W.type) - amounts[i]++ - else - return ..() +/obj/machinery/abductor/gland_dispenser/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/organ/heart/gland)) + return NONE + + if(!user.transferItemToLoc(tool, src)) + return ITEM_INTERACT_BLOCKING + + for(var/slot in 1 to gland_colors.len) + if(gland_types[slot] == tool.type) + amounts[slot]++ + return ITEM_INTERACT_SUCCESS /obj/machinery/abductor/gland_dispenser/proc/Dispense(count) if(amounts[count]>0) diff --git a/code/modules/antagonists/blob/structures/_blob.dm b/code/modules/antagonists/blob/structures/_blob.dm index 8bde78251ff5..340754275506 100644 --- a/code/modules/antagonists/blob/structures/_blob.dm +++ b/code/modules/antagonists/blob/structures/_blob.dm @@ -257,19 +257,17 @@ /obj/structure/blob/hulk_damage() return 15 -/obj/structure/blob/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers) - if(I.tool_behaviour == TOOL_ANALYZER) - user.changeNext_move(CLICK_CD_MELEE) - to_chat(user, "The analyzer beeps once, then reports:
") - SEND_SOUND(user, sound('sound/machines/ping.ogg')) - if(overmind) - to_chat(user, "Progress to Critical Mass: [span_notice("[overmind.blobs_legit.len]/[overmind.blobwincount].")]") - to_chat(user, chemeffectreport(user).Join("\n")) - else - to_chat(user, "Blob core neutralized. Critical mass no longer attainable.") - to_chat(user, typereport(user).Join("\n")) +/obj/structure/blob/analyzer_act(mob/living/user, obj/item/analyzer/tool) + user.changeNext_move(CLICK_CD_MELEE) + to_chat(user, "The analyzer beeps once, then reports:
") + SEND_SOUND(user, sound('sound/machines/ping.ogg')) + if(overmind) + to_chat(user, "Progress to Critical Mass: [span_notice("[overmind.blobs_legit.len]/[overmind.blobwincount].")]") + to_chat(user, chemeffectreport(user).Join("\n")) else - return ..() + to_chat(user, "Blob core neutralized. Critical mass no longer attainable.") + to_chat(user, typereport(user).Join("\n")) + return ITEM_INTERACT_SUCCESS /obj/structure/blob/proc/chemeffectreport(mob/user) RETURN_TYPE(/list) diff --git a/code/modules/antagonists/clown_ops/clown_weapons.dm b/code/modules/antagonists/clown_ops/clown_weapons.dm index 73c639c16a13..5a5baa90c851 100644 --- a/code/modules/antagonists/clown_ops/clown_weapons.dm +++ b/code/modules/antagonists/clown_ops/clown_weapons.dm @@ -62,13 +62,17 @@ var/datum/component/slippery/slipper = GetComponent(/datum/component/slippery) slipper.Slip(src, hit_atom) -/obj/item/melee/energy/sword/bananium/attackby(obj/item/weapon, mob/living/user, list/modifiers, list/attack_modifiers) - if(COOLDOWN_FINISHED(src, next_trombone_allowed) && istype(weapon, /obj/item/melee/energy/sword/bananium)) - COOLDOWN_START(src, next_trombone_allowed, 5 SECONDS) - to_chat(user, span_warning("You slap the two swords together. Sadly, they do not seem to fit!")) - playsound(src, 'sound/misc/sadtrombone.ogg', 50) - return TRUE - return ..() +/obj/item/melee/energy/sword/bananium/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/melee/energy/sword/bananium)) + return ..() + + if(!COOLDOWN_FINISHED(src, next_trombone_allowed)) + return ITEM_INTERACT_BLOCKING + + COOLDOWN_START(src, next_trombone_allowed, 5 SECONDS) + to_chat(user, span_warning("You slap the two swords together. Sadly, they do not seem to fit!")) + playsound(src, 'sound/misc/sadtrombone.ogg', 50) + return ITEM_INTERACT_SUCCESS /obj/item/melee/energy/sword/bananium/suicide_act(mob/living/user) if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) diff --git a/code/modules/antagonists/heretic/influences.dm b/code/modules/antagonists/heretic/influences.dm index aa21e484d865..882d3c593e34 100644 --- a/code/modules/antagonists/heretic/influences.dm +++ b/code/modules/antagonists/heretic/influences.dm @@ -205,14 +205,12 @@ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN -/obj/effect/heretic_influence/attackby(obj/item/weapon, mob/user, list/modifiers, list/attack_modifiers) - . = ..() - if(.) - return - +/obj/effect/heretic_influence/item_interaction(mob/living/user, obj/item/tool, list/modifiers) // Using a codex will give you two knowledge points for draining. - if(drain_influence_with_codex(user, weapon)) - return TRUE + if(drain_influence_with_codex(user, tool)) + return ITEM_INTERACT_SUCCESS + + return ..() /obj/effect/heretic_influence/proc/drain_influence_with_codex(mob/user, obj/item/codex_cicatrix/codex) if(!istype(codex) || being_drained) diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm b/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm index 06a95d809d00..5a04c0745b4d 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclear_authentication_disk.dm @@ -79,24 +79,24 @@ return discover_after -/obj/item/disk/nuclear/attackby(obj/item/weapon, mob/living/user, list/modifiers, list/attack_modifiers) - if(istype(weapon, /obj/item/claymore/highlander) && !fake) - var/obj/item/claymore/highlander/claymore = weapon - if(claymore.nuke_disk) - to_chat(user, span_notice("Wait... what?")) - qdel(claymore.nuke_disk) - claymore.nuke_disk = null - return - - user.visible_message( - span_warning("[user] captures [src]!"), - span_userdanger("You've got the disk! Defend it with your life!"), - ) - forceMove(claymore) - claymore.nuke_disk = src - return TRUE - - return ..() +/obj/item/disk/nuclear/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/claymore/highlander) || fake) + return ..() + + var/obj/item/claymore/highlander/claymore = tool + if(claymore.nuke_disk) + to_chat(user, span_notice("Wait... what?")) + qdel(claymore.nuke_disk) + claymore.nuke_disk = null + return ITEM_INTERACT_BLOCKING + + user.visible_message( + span_warning("[user] captures [src]!"), + span_userdanger("You've got the disk! Defend it with your life!"), + ) + forceMove(claymore) + claymore.nuke_disk = src + return ITEM_INTERACT_SUCCESS /obj/item/disk/nuclear/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is going delta! It looks like [user.p_theyre()] trying to commit suicide!")) diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm b/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm index f2ed3118e918..e0623c74bf12 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclear_bomb/_nuclear_bomb.dm @@ -111,86 +111,59 @@ GLOBAL_VAR(station_nuke_source) return TRUE -/obj/machinery/nuclearbomb/attackby(obj/item/weapon, mob/user, list/modifiers, list/attack_modifiers) - if (istype(weapon, /obj/item/disk/nuclear)) - if(!disk_check(weapon)) - return TRUE - if(!user.transferItemToLoc(weapon, src)) - return TRUE - auth = weapon +/obj/machinery/nuclearbomb/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if (istype(tool, /obj/item/disk/nuclear)) + if(!disk_check(tool)) + return ITEM_INTERACT_BLOCKING + + if(!user.transferItemToLoc(tool, src)) + return ITEM_INTERACT_BLOCKING + + auth = tool update_ui_mode() playsound(src, 'sound/machines/terminal/terminal_insert_disc.ogg', 50, FALSE) add_fingerprint(user) - return TRUE + return ITEM_INTERACT_SUCCESS switch(deconstruction_state) - if(NUKESTATE_INTACT) - if(istype(weapon, /obj/item/screwdriver/nuke)) - to_chat(user, span_notice("You start removing [src]'s front panel's screws...")) - if(!weapon.use_tool(src, user, 6 SECONDS, volume = 100)) - return TRUE - deconstruction_state = NUKESTATE_UNSCREWED - to_chat(user, span_notice("You remove the screws from [src]'s front panel.")) - update_appearance() - return TRUE - - if(NUKESTATE_UNSCREWED) - if(istype(weapon, /obj/item/screwdriver/nuke)) - to_chat(user, span_notice("You start screwing [src]'s front panel back in...")) - if(!weapon.use_tool(src, user, 8 SECONDS, volume = 100)) - return TRUE - deconstruction_state = NUKESTATE_INTACT - to_chat(user, span_notice("You screw [src]'s front panel back into place.")) - deconstruction_state = NUKESTATE_INTACT - update_appearance() - return TRUE - - if(NUKESTATE_PANEL_REMOVED) - if(weapon.tool_behaviour == TOOL_WELDER) - if(!weapon.tool_start_check(user, amount = 1)) - return TRUE - to_chat(user, span_notice("You start cutting [src]'s inner plate...")) - if(!weapon.use_tool(src, user, 8 SECONDS, volume=100)) - return TRUE - to_chat(user, span_notice("You cut [src]'s inner plate.")) - deconstruction_state = NUKESTATE_WELDED - update_appearance() - return TRUE - if(NUKESTATE_CORE_EXPOSED) - if(istype(weapon, /obj/item/nuke_core_container)) - var/obj/item/nuke_core_container/core_box = weapon + if(istype(tool, /obj/item/nuke_core_container)) + var/obj/item/nuke_core_container/core_box = tool to_chat(user, span_notice("You start loading the plutonium core into [core_box]...")) if(!do_after(user, 5 SECONDS, target = src, cog_icon = null)) - return TRUE - if(core_box.load(core, user)) - to_chat(user, span_notice("You load the plutonium core into [core_box].")) - deconstruction_state = NUKESTATE_CORE_REMOVED - update_appearance() - core = null - else + return ITEM_INTERACT_BLOCKING + + if(!core_box.load(core, user)) to_chat(user, span_warning("You fail to load the plutonium core into [core_box]. [core_box] has already been used!")) - return TRUE + return ITEM_INTERACT_BLOCKING + + to_chat(user, span_notice("You load the plutonium core into [core_box].")) + deconstruction_state = NUKESTATE_CORE_REMOVED + update_appearance() + core = null + return ITEM_INTERACT_SUCCESS - if(istype(weapon, /obj/item/stack/sheet/iron)) - if(!weapon.tool_start_check(user, amount = 20)) - return TRUE + if(istype(tool, /obj/item/stack/sheet/iron)) + if(!tool.tool_start_check(user, amount = 20)) + return ITEM_INTERACT_BLOCKING to_chat(user, span_notice("You begin repairing [src]'s inner metal plate...")) - if(!weapon.use_tool(src, user, 10 SECONDS, amount = 20)) - return TRUE + if(!tool.use_tool(src, user, 10 SECONDS, amount = 20)) + return ITEM_INTERACT_BLOCKING + to_chat(user, span_notice("You repair [src]'s inner metal plate. The radiation is contained.")) deconstruction_state = NUKESTATE_PANEL_REMOVED STOP_PROCESSING(SSobj, core) update_appearance() - return TRUE + return ITEM_INTERACT_SUCCESS if(NUKESTATE_CORE_REMOVED) - if(astype(weapon, /obj/item/nuke_core_container)?.core && !istype(weapon, /obj/item/nuke_core_container/supermatter)) - var/obj/item/nuke_core_container/core_box = weapon + if(astype(tool, /obj/item/nuke_core_container)?.core && !istype(tool, /obj/item/nuke_core_container/supermatter)) + var/obj/item/nuke_core_container/core_box = tool to_chat(user, span_notice("You pry open [core_box] and begin placing [core_box.core] into [src]'s inner chamber...")) if(!do_after(user, 15 SECONDS, src)) - return TRUE + return ITEM_INTERACT_BLOCKING + core_box.core.forceMove(src) core = core_box.core to_chat(user, span_notice("You place [core_box.core] into [src]'s inner chamber.")) @@ -198,53 +171,108 @@ GLOBAL_VAR(station_nuke_source) update_appearance() core_box.icon_state = core_box::icon_state core_box.core = null - return TRUE - if(istype(weapon, /obj/item/nuke_core) && !istype(weapon, /obj/item/nuke_core/supermatter_sliver)) - to_chat(user, span_notice("You begin placing [weapon] into [src]'s inner chamber...")) + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/nuke_core) && !istype(tool, /obj/item/nuke_core/supermatter_sliver)) + to_chat(user, span_notice("You begin placing [tool] into [src]'s inner chamber...")) if(!do_after(user, 6 SECONDS, src)) - return TRUE - weapon.forceMove(src) - core = weapon - to_chat(user, span_notice("You place [weapon] into [src]'s inner chamber.")) + return ITEM_INTERACT_BLOCKING + + tool.forceMove(src) + core = tool + to_chat(user, span_notice("You place [tool] into [src]'s inner chamber.")) deconstruction_state = NUKESTATE_CORE_EXPOSED update_appearance() - return TRUE + return ITEM_INTERACT_SUCCESS - return ..() + return NONE /obj/machinery/nuclearbomb/crowbar_act(mob/user, obj/item/tool) switch(deconstruction_state) if(NUKESTATE_UNSCREWED) to_chat(user, span_notice("You start removing [src]'s front panel...")) - if(!tool.use_tool(src, user, 30, volume=100)) - return TRUE + if(!tool.use_tool(src, user, 3 SECONDS, volume = 100)) + return ITEM_INTERACT_BLOCKING + to_chat(user, span_notice("You remove [src]'s front panel.")) deconstruction_state = NUKESTATE_PANEL_REMOVED update_appearance() - return TRUE + return ITEM_INTERACT_SUCCESS + if(NUKESTATE_WELDED) to_chat(user, span_notice("You start prying off [src]'s inner plate...")) - if(!tool.use_tool(src, user, 30, volume=100)) - return TRUE + if(!tool.use_tool(src, user, 3 SECONDS, volume = 100)) + return ITEM_INTERACT_BLOCKING + if(core) to_chat(user, span_notice("You pry off [src]'s inner plate. You can see the core's green glow!")) - deconstruction_state = NUKESTATE_CORE_EXPOSED START_PROCESSING(SSobj, core) + deconstruction_state = NUKESTATE_CORE_EXPOSED else to_chat(user, span_notice("You pry off [src]'s inner plate. The inner chamber is empty, save for some beer stains.")) deconstruction_state = NUKESTATE_CORE_REMOVED update_appearance() new /obj/item/stack/sheet/iron(loc, 15) - return TRUE + return ITEM_INTERACT_SUCCESS + if(NUKESTATE_PANEL_REMOVED) to_chat(user, span_notice("You start levering [src]'s inner panel back into place...")) - if(!tool.use_tool(src, user, 30, volume = 100)) - return TRUE + if(!tool.use_tool(src, user, 3 SECONDS, volume = 100)) + return ITEM_INTERACT_BLOCKING + to_chat(user, span_notice("You lever [src]'s inner panel back into place.")) deconstruction_state = NUKESTATE_UNSCREWED update_appearance() - return TRUE - return FALSE + return ITEM_INTERACT_SUCCESS + + return ITEM_INTERACT_SKIP_TO_ATTACK + +/obj/machinery/nuclearbomb/screwdriver_act(mob/living/user, obj/item/tool) + if(!istype(tool, /obj/item/screwdriver/nuke)) + return ITEM_INTERACT_SKIP_TO_ATTACK + + switch(deconstruction_state) + if(NUKESTATE_INTACT) + if(istype(tool, /obj/item/screwdriver/nuke)) + to_chat(user, span_notice("You start removing [src]'s front panel's screws...")) + if(!tool.use_tool(src, user, 6 SECONDS, volume = 100)) + return ITEM_INTERACT_BLOCKING + + deconstruction_state = NUKESTATE_UNSCREWED + to_chat(user, span_notice("You remove the screws from [src]'s front panel.")) + update_appearance() + return ITEM_INTERACT_SUCCESS + + if(NUKESTATE_UNSCREWED) + if(istype(tool, /obj/item/screwdriver/nuke)) + to_chat(user, span_notice("You start screwing [src]'s front panel back in...")) + if(!tool.use_tool(src, user, 8 SECONDS, volume = 100)) + return ITEM_INTERACT_BLOCKING + + deconstruction_state = NUKESTATE_INTACT + to_chat(user, span_notice("You screw [src]'s front panel back into place.")) + deconstruction_state = NUKESTATE_INTACT + update_appearance() + return ITEM_INTERACT_SUCCESS + + return ITEM_INTERACT_SKIP_TO_ATTACK + +/obj/machinery/nuclearbomb/welder_act(mob/living/user, obj/item/tool) + if(deconstruction_state != NUKESTATE_PANEL_REMOVED) + return ITEM_INTERACT_SKIP_TO_ATTACK + + if(!tool.tool_start_check(user, amount = 1)) + return ITEM_INTERACT_BLOCKING + + to_chat(user, span_notice("You start cutting [src]'s inner plate...")) + if(!tool.use_tool(src, user, 8 SECONDS, volume=100)) + return ITEM_INTERACT_BLOCKING + + to_chat(user, span_notice("You cut [src]'s inner plate.")) + deconstruction_state = NUKESTATE_WELDED + update_appearance() + return ITEM_INTERACT_SUCCESS + /obj/machinery/nuclearbomb/attack_hand_secondary(mob/user, list/modifiers) if(deconstruction_state != NUKESTATE_CORE_EXPOSED) diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_bomb/beer_nuke.dm b/code/modules/antagonists/nukeop/equipment/nuclear_bomb/beer_nuke.dm index 9b4f4bdc32d6..9906fb9cdc6b 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclear_bomb/beer_nuke.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclear_bomb/beer_nuke.dm @@ -28,12 +28,11 @@ else . += span_danger("It's empty.") -/obj/machinery/nuclearbomb/beer/attackby(obj/item/weapon, mob/user, list/modifiers, list/attack_modifiers) - if(weapon.is_refillable()) - weapon.interact_with_atom(keg, user) // redirect refillable containers to the keg, allowing them to be filled - return TRUE // pretend we handled the attack, too. - - return ..() +/obj/machinery/nuclearbomb/beer/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!tool.is_refillable()) + return ..() + tool.interact_with_atom(keg, user) // redirect refillable containers to the keg, allowing them to be filled + return ITEM_INTERACT_SUCCESS /obj/machinery/nuclearbomb/beer/actually_explode() if(core) diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm index 26b6fe78cf47..1cbdf02c0097 100644 --- a/code/modules/antagonists/wizard/equipment/soulstone.dm +++ b/code/modules/antagonists/wizard/equipment/soulstone.dm @@ -289,21 +289,22 @@ if(IS_CULTIST(user) || HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED) || user.stat == DEAD) . += extra_desc -/obj/structure/constructshell/attackby(obj/item/O, mob/user, list/modifiers, list/attack_modifiers) - if(istype(O, /obj/item/soulstone)) - var/obj/item/soulstone/SS = O - if(!IS_CULTIST(user) && !HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED) && !SS.theme == THEME_HOLY) - to_chat(user, span_danger("An overwhelming feeling of dread comes over you as you attempt to place [SS] into the shell. It would be wise to be rid of this quickly.")) - if(isliving(user)) - var/mob/living/living_user = user - living_user.set_dizzy_if_lower(1 MINUTES) - return - if(SS.theme == THEME_HOLY && IS_CULTIST(user)) - SS.hot_potato(user) - return - SS.transfer_to_construct(src, user) - else - return ..() +/obj/structure/constructshell/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/soulstone)) + return NONE + + var/obj/item/soulstone/whispering_gem = tool + if(!IS_CULTIST(user) && !HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED) && whispering_gem.theme != THEME_HOLY) + to_chat(user, span_danger("An overwhelming feeling of dread comes over you as you attempt to place [whispering_gem] into the shell. It would be wise to be rid of this quickly.")) + user.set_dizzy_if_lower(1 MINUTES) + return ITEM_INTERACT_BLOCKING + + if(whispering_gem.theme == THEME_HOLY && IS_CULTIST(user)) + whispering_gem.hot_potato(user) + return ITEM_INTERACT_BLOCKING + + whispering_gem.transfer_to_construct(src, user) + return ITEM_INTERACT_SUCCESS /// Procs for moving soul in and out off stone diff --git a/code/modules/art/statues.dm b/code/modules/art/statues.dm index 62d5bbebbf8c..a4b2807ff6c1 100644 --- a/code/modules/art/statues.dm +++ b/code/modules/art/statues.dm @@ -35,17 +35,21 @@ default_unfasten_wrench(user, tool) return ITEM_INTERACT_SUCCESS -/obj/structure/statue/attackby(obj/item/W, mob/living/user, list/modifiers, list/attack_modifiers) +/obj/structure/statue/item_interaction(mob/living/user, obj/item/tool, list/modifiers) add_fingerprint(user) - if(W.tool_behaviour == TOOL_WELDER) - if(!W.tool_start_check(user, amount=1, heat_required = HIGH_TEMPERATURE_REQUIRED)) - return FALSE - user.balloon_alert(user, "slicing apart...") - if(W.use_tool(src, user, 40, volume=50)) - deconstruct(TRUE) - return return ..() +/obj/structure/statue/welder_act(mob/living/user, obj/item/tool) + add_fingerprint(user) + if(!tool.tool_start_check(user, amount=1, heat_required = HIGH_TEMPERATURE_REQUIRED)) + return ITEM_INTERACT_BLOCKING + + user.balloon_alert(user, "slicing apart...") + if(!tool.use_tool(src, user, 4 SECONDS, volume = 50)) + return ITEM_INTERACT_BLOCKING + + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS /obj/structure/statue/atom_deconstruct(disassembled = TRUE) var/amount_mod = disassembled ? 0 : -2 diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm index f639d29e79f4..04cf6b9fc38f 100644 --- a/code/modules/assembly/assembly.dm +++ b/code/modules/assembly/assembly.dm @@ -120,42 +120,42 @@ return . = ..() -/obj/item/assembly/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) - if(isassembly(attacking_item)) - var/obj/item/assembly/new_assembly = attacking_item +/obj/item/assembly/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(isassembly(tool)) + var/obj/item/assembly/new_assembly = tool // Check both our's and their's assembly flags to see if either should not duplicate // If so, and we match types, don't create a holder - block it if(((new_assembly.assembly_flags|assembly_flags) & ASSEMBLY_NO_DUPLICATES) && istype(new_assembly, type)) balloon_alert(user, "can't attach another [new_assembly.name]!") - return + return ITEM_INTERACT_BLOCKING + if(new_assembly.secured) balloon_alert(user, "[new_assembly.name] is not attachable!") - return + return ITEM_INTERACT_BLOCKING + if(secured) balloon_alert(user, "[name] is not attachable!") - return + return ITEM_INTERACT_BLOCKING holder = new /obj/item/assembly_holder(drop_location()) holder.assemble(src, new_assembly, user) holder.balloon_alert(user, "parts combined") - return + return ITEM_INTERACT_SUCCESS - if(istype(attacking_item, /obj/item/assembly_holder)) - var/obj/item/assembly_holder/added_to_holder = attacking_item + if(istype(tool, /obj/item/assembly_holder)) + var/obj/item/assembly_holder/added_to_holder = tool added_to_holder.try_add_assembly(src, user) - return + return ITEM_INTERACT_BLOCKING - return ..() + return NONE -/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/I) - if(..()) - return TRUE +/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/tool) if(toggle_secure()) to_chat(user, span_notice("\The [src] is ready!")) else to_chat(user, span_notice("\The [src] can now be attached!")) add_fingerprint(user) - return TRUE + return ITEM_INTERACT_SUCCESS /obj/item/assembly/examine(mob/user) . = ..() diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index 769b60ecb04c..99d6273481cc 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -317,11 +317,11 @@ if (.) new /obj/effect/temp_visual/borgflash(get_turf(src)) -/obj/item/assembly/flash/cyborg/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers) - return +/obj/item/assembly/flash/cyborg/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + return ITEM_INTERACT_BLOCKING /obj/item/assembly/flash/cyborg/screwdriver_act(mob/living/user, obj/item/I) - return + return ITEM_INTERACT_BLOCKING /obj/item/assembly/flash/memorizer name = "memorizer" diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm index e9c57626bc67..95748cab157f 100644 --- a/code/modules/assembly/holder.dm +++ b/code/modules/assembly/holder.dm @@ -134,12 +134,12 @@ for(var/obj/item/assembly/assembly as anything in assemblies) assembly.attack_hand(user, modifiers) // Note override in assembly.dm to prevent side effects here -/obj/item/assembly_holder/attackby(obj/item/weapon, mob/user, list/modifiers, list/attack_modifiers) - if(isassembly(weapon)) - try_add_assembly(weapon, user) - return +/obj/item/assembly_holder/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!isassembly(tool)) + return NONE - return ..() + try_add_assembly(tool, user) + return ITEM_INTERACT_SUCCESS /obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/tool) diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index a65064634ea2..48574d119cbd 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -122,14 +122,18 @@ update_appearance() -/obj/item/assembly/signaler/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers) - if(issignaler(W)) - var/obj/item/assembly/signaler/signaler2 = W - if(secured && signaler2.secured) - code = signaler2.code - set_frequency(signaler2.frequency) - to_chat(user, "You transfer the frequency and code of \the [signaler2.name] to \the [name]") - ..() +/obj/item/assembly/signaler/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!issignaler(tool)) + return ..() + + var/obj/item/assembly/signaler/sister_signaler = tool + if(!secured || !sister_signaler.secured) + return ..() + + code = sister_signaler.code + set_frequency(sister_signaler.frequency) + to_chat(user, "You transfer the frequency and code of \the [sister_signaler.name] to \the [name]") + return ITEM_INTERACT_SUCCESS /obj/item/assembly/signaler/attack_self_secondary(mob/user, modifiers) . = ..() @@ -190,10 +194,11 @@ /obj/item/assembly/signaler/cyborg -/obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers) - return +/obj/item/assembly/signaler/cyborg/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + return ITEM_INTERACT_BLOCKING + /obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I) - return + return ITEM_INTERACT_BLOCKING /obj/item/assembly/signaler/internal name = "internal remote signaling device" @@ -201,11 +206,11 @@ /obj/item/assembly/signaler/internal/ui_state(mob/user) return GLOB.inventory_state -/obj/item/assembly/signaler/internal/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers) - return +/obj/item/assembly/signaler/internal/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + return ITEM_INTERACT_BLOCKING /obj/item/assembly/signaler/internal/screwdriver_act(mob/living/user, obj/item/I) - return + return ITEM_INTERACT_BLOCKING /obj/item/assembly/signaler/internal/can_interact(mob/user) if(ispAI(user)) diff --git a/code/modules/atmospherics/machinery/air_alarm/air_alarm_interact.dm b/code/modules/atmospherics/machinery/air_alarm/air_alarm_interact.dm index bc6296741583..d0a50d1ae121 100644 --- a/code/modules/atmospherics/machinery/air_alarm/air_alarm_interact.dm +++ b/code/modules/atmospherics/machinery/air_alarm/air_alarm_interact.dm @@ -100,57 +100,73 @@ if((buildstage == AIR_ALARM_BUILD_COMPLETE)) new /obj/item/stack/cable_coil(loc, 3) -/obj/machinery/airalarm/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers) +/obj/machinery/airalarm/item_interaction(mob/living/user, obj/item/tool, list/modifiers) update_last_used(user) switch(buildstage) if(AIR_ALARM_BUILD_COMPLETE) - if(W.GetID())// trying to unlock the interface with an ID card + if(tool.GetID())// trying to unlock the interface with an ID card togglelock(user) - return - else if(panel_open && is_wire_tool(W)) + return ITEM_INTERACT_SUCCESS + + if(panel_open && is_wire_tool(tool)) wires.interact(user) - return + return ITEM_INTERACT_SUCCESS + + return NONE + if(AIR_ALARM_BUILD_NO_WIRES) - if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/cable = W - if(cable.get_amount() < 5) - to_chat(user, span_warning("You need five lengths of cable to wire the air alarm!")) - return - user.visible_message(span_notice("[user.name] wires the air alarm."), \ - span_notice("You start wiring the air alarm...")) - if (do_after(user, 2 SECONDS, target = src)) - if (cable.get_amount() >= 5 && buildstage == AIR_ALARM_BUILD_NO_WIRES) - cable.use(5) - to_chat(user, span_notice("You wire the air alarm.")) - wires.repair() - aidisabled = FALSE - locked = FALSE - shorted = FALSE - danger_level = AIR_ALARM_ALERT_NONE - buildstage = AIR_ALARM_BUILD_COMPLETE - select_mode(user, /datum/air_alarm_mode/filtering) - update_appearance() - return + if(!istype(tool, /obj/item/stack/cable_coil)) + return NONE + + var/obj/item/stack/cable_coil/cable = tool + if(cable.get_amount() < 5) + to_chat(user, span_warning("You need five lengths of cable to wire the air alarm!")) + return ITEM_INTERACT_BLOCKING + + user.visible_message(span_notice("[user.name] wires the air alarm."), \ + span_notice("You start wiring the air alarm...")) + if(!do_after(user, 2 SECONDS, target = src)) + return ITEM_INTERACT_BLOCKING + + if(cable.get_amount() < 5 || buildstage != AIR_ALARM_BUILD_NO_WIRES) + return ITEM_INTERACT_BLOCKING + + cable.use(5) + to_chat(user, span_notice("You wire the air alarm.")) + wires.repair() + aidisabled = FALSE + locked = FALSE + shorted = FALSE + danger_level = AIR_ALARM_ALERT_NONE + buildstage = AIR_ALARM_BUILD_COMPLETE + select_mode(user, /datum/air_alarm_mode/filtering) + update_appearance() + return ITEM_INTERACT_SUCCESS + if(AIR_ALARM_BUILD_NO_CIRCUIT) - if(istype(W, /obj/item/electronics/airalarm)) - if(user.temporarilyRemoveItemFromInventory(W)) - to_chat(user, span_notice("You insert the circuit.")) - buildstage = AIR_ALARM_BUILD_NO_WIRES - update_appearance() - qdel(W) - return - - if(istype(W, /obj/item/electroadaptive_pseudocircuit)) - var/obj/item/electroadaptive_pseudocircuit/P = W - if(!P.adapt_circuit(user, circuit_cost = 0.025 * STANDARD_CELL_CHARGE)) - return + if(istype(tool, /obj/item/electronics/airalarm)) + if(!user.temporarilyRemoveItemFromInventory(tool)) + return ITEM_INTERACT_BLOCKING + + to_chat(user, span_notice("You insert the circuit.")) + buildstage = AIR_ALARM_BUILD_NO_WIRES + update_appearance() + qdel(tool) + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/electroadaptive_pseudocircuit)) + if(!astype(tool, /obj/item/electroadaptive_pseudocircuit).adapt_circuit(user, circuit_cost = 0.025 * STANDARD_CELL_CHARGE)) + return ITEM_INTERACT_BLOCKING + user.visible_message(span_notice("[user] fabricates a circuit and places it into [src]."), \ span_notice("You adapt an air alarm circuit and slot it into the assembly.")) buildstage = AIR_ALARM_BUILD_NO_WIRES update_appearance() - return + return ITEM_INTERACT_SUCCESS - return ..() + return NONE + + return NONE /obj/machinery/airalarm/proc/reset(wire) switch(wire) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index f7ac8c29e58e..9fb051576635 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -409,14 +409,16 @@ nodes[nodes.Find(reference)] = null update_appearance() -/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers) - if(istype(W, /obj/item/pipe)) //lets you autodrop - var/obj/item/pipe/pipe = W - if(user.dropItemToGround(pipe)) - pipe.set_piping_layer(piping_layer) //align it with us - return TRUE - else - return ..() +/obj/machinery/atmospherics/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/pipe)) //lets you autodrop + return NONE + + var/obj/item/pipe/pipe = tool + if(!user.dropItemToGround(pipe)) + return ITEM_INTERACT_BLOCKING + + pipe.set_piping_layer(piping_layer) //align it with us + return ITEM_INTERACT_SUCCESS /obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I) if(!can_unwrench(user)) diff --git a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm index a469f4cc2e74..983b136f5d4f 100644 --- a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm +++ b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm @@ -160,24 +160,27 @@ /obj/machinery/electrolyzer/crowbar_act(mob/living/user, obj/item/tool) return default_deconstruction_crowbar(user, tool) -/obj/machinery/electrolyzer/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers) +/obj/machinery/electrolyzer/item_interaction(mob/living/user, obj/item/tool, list/modifiers) add_fingerprint(user) - if(istype(I, /obj/item/stock_parts/power_store/cell)) - if(!panel_open) - balloon_alert(user, "open panel!") - return - if(cell) - balloon_alert(user, "cell inside!") - return - if(!user.transferItemToLoc(I, src)) - return - cell = I - I.add_fingerprint(usr) - balloon_alert(user, "inserted cell") - SStgui.update_uis(src) + if(!istype(tool, /obj/item/stock_parts/power_store/cell)) + return NONE - return - return ..() + if(!panel_open) + balloon_alert(user, "open panel!") + return ITEM_INTERACT_BLOCKING + + if(cell) + balloon_alert(user, "cell inside!") + return ITEM_INTERACT_BLOCKING + + if(!user.transferItemToLoc(tool, src)) + return ITEM_INTERACT_BLOCKING + + cell = tool + tool.add_fingerprint(usr) + balloon_alert(user, "inserted cell") + SStgui.update_uis(src) + return ITEM_INTERACT_SUCCESS /obj/machinery/electrolyzer/click_alt(mob/user) if(panel_open) diff --git a/code/modules/atmospherics/machinery/components/tank.dm b/code/modules/atmospherics/machinery/components/tank.dm index 8d9381df2732..2e949dc858e7 100644 --- a/code/modules/atmospherics/machinery/components/tank.dm +++ b/code/modules/atmospherics/machinery/components/tank.dm @@ -568,10 +568,14 @@ if(TANK_PLATING_UNSECURED) icon_state = "plated_frame" -/obj/structure/tank_frame/attackby(obj/item/item, mob/living/user, list/modifiers, list/attack_modifiers) - if(construction_state == TANK_FRAME && isstack(item) && add_plating(user, item)) - return - return ..() +/obj/structure/tank_frame/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(construction_state != TANK_FRAME || !isstack(tool)) + return ..() + + if(!add_plating(user, tool)) + return ITEM_INTERACT_BLOCKING + + return ITEM_INTERACT_SUCCESS /obj/structure/tank_frame/wrench_act(mob/living/user, obj/item/tool) . = ..() diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm index b4c48341f835..90928274ab9f 100644 --- a/code/modules/atmospherics/machinery/pipes/pipes.dm +++ b/code/modules/atmospherics/machinery/pipes/pipes.dm @@ -89,14 +89,15 @@ return air_temporary.remove(amount) return parent.air.remove(amount) -/obj/machinery/atmospherics/pipe/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) - if(istype(item, /obj/item/pipe_meter)) - var/obj/item/pipe_meter/meter = item - user.dropItemToGround(meter) - meter.setAttachLayer(piping_layer) - else +/obj/machinery/atmospherics/pipe/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/pipe_meter)) return ..() + var/obj/item/pipe_meter/meter = tool + user.dropItemToGround(meter) + meter.setAttachLayer(piping_layer) + return ITEM_INTERACT_SUCCESS + /obj/machinery/atmospherics/pipe/return_pipenet() return parent diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 79bb5370796a..0d83f675097b 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -412,22 +412,25 @@ if(internal_cell) internal_cell.forceMove(drop_location()) -/obj/machinery/portable_atmospherics/canister/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) - if(istype(item, /obj/item/stock_parts/power_store/cell)) - var/obj/item/stock_parts/power_store/cell/active_cell = item - if(!panel_open) - balloon_alert(user, "open hatch first!") - return TRUE - if(!user.transferItemToLoc(active_cell, src)) - return TRUE - if(internal_cell) - user.put_in_hands(internal_cell) - balloon_alert(user, "you replace the cell") - else - balloon_alert(user, "you install the cell") - internal_cell = active_cell - return TRUE - return ..() +/obj/machinery/portable_atmospherics/canister/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/stock_parts/power_store/cell)) + return ..() + + var/obj/item/stock_parts/power_store/cell/active_cell = tool + if(!panel_open) + balloon_alert(user, "open hatch first!") + return ITEM_INTERACT_BLOCKING + + if(!user.transferItemToLoc(active_cell, src)) + return ITEM_INTERACT_BLOCKING + + if(internal_cell) + user.put_in_hands(internal_cell) + balloon_alert(user, "you replace the cell") + else + balloon_alert(user, "you install the cell") + internal_cell = active_cell + return ITEM_INTERACT_SUCCESS /obj/machinery/portable_atmospherics/canister/screwdriver_act(mob/living/user, obj/item/screwdriver) return default_deconstruction_screwdriver(user, screwdriver) diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm index 7926b5b4173c..798e1c45fe5d 100644 --- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm +++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm @@ -271,10 +271,14 @@ update_appearance() return TRUE -/obj/machinery/portable_atmospherics/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) - if(istype(item, /obj/item/tank)) - return replace_tank(user, FALSE, item) - return ..() +/obj/machinery/portable_atmospherics/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/tank)) + return NONE + + if(!replace_tank(user, FALSE, tool)) + return ITEM_INTERACT_BLOCKING + + return ITEM_INTERACT_SUCCESS /obj/machinery/portable_atmospherics/wrench_act(mob/living/user, obj/item/wrench) if(machine_stat & BROKEN) diff --git a/code/modules/awaymissions/mission_code/Cabin.dm b/code/modules/awaymissions/mission_code/Cabin.dm index a4d3358a120c..d3b8b10fa132 100644 --- a/code/modules/awaymissions/mission_code/Cabin.dm +++ b/code/modules/awaymissions/mission_code/Cabin.dm @@ -50,17 +50,19 @@ active = FALSE toggleFirepit() -/obj/structure/firepit/attackby(obj/item/W,mob/living/user,list/modifiers) - if(!active) - var/msg = W.ignition_effect(src, user) - if(msg) - active = TRUE - visible_message(msg) - toggleFirepit() - else - return ..() - else - W.fire_act() +/obj/structure/firepit/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(active) + tool.fire_act() + return ITEM_INTERACT_SUCCESS + + var/msg = tool.ignition_effect(src, user) + if(!msg) + return NONE + + visible_message(msg) + toggleFirepit() + return ITEM_INTERACT_SUCCESS + /obj/structure/firepit/proc/toggleFirepit() active = !active diff --git a/code/modules/awaymissions/super_secret_room.dm b/code/modules/awaymissions/super_secret_room.dm index 2fa38219a1f0..38f8ae99aa5e 100644 --- a/code/modules/awaymissions/super_secret_room.dm +++ b/code/modules/awaymissions/super_secret_room.dm @@ -88,8 +88,9 @@ speaking = FALSE times_spoken_to++ -/obj/structure/speaking_tile/attackby(obj/item/W, mob/user, list/modifiers, list/attack_modifiers) - return interact(user) +/obj/structure/speaking_tile/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + interact(user) + return ITEM_INTERACT_SUCCESS /obj/structure/speaking_tile/attack_paw(mob/user, list/modifiers) return interact(user) diff --git a/code/modules/basketball/hoop.dm b/code/modules/basketball/hoop.dm index 7302a9fd4168..922e63e1528f 100644 --- a/code/modules/basketball/hoop.dm +++ b/code/modules/basketball/hoop.dm @@ -90,27 +90,28 @@ scoreboard.add_overlay(tens_overlay) scoreboard.add_overlay(emissive_tens_overlay) -/obj/structure/hoop/attackby(obj/item/ball, mob/living/baller, list/modifiers, list/attack_modifiers) - if(!baller.can_perform_action(src, NEED_HANDS|FORBID_TELEKINESIS_REACH)) - return // TK users aren't allowed to dunk +/obj/structure/hoop/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!user.can_perform_action(src, NEED_HANDS|FORBID_TELEKINESIS_REACH)) + return NONE// TK users aren't allowed to dunk - if(!baller.transfer_item_to_turf(ball, drop_location())) - return + if(!user.transfer_item_to_turf(tool, drop_location())) + return ITEM_INTERACT_BLOCKING - var/dunk_dir = get_dir(baller, src) + var/dunk_dir = get_dir(user, src) var/dunk_pixel_z = (dunk_dir & SOUTH) ? -16 : 16 var/dunk_pixel_w = ((dunk_dir & EAST) && 16) || ((dunk_dir & WEST) && -16) || 0 - animate(baller, pixel_w = dunk_pixel_w, pixel_z = dunk_pixel_z, time = 0.5 SECONDS, easing = BOUNCE_EASING|EASE_IN|EASE_OUT, flags = ANIMATION_PARALLEL|ANIMATION_RELATIVE) + animate(user, pixel_w = dunk_pixel_w, pixel_z = dunk_pixel_z, time = 0.5 SECONDS, easing = BOUNCE_EASING|EASE_IN|EASE_OUT, flags = ANIMATION_PARALLEL|ANIMATION_RELATIVE) animate(pixel_w = -dunk_pixel_w, pixel_z = -dunk_pixel_z, time = 0.5 SECONDS, flags = ANIMATION_RELATIVE) - visible_message(span_warning("[baller] dunks [ball] into \the [src]!")) - baller.add_mood_event("basketball", /datum/mood_event/basketball_dunk) - score(ball, baller, 2) + visible_message(span_warning("[user] dunks [tool] into \the [src]!")) + user.add_mood_event("basketball", /datum/mood_event/basketball_dunk) + score(tool, user, 2) - if(istype(ball, /obj/item/toy/basketball)) - baller.adjust_stamina_loss(STAMINA_COST_DUNKING) + if(istype(tool, /obj/item/toy/basketball)) + user.adjust_stamina_loss(STAMINA_COST_DUNKING) + return ITEM_INTERACT_SUCCESS /obj/structure/hoop/attack_hand(mob/living/baller, list/modifiers) . = ..() diff --git a/code/modules/capture_the_flag/ctf_game.dm b/code/modules/capture_the_flag/ctf_game.dm index e7316c12ec67..8bd7806dcc86 100644 --- a/code/modules/capture_the_flag/ctf_game.dm +++ b/code/modules/capture_the_flag/ctf_game.dm @@ -168,12 +168,17 @@ player_mob.add_traits(player_traits, CAPTURE_THE_FLAG_TRAIT) return player_mob //used in medisim_game.dm -/obj/machinery/ctf/spawner/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) - if(istype(item, /obj/item/ctf_flag)) - var/obj/item/ctf_flag/flag = item - if(flag.team != team) - ctf_game.capture_flag(team, user, team_span, flag) - flag.reset_flag(capture = TRUE) //This might be buggy, confirm and fix if it is. +/obj/machinery/ctf/spawner/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/ctf_flag)) + return NONE + + var/obj/item/ctf_flag/flag = tool + if(flag.team == team) + return ITEM_INTERACT_BLOCKING + + ctf_game.capture_flag(team, user, team_span, flag) + flag.reset_flag(capture = TRUE) //This might be buggy, confirm and fix if it is. + return ITEM_INTERACT_SUCCESS ///A flag used for the CTF minigame. /obj/item/ctf_flag @@ -265,14 +270,17 @@ user.set_anchored(TRUE) user.status_flags &= ~CANPUSH -/obj/item/ctf_flag/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) - if(!istype(item, /obj/item/ctf_flag)) - return ..() +/obj/item/ctf_flag/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/ctf_flag)) + return NONE + + var/obj/item/ctf_flag/flag = tool + if(flag.team == team) + return ITEM_INTERACT_BLOCKING - var/obj/item/ctf_flag/flag = item - if(flag.team != team) - to_chat(user, span_userdanger("Take \the [initial(flag.name)] to your team's controller!")) - user.playsound_local(get_turf(user), 'sound/machines/buzz/buzz-sigh.ogg', 100, vary = FALSE, use_reverb = FALSE) + to_chat(user, span_userdanger("Take \the [initial(flag.name)] to your team's controller!")) + user.playsound_local(get_turf(user), 'sound/machines/buzz/buzz-sigh.ogg', 100, vary = FALSE, use_reverb = FALSE) + return ITEM_INTERACT_SUCCESS /obj/item/ctf_flag/dropped(mob/user) ..() @@ -364,8 +372,9 @@ scores += UNLINT("[ctf_team.team_color] - [ctf_team.points]/[ctf_game.points_to_win]\n") balloon_alert_to_viewers(scores) -/obj/machinery/ctf/control_point/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) +/obj/machinery/ctf/control_point/item_interaction(mob/living/user, obj/item/tool, list/modifiers) capture(user) + return ITEM_INTERACT_SUCCESS /obj/machinery/ctf/control_point/attack_hand(mob/user, list/modifiers) . = ..() diff --git a/code/modules/cargo/universal_scanner.dm b/code/modules/cargo/universal_scanner.dm index 11189aa5798e..2456ccb04f96 100644 --- a/code/modules/cargo/universal_scanner.dm +++ b/code/modules/cargo/universal_scanner.dm @@ -70,32 +70,38 @@ return ITEM_INTERACT_SUCCESS return NONE -/obj/item/universal_scanner/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) - . = ..() - if(scanning_mode == SCAN_SALES_TAG && isidcard(attacking_item)) - var/obj/item/card/id/potential_acc = attacking_item - if(potential_acc.registered_account) - if(payments_acc == potential_acc.registered_account) - to_chat(user, span_notice("ID card already registered.")) - return - else - payments_acc = potential_acc.registered_account - playsound(src, 'sound/machines/ping.ogg', 40, TRUE) - to_chat(user, span_notice("[src] registers the ID card. Tag a wrapped item to create a barcode.")) - else if(!potential_acc.registered_account) +/obj/item/universal_scanner/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(scanning_mode == SCAN_SALES_TAG && isidcard(tool)) + var/obj/item/card/id/potential_acc = tool + if(!potential_acc.registered_account) to_chat(user, span_warning("This ID card has no account registered!")) - return - if(istype(attacking_item, /obj/item/paper)) - if (!(paper_count >= max_paper_count)) - paper_count += PAPER_PER_SHEET - qdel(attacking_item) - if (paper_count >= max_paper_count) - paper_count = max_paper_count - to_chat(user, span_notice("[src]'s paper supply is now full.")) - return - to_chat(user, span_notice("You refill [src]'s paper supply, you have [paper_count] left.")) - else + return ITEM_INTERACT_BLOCKING + + if(payments_acc == potential_acc.registered_account) + to_chat(user, span_notice("ID card already registered.")) + return ITEM_INTERACT_BLOCKING + + payments_acc = potential_acc.registered_account + playsound(src, 'sound/machines/ping.ogg', 40, TRUE) + to_chat(user, span_notice("[src] registers the ID card. Tag a wrapped item to create a barcode.")) + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/paper)) + if (paper_count >= max_paper_count) to_chat(user, span_notice("[src]'s paper supply is full.")) + return ITEM_INTERACT_BLOCKING + + paper_count += PAPER_PER_SHEET + qdel(tool) + if (paper_count >= max_paper_count) + paper_count = max_paper_count + to_chat(user, span_notice("[src]'s paper supply is now full.")) + return ITEM_INTERACT_SUCCESS + + to_chat(user, span_notice("You refill [src]'s paper supply, you have [paper_count] left.")) + return ITEM_INTERACT_SUCCESS + + return NONE /obj/item/universal_scanner/attack_self_secondary(mob/user, modifiers) . = ..() diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm index 15f299eaacf0..186155043fce 100644 --- a/code/modules/clothing/gloves/_gloves.dm +++ b/code/modules/clothing/gloves/_gloves.dm @@ -72,19 +72,23 @@ return FALSE // We don't want to cut dyed gloves. return TRUE -/obj/item/clothing/gloves/attackby(obj/item/tool, mob/user, list/modifiers, list/attack_modifiers) +/obj/item/clothing/gloves/item_interaction(mob/living/user, obj/item/tool, list/modifiers) . = ..() - if(.) - return + if(ITEM_INTERACT_ANY_BLOCKER & .) + return . + if(tool.tool_behaviour != TOOL_WIRECUTTER && !tool.get_sharpness()) - return + return . + if (!can_cut_with(tool)) - return + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "cutting off fingertips...") if(!do_after(user, 3 SECONDS, target=src, extra_checks = CALLBACK(src, PROC_REF(can_cut_with), tool))) - return + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "cut fingertips off") qdel(src) user.put_in_hands(new cut_type) - return TRUE + return ITEM_INTERACT_SUCCESS diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index 3bdf4aeca0c0..1075b50824c9 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -45,30 +45,30 @@ . = ..() AddComponent(/datum/component/seclite_attachable, light_icon_state = "flight") -/obj/item/clothing/head/helmet/sec/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) - if(issignaler(attacking_item)) - var/obj/item/assembly/signaler/attached_signaler = attacking_item - // There's a flashlight in us. Remove it first, or it'll be lost forever! - var/obj/item/flashlight/seclite/blocking_us = locate() in src - if(blocking_us) - to_chat(user, span_warning("[blocking_us] is in the way, remove it first!")) - return TRUE - - if(!attached_signaler.secured) - to_chat(user, span_warning("Secure [attached_signaler] first!")) - return TRUE - - to_chat(user, span_notice("You add [attached_signaler] to [src].")) - - qdel(attached_signaler) - var/obj/item/bot_assembly/secbot/secbot_frame = new(drop_location()) - var/held_index = user.is_holding(src) - qdel(src) - if (held_index) - user.put_in_hand(secbot_frame, held_index) - return TRUE - - return ..() +/obj/item/clothing/head/helmet/sec/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!issignaler(tool)) + return ..() + + var/obj/item/assembly/signaler/attached_signaler = tool + // There's a flashlight in us. Remove it first, or it'll be lost forever! + var/obj/item/flashlight/seclite/blocking_us = locate() in src + if(blocking_us) + to_chat(user, span_warning("[blocking_us] is in the way, remove it first!")) + return ITEM_INTERACT_BLOCKING + + if(!attached_signaler.secured) + to_chat(user, span_warning("Secure [attached_signaler] first!")) + return ITEM_INTERACT_BLOCKING + + to_chat(user, span_notice("You add [attached_signaler] to [src].")) + + qdel(attached_signaler) + var/obj/item/bot_assembly/secbot/secbot_frame = new(drop_location()) + var/held_index = user.is_holding(src) + qdel(src) + if (held_index) + user.put_in_hand(secbot_frame, held_index) + return ITEM_INTERACT_SUCCESS /obj/item/clothing/head/helmet/sec/attack_self(mob/user) . = ..() diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm index 48bbf32999e8..f843fe0437ce 100644 --- a/code/modules/clothing/head/jobs.dm +++ b/code/modules/clothing/head/jobs.dm @@ -327,28 +327,31 @@ return . -/obj/item/clothing/head/fedora/inspector_hat/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) +/obj/item/clothing/head/fedora/inspector_hat/item_interaction(mob/living/user, obj/item/tool, list/modifiers) . = ..() + if(ITEM_INTERACT_ANY_BLOCKER & .) + return . if(LAZYLEN(contents) >= max_items) balloon_alert(user, "full!") - return - if(item.w_class > max_weight) + return ITEM_INTERACT_BLOCKING + + if(tool.w_class > max_weight) balloon_alert(user, "too big!") - return + return ITEM_INTERACT_BLOCKING var/desired_phrase = tgui_input_text(user, "What is the activation phrase?", "Activation phrase", "gadget", max_length = 26) if(!desired_phrase || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) - return + return ITEM_INTERACT_BLOCKING - if(item.loc != user || !user.transferItemToLoc(item, src)) - return + if(tool.loc != user || !user.transferItemToLoc(tool, src)) + return ITEM_INTERACT_BLOCKING - to_chat(user, span_notice("You install [item] into the [thtotext(contents.len)] slot of [src].")) + to_chat(user, span_notice("You install [tool] into the [thtotext(contents.len)] slot of [src].")) playsound(src, 'sound/machines/click.ogg', 30, TRUE) - set_phrase(desired_phrase,item) + set_phrase(desired_phrase, tool) - return TRUE + return ITEM_INTERACT_SUCCESS /obj/item/clothing/head/fedora/inspector_hat/attack_self(mob/user) . = ..() diff --git a/code/modules/clothing/head/tophat.dm b/code/modules/clothing/head/tophat.dm index 2b587287aa2e..eae7766e5485 100644 --- a/code/modules/clothing/head/tophat.dm +++ b/code/modules/clothing/head/tophat.dm @@ -10,10 +10,12 @@ /// Cooldown for how often we can pull rabbits out of here COOLDOWN_DECLARE(rabbit_cooldown) -/obj/item/clothing/head/hats/tophat/attackby(obj/item/hitby_item, mob/user, list/modifiers, list/attack_modifiers) - . = ..() - if(istype(hitby_item, /obj/item/gun/magic/wand)) - abracadabra(hitby_item, user) +/obj/item/clothing/head/hats/tophat/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/gun/magic/wand)) + return ..() + + abracadabra(tool, user) + return ITEM_INTERACT_SUCCESS /obj/item/clothing/head/hats/tophat/proc/abracadabra(obj/item/hitby_wand, mob/magician) if(!COOLDOWN_FINISHED(src, rabbit_cooldown)) diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index 40f3251968b9..28170db5b3ef 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -95,11 +95,10 @@ GLOBAL_LIST_INIT(clown_mask_options, list( var/mob/wearer = loc wearer.update_worn_mask() -/obj/item/clothing/mask/gas/attackby(obj/item/tool, mob/user) +/obj/item/clothing/mask/gas/item_interaction(mob/living/user, obj/item/tool, list/modifiers) var/valid_wearer = ismob(loc) var/mob/wearer = loc if(istype(tool, /obj/item/cigarette)) - if(max_filters <= 0 || cig) balloon_alert(user, "can't hold that!") return ..() @@ -115,22 +114,26 @@ GLOBAL_LIST_INIT(clown_mask_options, list( cig.forceMove(src) if(valid_wearer) wearer.update_worn_mask() - return TRUE + return ITEM_INTERACT_SUCCESS if(cig) - var/cig_attackby = cig.attackby(tool, user) + var/cig_interaction = cig.item_interaction(user, tool) if(valid_wearer) wearer.update_worn_mask() - return cig_attackby + return cig_interaction + if(!istype(tool, /obj/item/gas_filter)) return ..() + if(LAZYLEN(gas_filters) >= max_filters) return ..() + if(!user.transferItemToLoc(tool, src)) return ..() + LAZYADD(gas_filters, tool) has_filter = TRUE - return TRUE + return ITEM_INTERACT_SUCCESS /obj/item/clothing/mask/gas/attack_hand_secondary(mob/user, list/modifiers) if(cig) diff --git a/code/modules/clothing/neck/collar_bomb.dm b/code/modules/clothing/neck/collar_bomb.dm index 10450f53c94e..5d3c598fb2d4 100644 --- a/code/modules/clothing/neck/collar_bomb.dm +++ b/code/modules/clothing/neck/collar_bomb.dm @@ -38,12 +38,13 @@ return . += span_tinynotice("It has a [EXAMINE_HINT("wire")] panel that could be interacted with...") -/obj/item/clothing/neck/collar_bomb/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) - if(is_wire_tool(item)) - wires.interact(user) - else +/obj/item/clothing/neck/collar_bomb/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!is_wire_tool(tool)) return ..() + wires.interact(user) + return ITEM_INTERACT_SUCCESS + /obj/item/clothing/neck/collar_bomb/equipped(mob/user, slot, initial = FALSE) . = ..() if(slot == ITEM_SLOT_NECK) diff --git a/code/modules/clothing/shoes/sneakers.dm b/code/modules/clothing/shoes/sneakers.dm index 1faed5d2eef0..4e82da67cd41 100644 --- a/code/modules/clothing/shoes/sneakers.dm +++ b/code/modules/clothing/shoes/sneakers.dm @@ -157,16 +157,11 @@ return return ..() -/obj/item/clothing/shoes/sneakers/orange/pre_attack(atom/movable/attacking_movable, mob/living/user, list/modifiers, list/attack_modifiers) - if(attached_cuffs || attacking_movable.type != /obj/item/restraints/handcuffs) +/obj/item/clothing/shoes/sneakers/orange/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(attached_cuffs || tool.type != /obj/item/restraints/handcuffs) // Note: not using istype here because we want to ignore all subtypes return ..() - attacking_movable.forceMove(src) - return TRUE - -/obj/item/clothing/shoes/sneakers/orange/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) - if(attached_cuffs || attacking_item.type != /obj/item/restraints/handcuffs) // Note: not using istype here because we want to ignore all subtypes - return ..() - attacking_item.forceMove(src) + tool.forceMove(src) + return ITEM_INTERACT_SUCCESS /obj/item/clothing/shoes/sneakers/orange/can_mob_unequip(mob/user) if(user.get_item_by_slot(slot_flags) == src && attached_cuffs) diff --git a/code/modules/clothing/spacesuits/_spacesuits.dm b/code/modules/clothing/spacesuits/_spacesuits.dm index 4659c27100e2..33095e716497 100644 --- a/code/modules/clothing/spacesuits/_spacesuits.dm +++ b/code/modules/clothing/spacesuits/_spacesuits.dm @@ -242,17 +242,20 @@ return ITEM_INTERACT_SUCCESS // object handling for accessing features of the suit -/obj/item/clothing/suit/space/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers) - if(!cell_cover_open || !istype(I, /obj/item/stock_parts/power_store/cell)) +/obj/item/clothing/suit/space/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!cell_cover_open || !istype(tool, /obj/item/stock_parts/power_store/cell)) return ..() + if(cell) to_chat(user, span_warning("[src] already has a cell installed.")) - return - if(user.transferItemToLoc(I, src)) - cell = I - to_chat(user, span_notice("You successfully install \the [cell] into [src].")) - update_hud_icon(user) - return + return ITEM_INTERACT_BLOCKING + + if(!user.transferItemToLoc(tool, src)) + return ITEM_INTERACT_BLOCKING + cell = tool + to_chat(user, span_notice("You successfully install \the [cell] into [src].")) + update_hud_icon(user) + return ITEM_INTERACT_SUCCESS /// Open the cell cover when ALT+Click on the suit /obj/item/clothing/suit/space/click_alt(mob/living/user) diff --git a/code/modules/clothing/spacesuits/softsuit.dm b/code/modules/clothing/spacesuits/softsuit.dm index d6cb7bf0c898..3caf9ec0a55a 100644 --- a/code/modules/clothing/spacesuits/softsuit.dm +++ b/code/modules/clothing/spacesuits/softsuit.dm @@ -53,19 +53,19 @@ . = ..() . += span_notice("You can start constructing a critter sized mecha with a [span_bold("cyborg leg")].") -/obj/item/clothing/head/helmet/space/eva/attackby(obj/item/attacked_with, mob/user, list/modifiers, list/attack_modifiers) - . = ..() - if(.) - return - if(!istype(attacked_with, /obj/item/bodypart/leg/left/robot) && !istype(attacked_with, /obj/item/bodypart/leg/right/robot)) - return +/obj/item/clothing/head/helmet/space/eva/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/bodypart/leg/left/robot) && !istype(tool, /obj/item/bodypart/leg/right/robot)) + return ..() + if(ismob(loc)) user.balloon_alert(user, "drop the helmet first!") - return + return ITEM_INTERACT_BLOCKING + user.balloon_alert(user, "leg attached") new /obj/item/bot_assembly/vim(loc) - qdel(attacked_with) + qdel(tool) qdel(src) + return ITEM_INTERACT_SUCCESS //Emergency suit /obj/item/clothing/head/helmet/space/fragile diff --git a/code/modules/explorer_drone/exodrone.dm b/code/modules/explorer_drone/exodrone.dm index ad2a55592e5b..81850230026a 100644 --- a/code/modules/explorer_drone/exodrone.dm +++ b/code/modules/explorer_drone/exodrone.dm @@ -366,19 +366,24 @@ GLOBAL_LIST_EMPTY(exodrone_launchers) if(fuel_canister) . += span_notice("You can remove the [fuel_canister] with a prying tool.") -/obj/machinery/exodrone_launcher/attackby(obj/item/weapon, mob/living/user, list/modifiers, list/attack_modifiers) - if(istype(weapon, /obj/item/fuel_pellet)) +/obj/machinery/exodrone_launcher/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/fuel_pellet)) if(fuel_canister) to_chat(user, span_warning("There's already fuel loaded inside [src]!")) - return TRUE - if(!user.transferItemToLoc(weapon, src)) - return - fuel_canister = weapon + return ITEM_INTERACT_BLOCKING + + if(!user.transferItemToLoc(tool, src)) + return ITEM_INTERACT_BLOCKING + + fuel_canister = tool update_icon() - return TRUE + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/exodrone)) + if(!user.transferItemToLoc(tool, drop_location())) + return ITEM_INTERACT_BLOCKING - if(istype(weapon, /obj/item/exodrone) && user.transferItemToLoc(weapon, drop_location())) - return TRUE + return ITEM_INTERACT_SUCCESS return ..() diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index 08eadf59f47d..d59e7c4a79b8 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -533,18 +533,21 @@ GLOBAL_LIST_INIT(fish_compatible_fluid_types, list( icon_state = base_icon_state return ..() -/obj/item/fish/attackby(obj/item/item, mob/living/user, list/modifiers, list/attack_modifiers) - if(!istype(item, /obj/item/reagent_containers/cup/fish_feed)) - return ..() - if(!item.reagents.total_volume) - balloon_alert(user, "[item.name] is empty!") - return TRUE +/obj/item/fish/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/reagent_containers/cup/fish_feed)) + return NONE + + if(!tool.reagents.total_volume) + balloon_alert(user, "[tool.name] is empty!") + return ITEM_INTERACT_BLOCKING + if(status == FISH_DEAD) balloon_alert(user, "[name] [HAS_MIND_TRAIT(user, TRAIT_NAIVE) ? "isn't hungry" : "is dead!"]") - return TRUE - feed(item.reagents) + return ITEM_INTERACT_BLOCKING + + feed(tool.reagents) balloon_alert(user, "fed [name]") - return TRUE + return ITEM_INTERACT_SUCCESS /obj/item/fish/examine(mob/user) . = ..() diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index 67d5845bb45c..3eab60e4e1db 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -500,20 +500,23 @@ . += line_overlay . += mutable_appearance(icon_file, "hook_overlay") -/obj/item/fishing_rod/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) - if(slot_check(attacking_item,ROD_SLOT_LINE)) - use_slot(ROD_SLOT_LINE, user, attacking_item) +/obj/item/fishing_rod/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(slot_check(tool, ROD_SLOT_LINE)) + use_slot(ROD_SLOT_LINE, user, tool) SStgui.update_uis(src) - return TRUE - else if(slot_check(attacking_item,ROD_SLOT_HOOK)) - use_slot(ROD_SLOT_HOOK, user, attacking_item) + return ITEM_INTERACT_SUCCESS + + if(slot_check(tool, ROD_SLOT_HOOK)) + use_slot(ROD_SLOT_HOOK, user, tool) SStgui.update_uis(src) - return TRUE - else if(slot_check(attacking_item,ROD_SLOT_BAIT) || istype(attacking_item, /obj/item/bait_can)) //Can click on the fishing rod with bait can directly - use_slot(ROD_SLOT_BAIT, user, attacking_item) + return ITEM_INTERACT_SUCCESS + + if(slot_check(tool, ROD_SLOT_BAIT) || istype(tool, /obj/item/bait_can)) //Can click on the fishing rod with bait can directly + use_slot(ROD_SLOT_BAIT, user, tool) SStgui.update_uis(src) - return TRUE - . = ..() + return ITEM_INTERACT_SUCCESS + + return NONE /obj/item/fishing_rod/ui_data(mob/user) . = ..() diff --git a/code/modules/food_and_drinks/food/egg.dm b/code/modules/food_and_drinks/food/egg.dm index 032d5fd0b276..6b884e11b8fe 100644 --- a/code/modules/food_and_drinks/food/egg.dm +++ b/code/modules/food_and_drinks/food/egg.dm @@ -89,37 +89,40 @@ GLOBAL_VAR_INIT(chicks_from_eggs, 0) new /mob/living/basic/chick(spawn_turf) GLOB.chicks_from_eggs++ -/obj/item/food/egg/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) - if(istype(item, /obj/item/toy/crayon)) - var/obj/item/toy/crayon/crayon = item - var/clr = crayon.crayon_color +/obj/item/food/egg/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/crayon = tool + var/pigment = crayon.crayon_color - if(!(clr in list("blue", "green", "mime", "orange", "purple", "rainbow", "red", "yellow"))) + if(!(pigment in list("blue", "green", "mime", "orange", "purple", "rainbow", "red", "yellow"))) to_chat(usr, span_notice("[src] refuses to take on this colour!")) - return + return ITEM_INTERACT_BLOCKING - to_chat(usr, span_notice("You colour [src] with [item].")) - icon_state = "egg-[clr]" + to_chat(usr, span_notice("You colour [src] with [tool].")) + icon_state = "egg-[pigment]" + return ITEM_INTERACT_SUCCESS - else if(istype(item, /obj/item/stamp/clown)) + if(istype(tool, /obj/item/stamp/clown)) var/clowntype = pick("grock", "grimaldi", "rainbow", "chaos", "joker", "sexy", "standard", "bobble", "krusty", "bozo", "pennywise", "ronald", "jacobs", "kelly", "popov", "cluwne") icon_state = "egg-clown-[clowntype]" desc = "An egg that has been decorated with the grotesque, robustable likeness of a clown's face. " - to_chat(usr, span_notice("You stamp [src] with [item], creating an artistic and not remotely horrifying likeness of clown makeup.")) + to_chat(usr, span_notice("You stamp [src] with [tool], creating an artistic and not remotely horrifying likeness of clown makeup.")) + return ITEM_INTERACT_SUCCESS - else if(is_reagent_container(item)) - var/obj/item/reagent_containers/dunk_test_container = item + if(is_reagent_container(tool)) + var/obj/item/reagent_containers/dunk_test_container = tool if (!dunk_test_container.is_drainable() || !dunk_test_container.reagents.has_reagent(/datum/reagent/water)) - return + return NONE to_chat(user, span_notice("You check if [src] is rotten.")) if(istype(src, /obj/item/food/egg/rotten)) to_chat(user, span_warning("[src] floats in the [dunk_test_container]!")) else to_chat(user, span_notice("[src] sinks into the [dunk_test_container]!")) - else - ..() + return ITEM_INTERACT_SUCCESS + + return NONE /obj/item/food/egg/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers) if(!istype(interacting_with, /obj/machinery/griddle)) @@ -285,23 +288,25 @@ GLOBAL_VAR_INIT(chicks_from_eggs, 0) . = ..() AddElement(/datum/element/love_food_buff, /datum/status_effect/food/speech/french) -/obj/item/food/omelette/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) - if(istype(item, /obj/item/kitchen/fork)) - var/obj/item/kitchen/fork/fork = item - if(fork.forkload) - to_chat(user, span_warning("You already have omelette on your fork!")) - else - fork.icon_state = "forkloaded" - user.visible_message(span_notice("[user] takes a piece of omelette with [user.p_their()] fork!"), \ - span_notice("You take a piece of omelette with your fork.")) - - var/datum/reagent/reagent = pick(reagents.reagent_list) - reagents.remove_reagent(reagent.type, 1) - fork.forkload = reagent - if(reagents.total_volume <= 0) - qdel(src) - return - ..() +/obj/item/food/omelette/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/kitchen/fork)) + return NONE + + var/obj/item/kitchen/fork/fork = tool + if(fork.forkload) + to_chat(user, span_warning("You already have omelette on your fork!")) + return ITEM_INTERACT_BLOCKING + + fork.icon_state = "forkloaded" + user.visible_message(span_notice("[user] takes a piece of omelette with [user.p_their()] fork!"), \ + span_notice("You take a piece of omelette with your fork.")) + + var/datum/reagent/reagent = pick(reagents.reagent_list) + reagents.remove_reagent(reagent.type, 1) + fork.forkload = reagent + if(reagents.total_volume <= 0) + qdel(src) + return ITEM_INTERACT_SUCCESS /obj/item/food/benedict name = "eggs benedict" diff --git a/code/modules/food_and_drinks/food/misc.dm b/code/modules/food_and_drinks/food/misc.dm index d46633c502b9..429d7b7c0bdd 100644 --- a/code/modules/food_and_drinks/food/misc.dm +++ b/code/modules/food_and_drinks/food/misc.dm @@ -394,20 +394,22 @@ if (can_stick) . += span_notice("If you had a rod you could make butter on a stick.") -/obj/item/food/butter/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) - if(!istype(item, /obj/item/stack/rods) || !can_stick) - return ..() - var/obj/item/stack/rods/rods = item +/obj/item/food/butter/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/stack/rods) || !can_stick) + return NONE + + var/obj/item/stack/rods/rods = tool if(!rods.use(1))//borgs can still fail this if they have no metal to_chat(user, span_warning("You do not have enough iron to put [src] on a stick!")) - return ..() + return ITEM_INTERACT_BLOCKING + to_chat(user, span_notice("You stick the rod into the stick of butter.")) user.temporarilyRemoveItemFromInventory(src) var/obj/item/food/butter/on_a_stick/new_item = new(drop_location()) if (new_item.IsReachableBy(user)) user.put_in_hands(new_item) qdel(src) - return TRUE + return ITEM_INTERACT_SUCCESS /obj/item/food/butter/on_a_stick //there's something so special about putting it on a stick. name = "butter on a stick" diff --git a/code/modules/food_and_drinks/food/pancakes.dm b/code/modules/food_and_drinks/food/pancakes.dm index d618c391e564..84ab82340cff 100644 --- a/code/modules/food_and_drinks/food/pancakes.dm +++ b/code/modules/food_and_drinks/food/pancakes.dm @@ -32,24 +32,28 @@ positive_result = TRUE,\ use_large_steam_sprite = TRUE) -/obj/item/food/pancakes/raw/attackby(obj/item/garnish, mob/living/user, list/modifiers, list/attack_modifiers) +/obj/item/food/pancakes/raw/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(name != initial(name))// we already have additives + return ..() + var/newresult - if(istype(garnish, /obj/item/food/grown/berries)) + if(istype(tool, /obj/item/food/grown/berries)) newresult = /obj/item/food/pancakes/blueberry name = "raw blueberry pancake" icon_state = "rawbbpancakes_1" stack_name = "rawbbpancakes" - else if(istype(garnish, /obj/item/food/chocolatebar)) + else if(istype(tool, /obj/item/food/chocolatebar)) newresult = /obj/item/food/pancakes/chocolatechip name = "raw chocolate chip pancake" icon_state = "rawccpancakes_1" stack_name = "rawccpancakes" - else + if(!newresult) return ..() - if(newresult) - qdel(garnish) - to_chat(user, span_notice("You add [garnish] to [src].")) - AddComponent(/datum/component/grillable, cook_result = newresult) + + qdel(tool) + to_chat(user, span_notice("You add [tool] to [src].")) + AddComponent(/datum/component/grillable, cook_result = newresult) + return ITEM_INTERACT_SUCCESS /obj/item/food/pancakes/raw/examine(mob/user) . = ..() @@ -119,31 +123,35 @@ ingredients_listed += "[ING.name], " . += "It contains [contents.len?"[ingredients_listed]":"no ingredient, "]on top of a [initial(name)]." -/obj/item/food/pancakes/attackby(obj/item/item, mob/living/user, list/modifiers, list/attack_modifiers) - if(istype(item, /obj/item/food/pancakes)) - var/obj/item/food/pancakes/pancake = item +/obj/item/food/pancakes/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/food/pancakes)) + var/obj/item/food/pancakes/pancake = tool if((contents.len >= PANCAKE_MAX_STACK) || ((pancake.contents.len + contents.len) > PANCAKE_MAX_STACK)) to_chat(user, span_warning("You can't add that many pancakes to [src]!")) - else - if(!user.transferItemToLoc(pancake, src)) - return - to_chat(user, span_notice("You add the [pancake] to the [src].")) - pancake.name = initial(pancake.name) - contents += pancake - update_snack_overlays(pancake) - if (pancake.contents.len) - for(var/pancake_content in pancake.contents) - pancake = pancake_content - pancake.name = initial(pancake.name) - contents += pancake - update_snack_overlays(pancake) - pancake = item - pancake.contents.Cut() - return - else if(contents.len) - var/obj/O = contents[contents.len] - return O.attackby(item, user, modifiers) - ..() + return ITEM_INTERACT_BLOCKING + + if(!user.transferItemToLoc(pancake, src)) + return ITEM_INTERACT_BLOCKING + + to_chat(user, span_notice("You add the [pancake] to the [src].")) + pancake.name = initial(pancake.name) + contents += pancake + update_snack_overlays(pancake) + if (pancake.contents.len) + for(var/pancake_content in pancake.contents) + pancake = pancake_content + pancake.name = initial(pancake.name) + contents += pancake + update_snack_overlays(pancake) + pancake = tool + pancake.contents.Cut() + return ITEM_INTERACT_SUCCESS + + if(contents.len) + var/obj/pancake = contents[contents.len] + return pancake.item_interaction(user, tool, modifiers) + + return NONE /obj/item/food/pancakes/proc/update_snack_overlays(obj/item/food/pancakes/pancake) var/mutable_appearance/pancake_visual = mutable_appearance(icon, "[pancake.stack_name]_[rand(1, 3)]") diff --git a/code/modules/food_and_drinks/food/pizza.dm b/code/modules/food_and_drinks/food/pizza.dm index fa58a3f465b8..ee57c9e6b3b7 100644 --- a/code/modules/food_and_drinks/food/pizza.dm +++ b/code/modules/food_and_drinks/food/pizza.dm @@ -503,18 +503,25 @@ to_chat(user, "If you want something crazy like pineapple, I'll kill you.") //this is in bigger text because it's hard to spam something that gibs you, and so that you're perfectly aware of the reason why you died user.investigate_log("has been gibbed by putting pineapple on an arnold pizza.", INVESTIGATE_DEATHS) user.gib(DROP_ALL_REMAINS) //if you want something crazy like pineapple, i'll kill you - else if(istype(item, /obj/item/food/grown/mushroom) && iscarbon(user)) + return TRUE + + if(istype(item, /obj/item/food/grown/mushroom) && iscarbon(user)) to_chat(user, span_userdanger("So, if you want mushroom, shut up.")) //not as large as the pineapple text, because you could in theory spam it var/mob/living/carbon/shutup = user shutup.gain_trauma(/datum/brain_trauma/severe/mute) + return TRUE + + return FALSE /obj/item/food/pizza/arnold/attack(mob/living/target, mob/living/user) . = ..() try_break_off(target, user) -/obj/item/food/pizza/arnold/attackby(obj/item/item, mob/user) - i_kill_you(item, user) - . = ..() +/obj/item/food/pizza/arnold/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!i_kill_you(tool, user)) + return ..() + + return ITEM_INTERACT_SUCCESS /obj/item/food/pizzaslice/arnold name = "\improper Arnold pizza slice" @@ -528,9 +535,11 @@ . =..() try_break_off(target, user) -/obj/item/food/pizzaslice/arnold/attackby(obj/item/item, mob/user) - i_kill_you(item, user) - . = ..() +/obj/item/food/pizzaslice/arnold/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!i_kill_you(tool, user)) + return ..() + + return ITEM_INTERACT_SUCCESS // Ant Pizza, now with more ants. /obj/item/food/pizzaslice/ants diff --git a/code/modules/food_and_drinks/machinery/griddle.dm b/code/modules/food_and_drinks/machinery/griddle.dm index 5041259352a8..ed53dd7e7c45 100644 --- a/code/modules/food_and_drinks/machinery/griddle.dm +++ b/code/modules/food_and_drinks/machinery/griddle.dm @@ -66,22 +66,54 @@ visible_message(span_notice("[exposing_reagent] begins to cook on [src].")) return NONE -/obj/machinery/griddle/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers) +/obj/machinery/griddle/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(user.combat_mode) + return NONE + + if(tool.atom_storage) + if(length(griddled_objects) >= max_items) + balloon_alert(user, "it's full!") + return ITEM_INTERACT_BLOCKING + + if(!istype(tool, /obj/item/storage/bag/tray)) + // Non-tray dumping requires a do_after + to_chat(user, span_notice("You start dumping out the contents of [tool] into [src]...")) + if(!do_after(user, 2 SECONDS, target = tool)) + return ITEM_INTERACT_BLOCKING + + var/loaded = 0 + for(var/obj/tray_item in tool) + if(!IS_EDIBLE(tray_item)) + continue + if(length(griddled_objects) >= max_items) + break + if(tool.atom_storage.attempt_remove(tray_item, src)) + loaded++ + AddToGrill(tray_item, user) + if(!loaded) + return ITEM_INTERACT_BLOCKING + to_chat(user, span_notice("You insert [loaded] item\s into [src].")) + update_appearance() + return ITEM_INTERACT_SUCCESS if(griddled_objects.len >= max_items) to_chat(user, span_notice("[src] can't fit more items!")) - return + return ITEM_INTERACT_BLOCKING + //Center the icon where the user clicked. if(!LAZYACCESS(modifiers, ICON_X) || !LAZYACCESS(modifiers, ICON_Y)) - return - if(user.transferItemToLoc(I, src, silent = FALSE)) - //Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf) - I.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(ICON_SIZE_X/2), ICON_SIZE_X/2) - I.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(ICON_SIZE_Y/2), ICON_SIZE_Y/2) - to_chat(user, span_notice("You place [I] on [src].")) - AddToGrill(I, user) - else - return ..() + return ITEM_INTERACT_BLOCKING + + if(!user.transferItemToLoc(tool, src, silent = FALSE)) + return ITEM_INTERACT_BLOCKING + + //Clamp it so that the icon never moves more than 16 pixels in either direction (thus leaving the table turf) + tool.pixel_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(ICON_SIZE_X/2), ICON_SIZE_X/2) + tool.pixel_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(ICON_SIZE_Y/2), ICON_SIZE_Y/2) + to_chat(user, span_notice("You place [tool] on [src].")) + AddToGrill(tool, user) + return ITEM_INTERACT_SUCCESS + /obj/machinery/griddle/item_interaction_secondary(mob/living/user, obj/item/item, list/modifiers) if(isnull(item.atom_storage)) @@ -91,35 +123,6 @@ item.atom_storage.attempt_insert(tray_item, user, TRUE) return ITEM_INTERACT_SUCCESS -/obj/machinery/griddle/item_interaction(mob/living/user, obj/item/item, list/modifiers) - if(isnull(item.atom_storage)) - return NONE - - if(length(griddled_objects) >= max_items) - balloon_alert(user, "it's full!") - return ITEM_INTERACT_BLOCKING - - if(!istype(item, /obj/item/storage/bag/tray)) - // Non-tray dumping requires a do_after - to_chat(user, span_notice("You start dumping out the contents of [item] into [src]...")) - if(!do_after(user, 2 SECONDS, target = item)) - return ITEM_INTERACT_BLOCKING - - var/loaded = 0 - for(var/obj/tray_item in item) - if(!IS_EDIBLE(tray_item)) - continue - if(length(griddled_objects) >= max_items) - break - if(item.atom_storage.attempt_remove(tray_item, src)) - loaded++ - AddToGrill(tray_item, user) - if(loaded) - to_chat(user, span_notice("You insert [loaded] item\s into [src].")) - update_appearance() - return ITEM_INTERACT_SUCCESS - return ITEM_INTERACT_BLOCKING - /obj/machinery/griddle/attack_hand(mob/user, list/modifiers) . = ..() toggle_mode() diff --git a/code/modules/food_and_drinks/machinery/icecream_vat.dm b/code/modules/food_and_drinks/machinery/icecream_vat.dm index 2b47f7852e32..241b7946fdaf 100644 --- a/code/modules/food_and_drinks/machinery/icecream_vat.dm +++ b/code/modules/food_and_drinks/machinery/icecream_vat.dm @@ -114,50 +114,50 @@ . = ..() . += "You can use a [EXAMINE_HINT("spoon")] or [EXAMINE_HINT("soup ladle")] to spill reagents." -/obj/machinery/icecream_vat/attackby(obj/item/weapon, mob/user, list/modifiers, list/attack_modifiers) - . = ..() - if(.) - return - - if(istype(weapon, /obj/item/kitchen/spoon) || istype(weapon, /obj/item/kitchen/spoon/soup_ladle)) +/obj/machinery/icecream_vat/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(istype(tool, /obj/item/kitchen/spoon) || istype(tool, /obj/item/kitchen/spoon/soup_ladle)) spill_reagents(user) - return TRUE + return ITEM_INTERACT_SUCCESS - var/obj/item/reagent_containers/beaker = weapon + var/obj/item/reagent_containers/beaker = tool if(!istype(beaker) || !beaker.reagents || (beaker.item_flags & ABSTRACT) || !beaker.is_open_container()) - return + return NONE if(custom_ice_cream_beaker) - if(user.transferItemToLoc(beaker, src)) - try_put_in_hand(custom_ice_cream_beaker, user) - balloon_alert(user, "beakers swapped") - custom_ice_cream_beaker = beaker - else + if(!user.transferItemToLoc(beaker, src)) balloon_alert(user, "beaker slot full!") - return + return ITEM_INTERACT_BLOCKING + + try_put_in_hand(custom_ice_cream_beaker, user) + balloon_alert(user, "beakers swapped") + custom_ice_cream_beaker = beaker + return ITEM_INTERACT_SUCCESS + if(!user.transferItemToLoc(beaker, src)) - return + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "beaker inserted") custom_ice_cream_beaker = beaker + return ITEM_INTERACT_SUCCESS -/obj/machinery/icecream_vat/attackby_secondary(obj/item/reagent_containers/beaker, mob/user, list/modifiers, list/attack_modifiers) - . = ..() - if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) - return +/obj/machinery/icecream_vat/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers) + var/obj/item/reagent_containers/beaker = tool if(!istype(beaker) || !beaker.reagents || (beaker.item_flags & ABSTRACT) || !beaker.is_open_container()) - return SECONDARY_ATTACK_CONTINUE_CHAIN + return NONE + var/added_reagents = FALSE for(var/datum/reagent/beaker_reagents in beaker.reagents.reagent_list) if(beaker_reagents.type in icecream_vat_reagents) added_reagents = TRUE beaker.reagents.trans_to(src, beaker_reagents.volume, target_id = beaker_reagents.type) - if(added_reagents) - balloon_alert(user, "refilling reagents") - playsound(src, 'sound/items/drink.ogg', 25, TRUE) - else + if(!added_reagents) balloon_alert(user, "no reagents to transfer!") - return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + return ITEM_INTERACT_BLOCKING + + balloon_alert(user, "refilling reagents") + playsound(src, 'sound/items/drink.ogg', 25, TRUE) + return ITEM_INTERACT_SUCCESS /obj/machinery/icecream_vat/attack_hand_secondary(mob/user, list/modifiers) if(swap_modes(user)) diff --git a/code/modules/food_and_drinks/machinery/oven.dm b/code/modules/food_and_drinks/machinery/oven.dm index 95647c4bdbb9..7c763c3e8648 100644 --- a/code/modules/food_and_drinks/machinery/oven.dm +++ b/code/modules/food_and_drinks/machinery/oven.dm @@ -105,18 +105,19 @@ update_appearance() use_energy(active_power_usage) -/obj/machinery/oven/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers) - if(!open || used_tray || !istype(item, /obj/item/plate/oven_tray)) - return ..() +/obj/machinery/oven/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(open && used_tray && tool.atom_storage) + return used_tray.item_interaction(user, tool, modifiers) - if(user.transferItemToLoc(item, src, silent = FALSE)) - to_chat(user, span_notice("You put [item] in [src].")) - add_tray_to_oven(item, user) + if(!open || used_tray || !istype(tool, /obj/item/plate/oven_tray)) + return NONE -/obj/machinery/oven/item_interaction(mob/living/user, obj/item/item, list/modifiers) - if(open && used_tray && item.atom_storage) - return used_tray.item_interaction(user, item, modifiers) - return NONE + if(!user.transferItemToLoc(tool, src, silent = FALSE)) + return ITEM_INTERACT_BLOCKING + + to_chat(user, span_notice("You put [tool] in [src].")) + add_tray_to_oven(tool, user) + return ITEM_INTERACT_SUCCESS /obj/machinery/oven/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers) if(open && used_tray && tool.atom_storage) diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm index dfa1526dbb68..98904a13ce4b 100644 --- a/code/modules/holodeck/items.dm +++ b/code/modules/holodeck/items.dm @@ -151,10 +151,12 @@ /obj/machinery/conveyor/holodeck -/obj/machinery/conveyor/holodeck/attackby(obj/item/I, mob/user, list/modifiers, list/attack_modifiers) - if(!user.transferItemToLoc(I, drop_location())) +/obj/machinery/conveyor/holodeck/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!user.transferItemToLoc(tool, drop_location())) return ..() + return ITEM_INTERACT_SUCCESS + /obj/item/paper/fluff/holodeck/trek_diploma name = "paper - Starfleet Academy Diploma" default_raw_text = {"

Starfleet Academy


Official Diploma


"} diff --git a/code/modules/hydroponics/fermenting_barrel.dm b/code/modules/hydroponics/fermenting_barrel.dm index b4c15f221a24..2e2418d1ef8b 100644 --- a/code/modules/hydroponics/fermenting_barrel.dm +++ b/code/modules/hydroponics/fermenting_barrel.dm @@ -57,23 +57,26 @@ else . += span_notice("It is currently closed, letting it ferment fruits or draw reagents from its tap.") -/obj/structure/fermenting_barrel/attackby(obj/item/object, mob/user, list/modifiers, list/attack_modifiers) +/obj/structure/fermenting_barrel/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(open) - if(istype(object, /obj/item/food/grown) && insert_fruit(user, object)) + if(istype(tool, /obj/item/food/grown) && insert_fruit(user, tool)) balloon_alert(user, "added fruit") - return - if(istype(object, /obj/item/storage/bag/plants)) - var/obj/item/storage/bag/plants/bag = object + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/storage/bag/plants)) + var/obj/item/storage/bag/plants/bag = tool var/inserted_fruits = 0 for(var/obj/item/food/grown/fruit in bag.contents) if(!insert_fruit(user, fruit, bag)) break inserted_fruits++ - if(inserted_fruits) - balloon_alert(user, "added [inserted_fruits] fruit\s") - else if(object.is_refillable()) - return //so we can refill them via their afterattack. - return ..() + if(!inserted_fruits) + return ITEM_INTERACT_BLOCKING + + balloon_alert(user, "added [inserted_fruits] fruit\s") + return ITEM_INTERACT_SUCCESS + + return NONE /obj/structure/fermenting_barrel/attack_hand(mob/user, list/modifiers) if(!can_open) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 7311af695e21..9e847f30b1b6 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -895,18 +895,17 @@ default_unfasten_wrench(user, tool) return ITEM_INTERACT_SUCCESS -/obj/machinery/hydroponics/attackby(obj/item/O, mob/user, list/modifiers, list/attack_modifiers) - //Called when mob user "attacks" it with object O - if(IS_EDIBLE(O) || is_reagent_container(O)) // Syringe stuff (and other reagent containers now too) - var/obj/item/reagent_containers/reagent_source = O +/obj/machinery/hydroponics/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(IS_EDIBLE(tool) || is_reagent_container(tool)) // Syringe stuff (and other reagent containers now too) + var/obj/item/reagent_containers/reagent_source = tool if(!reagent_source.reagents.total_volume) to_chat(user, span_warning("[reagent_source] is empty!")) - return 1 + return ITEM_INTERACT_BLOCKING if(reagents.total_volume >= reagents.maximum_volume && !reagent_source.reagents.has_reagent(/datum/reagent/water, 1)) to_chat(user, span_notice("[src] is full.")) - return + return ITEM_INTERACT_BLOCKING var/list/trays = list(src)//makes the list just this in cases of syringes and compost etc var/target = myseed ? myseed.plantname : src @@ -915,9 +914,9 @@ if(IS_EDIBLE(reagent_source)) if(HAS_TRAIT(reagent_source, TRAIT_UNCOMPOSTABLE)) - to_chat(user, "[reagent_source] cannot be composted in its current state") - return - visi_msg="[user] composts [reagent_source], spreading it through [target]" + to_chat(user, "[reagent_source] cannot be composted in its current state.") + return ITEM_INTERACT_BLOCKING + visi_msg = "[user] composts [reagent_source], spreading it through [target]" transfer_amount = reagent_source.reagents.total_volume SEND_SIGNAL(reagent_source, COMSIG_ITEM_ON_COMPOSTED, user) if((tray_flags & WORM_HABITAT) && prob(transfer_amount / 2)) @@ -928,8 +927,7 @@ else transfer_amount = min(reagent_source.amount_per_transfer_from_this, reagent_source.reagents.total_volume) if(istype(reagent_source, /obj/item/reagent_containers/syringe/)) - var/obj/item/reagent_containers/syringe/syr = reagent_source - visi_msg="[user] injects [target] with [syr]" + visi_msg = "[user] injects [target] with [reagent_source]" // Beakers, bottles, buckets, etc. if(reagent_source.is_drainable()) playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE) @@ -940,70 +938,74 @@ if(visi_msg) visible_message(span_notice("[visi_msg].")) - for(var/obj/machinery/hydroponics/H in trays) + for(var/obj/machinery/hydroponics/iterating_tray in trays) //cause I don't want to feel like im juggling 15 tamagotchis and I can get to my real work of ripping flooring apart in hopes of validating my life choices of becoming a space-gardener //This was originally in apply_chemicals, but due to apply_chemicals only holding nutrients, we handle it here now. if(reagent_source.reagents.has_reagent(/datum/reagent/water)) var/water_amt = reagent_source.reagents.get_reagent_amount(/datum/reagent/water) * transfer_amount / reagent_source.reagents.total_volume - var/water_amt_adjusted = H.adjust_waterlevel(round(water_amt)) + var/water_amt_adjusted = iterating_tray.adjust_waterlevel(round(water_amt)) reagent_source.reagents.remove_reagent(/datum/reagent/water, water_amt_adjusted) for(var/datum/reagent/not_water_reagent as anything in reagent_source.reagents.reagent_list) if(istype(not_water_reagent,/datum/reagent/water)) continue var/transfer_me_to_tray = reagent_source.reagents.get_reagent_amount(not_water_reagent.type) * transfer_amount / reagent_source.reagents.total_volume - reagent_source.reagents.trans_to(H.reagents, transfer_me_to_tray, target_id = not_water_reagent.type) + reagent_source.reagents.trans_to(iterating_tray.reagents, transfer_me_to_tray, target_id = not_water_reagent.type) else - reagent_source.reagents.trans_to(H.reagents, transfer_amount, transferred_by = user) + reagent_source.reagents.trans_to(iterating_tray.reagents, transfer_amount, transferred_by = user) lastuser = WEAKREF(user) if(IS_EDIBLE(reagent_source) || istype(reagent_source, /obj/item/reagent_containers/applicator/pill)) qdel(reagent_source) - H.update_appearance() - return 1 - H.update_appearance() + iterating_tray.update_appearance() + return ITEM_INTERACT_SUCCESS + + iterating_tray.update_appearance() if(reagent_source) // If the source wasn't composted and destroyed reagent_source.update_appearance() - return 1 + return ITEM_INTERACT_SUCCESS - else if(istype(O, /obj/item/seeds)) - propagate_plant(O, user) - return - else if(istype(O, /obj/item/cultivator)) - if(weedlevel > 0) - user.visible_message(span_notice("[user] uproots the weeds."), span_notice("You remove the weeds from [src].")) - set_weedlevel(0) - return - else + if(istype(tool, /obj/item/seeds)) + propagate_plant(tool, user) + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/cultivator)) + if(!weedlevel) to_chat(user, span_warning("This plot is completely devoid of weeds! It doesn't need uprooting.")) - return + return ITEM_INTERACT_BLOCKING + + user.visible_message(span_notice("[user] uproots the weeds."), span_notice("You remove the weeds from [src].")) + set_weedlevel(0) + return ITEM_INTERACT_SUCCESS - else if(istype(O, /obj/item/secateurs)) + if(istype(tool, /obj/item/secateurs)) if(!myseed) to_chat(user, span_notice("This plot is empty.")) - return - else if(plant_status != HYDROTRAY_PLANT_HARVESTABLE) - to_chat(user, span_notice("This plant must be harvestable in order to be grafted.")) - return - else if(myseed.grafts_taken >= ((tray_flags & MULTIGRAFT) ? MULTI_GRAFT_MAX_COUNT : 1)) - to_chat(user, span_notice("You can't take any more cuttings from this plant!")) - return - else - user.visible_message(span_notice("[user] grafts off a limb from [src]."), span_notice("You carefully graft off a portion of [src].")) - var/obj/item/graft/snip = myseed.create_graft() - if(!snip) - return // The plant did not return a graft. + return ITEM_INTERACT_BLOCKING - snip.forceMove(drop_location()) - myseed.grafts_taken++ - adjust_plant_health(-5) - return + if(plant_status != HYDROTRAY_PLANT_HARVESTABLE) + to_chat(user, span_notice("This plant must be harvestable in order to be grafted.")) + return ITEM_INTERACT_BLOCKING - else if(istype(O, /obj/item/geneshears)) + if(myseed.grafts_taken >= ((tray_flags & MULTIGRAFT) ? MULTI_GRAFT_MAX_COUNT : 1)) + to_chat(user, span_notice("You can't take any more cuttings from this plant!")) + return ITEM_INTERACT_BLOCKING + user.visible_message(span_notice("[user] grafts off a limb from [src]."), span_notice("You carefully graft off a portion of [src].")) + var/obj/item/graft/snip = myseed.create_graft() + if(!snip) + return ITEM_INTERACT_BLOCKING // The plant did not return a graft. + + snip.forceMove(drop_location()) + myseed.grafts_taken++ + adjust_plant_health(-5) + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/geneshears)) if(!myseed) to_chat(user, span_notice("The tray is empty.")) - return + return ITEM_INTERACT_BLOCKING + if(plant_health <= GENE_SHEAR_MIN_HEALTH) to_chat(user, span_notice("This plant looks too unhealty to be sheared right now.")) - return + return ITEM_INTERACT_BLOCKING var/list/current_traits = list() for(var/datum/plant_gene/gene in myseed.genes) @@ -1014,13 +1016,17 @@ current_traits[gene.name] = gene var/removed_trait = tgui_input_list(user, "Trait to remove from the [myseed.plantname]", "Plant Trait Removal", sort_list(current_traits)) if(isnull(removed_trait)) - return + return ITEM_INTERACT_BLOCKING + if(!user.can_perform_action(src)) - return + return ITEM_INTERACT_BLOCKING + if(!myseed) - return + return ITEM_INTERACT_BLOCKING + if(plant_health <= GENE_SHEAR_MIN_HEALTH) //Check health again to make sure they're not keeping inputs open to get free shears. - return + return ITEM_INTERACT_BLOCKING + for(var/datum/plant_gene/gene in myseed.genes) if(gene.name == removed_trait) if(myseed.genes.Remove(gene)) @@ -1031,91 +1037,113 @@ adjust_plant_health(-15) to_chat(user, span_notice("You carefully shear the genes off of the [myseed.plantname], leaving the plant looking weaker.")) update_appearance() - return + return ITEM_INTERACT_SUCCESS - else if(istype(O, /obj/item/graft)) - var/obj/item/graft/snip = O + if(istype(tool, /obj/item/graft)) + var/obj/item/graft/snip = tool if(!myseed) - if(tray_flags & GRAFT_MEDIUM) - propagate_plant(snip.plant_dna, user) - qdel(snip) - return - to_chat(user, span_notice("The tray is empty.")) - return + if(!(tray_flags & GRAFT_MEDIUM)) + to_chat(user, span_notice("The tray is empty.")) + return ITEM_INTERACT_BLOCKING + + propagate_plant(snip.plant_dna, user) + qdel(snip) + return ITEM_INTERACT_SUCCESS + var/datum/plant_gene/grafted_trait = myseed.apply_graft(snip) if(grafted_trait) to_chat(user, span_notice("You carefully integrate the grafted plant limb onto [myseed.plantname], granting it [grafted_trait.get_name()].")) + . = ITEM_INTERACT_SUCCESS else to_chat(user, span_notice("You try to integrate the grafted plant limb onto [myseed.plantname], but it rejects the trait from the [snip].")) + . = ITEM_INTERACT_BLOCKING qdel(snip) - return + return . - else if(istype(O, /obj/item/storage/bag/plants)) + if(istype(tool, /obj/item/storage/bag/plants)) if(plant_status == HYDROTRAY_PLANT_HARVESTABLE) var/list/harvest = myseed.harvest(user) - for(var/obj/item/food/grown/G in harvest) - O.atom_storage?.attempt_insert(G, user, TRUE) - else if(plant_status == HYDROTRAY_PLANT_DEAD) + for(var/obj/item/food/grown/bounty in harvest) + tool.atom_storage?.attempt_insert(bounty, user, TRUE) + return ITEM_INTERACT_SUCCESS + + if(plant_status == HYDROTRAY_PLANT_DEAD) to_chat(user, span_notice("You remove the dead plant from [src].")) set_seed(null) - return + return ITEM_INTERACT_SUCCESS - else if(O.tool_behaviour == TOOL_SHOVEL) + return NONE + + if(tool.tool_behaviour == TOOL_SHOVEL) if(!myseed && !weedlevel) to_chat(user, span_warning("[src] doesn't have any plants or weeds!")) - return + return ITEM_INTERACT_BLOCKING + user.visible_message(span_notice("[user] starts digging out [src]'s plants..."), - span_notice("You start digging out [src]'s plants...")) - if(O.use_tool(src, user, 50, volume=50) || (!myseed && !weedlevel)) - user.visible_message(span_notice("[user] digs out the plants in [src]!"), span_notice("You dig out all of [src]'s plants!")) - remove_plant() - return - else if(istype(O, /obj/item/gun/energy/floragun)) - var/obj/item/gun/energy/floragun/flowergun = O + span_notice("You start digging out [src]'s plants...")) + if(!tool.use_tool(src, user, 5 SECONDS, volume = 50) || (!myseed && !weedlevel)) + return ITEM_INTERACT_BLOCKING + + user.visible_message(span_notice("[user] digs out the plants in [src]!"), span_notice("You dig out all of [src]'s plants!")) + remove_plant() + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/gun/energy/floragun)) + var/obj/item/gun/energy/floragun/flowergun = tool if(flowergun.cell.charge < flowergun.cell.maxcharge) to_chat(user, span_notice("[flowergun] must be fully charged to lock in a mutation!")) - return + return ITEM_INTERACT_BLOCKING + if(!myseed) to_chat(user, span_warning("[src] is empty!")) - return + return ITEM_INTERACT_BLOCKING + if(myseed.endurance <= FLORA_GUN_MIN_ENDURANCE) to_chat(user, span_warning("[myseed.plantname] isn't hardy enough to sequence its mutation!")) - return + return ITEM_INTERACT_BLOCKING + if(!LAZYLEN(myseed.mutatelist)) to_chat(user, span_warning("[myseed.plantname] has nothing else to mutate into!")) - return - else - var/list/fresh_mut_list = list() - for(var/muties in myseed.mutatelist) - var/obj/item/seeds/another_mut = new muties - fresh_mut_list[another_mut.plantname] = muties - var/locked_mutation = tgui_input_list(user, "Mutation to lock", "Plant Mutation Locks", sort_list(fresh_mut_list)) - if(isnull(locked_mutation)) - return - if(isnull(fresh_mut_list[locked_mutation])) - return - if(!user.can_perform_action(src)) - return - myseed.mutatelist = list(fresh_mut_list[locked_mutation]) - myseed.set_endurance(myseed.endurance/2) - flowergun.cell.use(flowergun.cell.charge) - flowergun.update_appearance() - to_chat(user, span_notice("[myseed.plantname]'s mutation was set to [locked_mutation], depleting [flowergun]'s cell!")) - return - else if(istype(O, /obj/item/soil_sack)) - var/obj/item/soil_sack/oursoil = O + return ITEM_INTERACT_BLOCKING + + var/list/fresh_mut_list = list() + for(var/muties in myseed.mutatelist) + var/obj/item/seeds/another_mut = new muties + fresh_mut_list[another_mut.plantname] = muties + var/locked_mutation = tgui_input_list(user, "Mutation to lock", "Plant Mutation Locks", sort_list(fresh_mut_list)) + if(isnull(locked_mutation)) + return ITEM_INTERACT_BLOCKING + + if(isnull(fresh_mut_list[locked_mutation])) + return ITEM_INTERACT_BLOCKING + + if(!user.can_perform_action(src)) + return ITEM_INTERACT_BLOCKING + + myseed.mutatelist = list(fresh_mut_list[locked_mutation]) + myseed.set_endurance(myseed.endurance/2) + flowergun.cell.use(flowergun.cell.charge) + flowergun.update_appearance() + to_chat(user, span_notice("[myseed.plantname]'s mutation was set to [locked_mutation], depleting [flowergun]'s cell!")) + return ITEM_INTERACT_SUCCESS + + if(istype(tool, /obj/item/soil_sack)) + var/obj/item/soil_sack/oursoil = tool if(plant_status != HYDROTRAY_NO_PLANT) balloon_alert(user, "remove the plants first!") - return + return ITEM_INTERACT_BLOCKING if(!isnull(current_soil)) balloon_alert(user, "tray is full!") - return + return ITEM_INTERACT_BLOCKING balloon_alert(user, "filling the tray...") if(!do_after(user, 2 SECONDS, src)) - return + return ITEM_INTERACT_BLOCKING + + if((plant_status != HYDROTRAY_NO_PLANT) || current_soil) + return ITEM_INTERACT_BLOCKING current_soil = oursoil.transfer_soil(src, inside_tray = TRUE) RefreshParts() @@ -1123,16 +1151,15 @@ qdel(oursoil) update_appearance() - return TRUE + return ITEM_INTERACT_SUCCESS - else - return ..() + return NONE -/obj/machinery/hydroponics/attackby_secondary(obj/item/weapon, mob/user, list/modifiers, list/attack_modifiers) - if (istype(weapon, /obj/item/reagent_containers/syringe)) +/obj/machinery/hydroponics/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers) + if (istype(tool, /obj/item/reagent_containers/syringe)) to_chat(user, span_warning("You can't get any extract out of this plant.")) - return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN - return SECONDARY_ATTACK_CALL_NORMAL + return ITEM_INTERACT_BLOCKING + return NONE /obj/machinery/hydroponics/can_be_unfasten_wrench(mob/user, silent) if (!unwrenchable) // case also covered by NODECONSTRUCT checks in default_unfasten_wrench diff --git a/code/modules/hydroponics/soil.dm b/code/modules/hydroponics/soil.dm index b67c789b4266..6d924b04536c 100644 --- a/code/modules/hydroponics/soil.dm +++ b/code/modules/hydroponics/soil.dm @@ -30,15 +30,17 @@ /obj/machinery/hydroponics/soil/update_status_light_overlays() return // Has no lights -/obj/machinery/hydroponics/soil/attackby_secondary(obj/item/weapon, mob/user, list/modifiers, list/attack_modifiers) - if(weapon.tool_behaviour != TOOL_SHOVEL) //Spades can still uproot plants on left click - return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN +/obj/machinery/hydroponics/soil/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers) + if(tool.tool_behaviour != TOOL_SHOVEL) //Spades can still uproot plants on left click + return ..() + balloon_alert(user, "digging up soil...") - if(weapon.use_tool(src, user, 3 SECONDS, volume=50)) - balloon_alert(user, "bagged") - new sack_type(loc, src) //The bag handles sucking up the soil, stopping processing and setting relevants stats. + if(!tool.use_tool(src, user, 3 SECONDS, volume = 50)) + return ITEM_INTERACT_BLOCKING - return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + balloon_alert(user, "bagged") + new sack_type(loc, src) //The bag handles sucking up the soil, stopping processing and setting relevants stats. + return ITEM_INTERACT_SUCCESS /obj/machinery/hydroponics/soil/click_ctrl(mob/user) return CLICK_ACTION_BLOCKING //Soil has no electricity. From 4f3ea1dd8bef7afac285c1398f6395fe253a7b65 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 05:46:18 +0000 Subject: [PATCH 233/445] Automatic changelog for PR #96785 [ci skip] --- html/changelogs/AutoChangeLog-pr-96785.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96785.yml diff --git a/html/changelogs/AutoChangeLog-pr-96785.yml b/html/changelogs/AutoChangeLog-pr-96785.yml new file mode 100644 index 000000000000..1f3d3a9f1cda --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96785.yml @@ -0,0 +1,7 @@ +author: "lelandkemble" +delete-after: True +changes: + - bugfix: "the firepit structure that spawns somewhere(probably on icebox) can now be lit" + - bugfix: "you can hit a griddle on combat mode" + - bugfix: "you no longer succeed at digging up a hydroponics tray after failing the do_after()" + - code_imp: "50 files have been converted from attackby() to item_interaction" \ No newline at end of file From f8f67b9a521ad0dd687613fd2a89c6fb7afa1822 Mon Sep 17 00:00:00 2001 From: Hatterhat <31829017+Hatterhat@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:47:59 -0500 Subject: [PATCH 234/445] adjusts a bunch of rifle descriptions (ft. extended lore examines) (#97020) ## About The Pull Request Originally, this PR was just supposed to trim down the SKS's description and nudge its extended lore to `add_deep_lore()`. And then it got a little out of scope. In the end, a bunch of descriptions for rifles got tweaked, and some non-essential tidbits e.g. longer lore pieces got nudged to examining the gun twice. ## Why It's Good For The Game Whoa, more worldbuilding! Also, the comparatively long descriptions on some guns irked me, slightly, especially when Anne rolled out the `add_deep_lore` proc to guns in general. ## Changelog :cl: spellcheck: Adjusted some descriptions on some rifles (the Sakhno bolt-action, the SKS, the anti-materiel sniper) and nudged extended lore bits to examining twice. /:cl: --------- Co-authored-by: Hatterhat --- .../projectiles/guns/ballistic/rifle.dm | 175 ++++++++++++------ 1 file changed, 122 insertions(+), 53 deletions(-) diff --git a/code/modules/projectiles/guns/ballistic/rifle.dm b/code/modules/projectiles/guns/ballistic/rifle.dm index e94641bede8a..76400c7ffac2 100644 --- a/code/modules/projectiles/guns/ballistic/rifle.dm +++ b/code/modules/projectiles/guns/ballistic/rifle.dm @@ -52,11 +52,10 @@ /obj/item/gun/ballistic/rifle/boltaction name = "\improper Sakhno Precision Rifle" - desc = "A Sakhno Precision Rifle, a bolt action weapon that was (and certainly still is) popular with \ - frontiersmen, cargo runners, private security forces, explorers, and other unsavoury types. This particular \ - pattern of the rifle dates back all the way to 2440." - sawn_desc = "A sawn-off Sakhno Precision Rifle, popularly known as an \"Obrez\". \ - There was probably a reason it wasn't manufactured this short to begin with. \ + desc = "A bolt-action rifle that was - and certainly still is - popular with \ + frontiersmen, cargo runners, private security forces, explorers, and other unsavoury types." + sawn_desc = "A sawn-off bolt-action rifle, popularly known as an \"Obrez\". \ + There's probably a reason it wasn't manufactured this short to begin with. \ Despite the terrible nature of the modification, the weapon seems otherwise in good condition." icon_state = "sakhno" @@ -76,6 +75,23 @@ SET_BASE_PIXEL(-8, 0) +/obj/item/gun/ballistic/rifle/boltaction/add_deep_lore() + AddElement(/datum/element/examine_lore, \ + lore = "The Sakhno Precision Rifle's origins are closely tied to the Sakhno Concern, one of the Third Soviet Union's \ + state-ran weapons manufacturing concerns before the Union's collapse in the February Revolution, and subsequently \ + one of the Spinward Stellar Coalition's favored industrial concerns.
\ +
\ + Designed to be easily manufactured with the tooling and equipment that the nascent TSU had on-hand, \ + and with enough reliability and stopping power for all sorts of purposes, from hunting to home defense, \ + the Sakhno became a mainstay for Soviet citizens since 2440... \ + and, subsequently, a mainstay when those citizens became conspirators and combatants against dictatorship.
\ +
\ + Even to this day, though, Sakhnos in various conditions and states of modification end up all around the Spinward Sector. \ + Many are manufactured or refurbished with new internals, with reliability to match their history, \ + while others are from stockpiles of surplus or salvage, scavenged and refurbished time and time again... \ + which typically isn't great for reliability." \ + ) + /obj/item/gun/ballistic/rifle/boltaction/add_bayonet_point() AddComponent(/datum/component/bayonet_attachable, offset_x = 41, offset_y = 14, bayonet_overlay = "bayonet_thin") @@ -115,7 +131,7 @@ /obj/item/gun/ballistic/rifle/boltaction/harpoon name = "ballistic harpoon gun" - desc = "A weapon favored by carp hunters, but just as infamously employed by agents of the Animal Rights Consortium against human aggressors. Because it's ironic." + desc = "A weapon favored by carp hunters, but just as infamously employed by agents of the Animal Rights Consortium against human aggressors, because it's ironic." icon = 'icons/obj/weapons/guns/ballistic.dmi' icon_state = "speargun" inhand_icon_state = "speargun" @@ -126,16 +142,29 @@ SET_BASE_PIXEL(0, 0) +/obj/item/gun/ballistic/rifle/boltaction/harpoon/add_deep_lore() + AddElement(/datum/element/examine_lore, \ + lore = "Breech-loaded spearguns aren't a common sight around Nanotrasen stations, because \ + everything you could reasonably need a speargun for, like carp hunting, is better served \ + by more available options, such as laser guns or toolboxes.
\ +
\ + However, the Animal Rights Consortium intentionally sends some \"lucky\" operatives \ + out with such weapons, as their uncommon nature makes it very unambiguous who sent the madmen \ + who willingly use such weapons, and what drives them. \ + Or, equally as likely, some other cell of the Syndicate pilfered it to paint the Consortium as \ + responsible for whatever attack could be committed with a speargun.
\ +
\ + Either way, having a ballistic harpoon stuck in a limb is widely regarded as being a drag." \ + ) + /obj/item/gun/ballistic/rifle/boltaction/surplus name = "\improper Sakhno M2442 Army" - desc = "A modification of the Sakhno Precision Rifle, \"Sakhno M2442 Army\" is stamped into the side. \ - It is unknown what army this pattern of rifle was made for or if it was ever even used by an army \ - of any sort. What you can discern, however, is that its previous owner did not treat the weapon well. \ + desc = "A bolt-action rifle with \"Sakhno M2442 Army\" stamped into the side. \ + It's unknown what army this rifle was made for, or if it was ever used by one, but it's obvious the previous owner did not treat it well. \ For some reason, there's moisture all through the internals." - sawn_desc = "A sawn-off Sakhno Precision Rifle, popularly known as an \"Obrez\". \ - \"Sakhno M2442 Army\" is stamped into the side of it. \ - There was probably a reason it wasn't manufactured this short to begin with. \ - Cutting the weapon down seems to have not helped with the moisture problem." + sawn_desc = "A sawn-off bolt-action rifle, popularly known as an \"Obrez\", with \"Sakhno M2442 Army\" stamped into the side. \ + There's probably a reason it wasn't manufactured this short to begin with. \ + Cutting the weapon down has not helped with the moisture problem." icon_state = "sakhno_tactifucked" inhand_icon_state = "slopno" worn_icon_state = "slopno" @@ -149,7 +178,7 @@ name = "\improper Sakhno-Zhihao Sporting Rifle" desc = "An upgrade and modernisation of the original Sakhno rifle, made with such wonders as \ modern materials, a scope, and other impressive technological advancements that, to be honest, \ - were already around when the original weapon was designed. Surprisingly for a rifle of this type, \ + were already around when the original weapon was designed. Surprisingly, for a rifle of this type, \ the scope actually has magnification, rather than being decorative." icon_state = "zhihao" inhand_icon_state = "zhihao" @@ -157,8 +186,8 @@ can_be_sawn_off = TRUE sawn_desc = "A sawn-off Sakhno-Zhihao Sporting Rifle... Doing this was a sin, I hope you're happy. \ You are now probably one of the few people in the universe to ever hold an \"Obrez Moderna\". \ - All you had to do was take an allen wrench to the stock to take it off. But no, you just had to \ - go for the saw." + All you had to do was take an allen wrench to the stock to take it off, but no - \ + you just had to go for the saw." accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction/phasic /obj/item/gun/ballistic/rifle/boltaction/prime/Initialize(mapload) @@ -172,7 +201,9 @@ /obj/item/gun/ballistic/rifle/boltaction/donkrifle name = "\improper Donk Co. Jezail" - desc = "A mass-manufactured bolt-action sporting rifle with a distinctively long barrel. Powerful enough to take down a space bear from a thousand paces. The lengthened barrel gives it good accuracy and power, even at range." + desc = "A mass-manufactured bolt-action sporting rifle with a distinctively long barrel. \ + Powerful enough to take down a space bear from a thousand paces. \ + The lengthened barrel gives it good accuracy and power, even at range." w_class = WEIGHT_CLASS_HUGE lefthand_file = 'icons/mob/inhands/weapons/64x_guns_left.dmi' righthand_file = 'icons/mob/inhands/weapons/64x_guns_right.dmi' @@ -183,7 +214,21 @@ worn_icon_state = "jezail" accepted_magazine_type = /obj/item/ammo_box/magazine/internal/boltaction/jezail can_be_sawn_off = TRUE - sawn_desc = "A mass-manufactured bolt-action sporting rifle with a distinctively long barrel. Powerful enough to take down a space bear from a thousand paces. Its barrel has been cut off, so its power and accuracy have been impaired." + sawn_desc = "A mass-manufactured bolt-action sporting rifle with a formerly-distinctively long barrel. \ + Powerful enough to take down a space bear from... less than a thousand paces. \ + Its barrel has been cut off, so its power and accuracy have been impaired." + +/obj/item/gun/ballistic/rifle/boltaction/donkrifle/add_deep_lore() + AddElement(/datum/element/examine_lore, \ + lore = "Donk Co.'s Jezail line of bolt-action sporting rifles is, admittedly, a strange, almost archaic design, \ + coming from an even stranger place, as most people would not expect a logistics and food processing company to \ + create, market, and distribute bolt-action rifles.
\ +
\ + For better or for worse, though, they're pretty good as rifles - \ + though they're not nearly as ubiquitous as the Sakhno, especially in the Spinward Sector, \ + they still share the same chambering in .310 Strilka, and thus, the stopping power of the cartridge, with \ + no risk of receiving a refurbished, poorly-kept surplus arm that jams more than it shoots." \ + ) /obj/item/gun/ballistic/rifle/boltaction/donkrifle/sawoff(mob/user) //the heavy price one pays for fitting this in a backpack . = ..() @@ -193,10 +238,9 @@ /obj/item/gun/ballistic/rifle/rebarxbow name = "heated rebar crossbow" - desc = "A handcrafted crossbow. \ - Aside from conventional sharpened iron rods, it can also fire specialty ammo made from the atmos crystalizer - zaukerite, metallic hydrogen, and healium rods all work. \ - A makeshift Quiver can be made with an oxygen tank, wire, and a welder.\ - Very slow to reload - you can craft the crossbow with a crowbar to loosen the crossbar, but risk a misfire, or worse..." + desc = "A handcrafted crossbow. Aside from conventional sharpened iron rods, it can also fire specialty rods made from crystallized gases. \ + Very slow to reload, and probably worth pairing with a quiver. \ + Further modifications for increased capacity are... possible, if unwise." icon = 'icons/obj/weapons/guns/ballistic.dmi' icon_state = "rebarxbow" inhand_icon_state = "rebarxbow" @@ -270,7 +314,7 @@ /obj/item/gun/ballistic/rifle/rebarxbow/forced name = "stressed rebar crossbow" - desc = "Some idiot decided that they would risk shooting themselves in the face if it meant they could rack and reload the crossbow faster. Hopefully, it was worth it." + desc = "Some idiot decided that they would risk shooting themselves in the face if it meant they could rack and reload the crossbow faster. Hopefully it was worth it." // Feel free to add a recipe to allow you to change it back if you would like, I just wasn't sure if you could have two recipes for the same thing. can_misfire = TRUE draw_time = 0.5 SECONDS @@ -281,8 +325,9 @@ /obj/item/gun/ballistic/rifle/rebarxbow/syndie name = "syndicate rebar crossbow" - desc = "The syndicate liked the bootleg rebar crossbow NT engineers made, so they showed what it could be if properly developed. \ - auto reloads from backpack quiver when racking." + desc = "The Syndicate liked the bootleg rebar crossbow NT engineers made, and decided to one-up them by properly manufacturing a rebar \ + crossbow that's significantly less prone to maiming the user. \ + Automatically reloads from a backpack quiver when pulling the string." icon_state = "rebarxbowsyndie" inhand_icon_state = "rebarxbowsyndie" worn_icon_state = "rebarxbowsyndie" @@ -317,19 +362,18 @@ SET_BASE_PIXEL(-8, 0) -/obj/item/gun/ballistic/rifle/boltaction/pipegun/Initialize(mapload) - . = ..() +/obj/item/gun/ballistic/rifle/boltaction/pipegun/add_deep_lore() AddElement(/datum/element/examine_lore, \ lore_hint = span_notice("You can [EXAMINE_HINT("look closer")] to recall a tale about [src]."), \ lore = "You were told this story, in hushed tones, from a wizened man in a grey jumpsuit...

\ - It is said that the first slaying committed on a Nanotrasen space station was by an assistant.

\ - That this act, done by toolbox, maybe spear, was what consigned their kind to a life of destitution, rejection and violence.

\ - They carry the weight of this act visibly; the grey jumpsuit. Breathing deeply filtered air. And with bloodsoaked yellow hands clenched into fists.

\ - Eyes, sharp and waiting. Hunters in the dark.

\ - Eventually, these killing spirits sought to stake a claim on the metal tombs they were trapped within. Rejecting their status. Determined to be something more.

\ - This weapon is one such tool. And it is a grim one indeed. Wrought from scrap, pulled from the station's walls and floors and the very nails holding it together.
\ -
\ - It is a symbol that the true masters of this place are not those who merely inhabit it. But the one willing to twist it towards a killing intent." \ + It is said that the first slaying committed on a Nanotrasen space station was by an assistant.

\ + That this act, done by toolbox, maybe spear, was what consigned their kind to a life of destitution, rejection and violence.

\ + They carry the weight of this act visibly; the grey jumpsuit. Breathing deeply filtered air. And with bloodsoaked yellow hands clenched into fists.

\ + Eyes, sharp and waiting. Hunters in the dark.

\ + Eventually, these killing spirits sought to stake a claim on the metal tombs they were trapped within. Rejecting their status. Determined to be something more.

\ + This weapon is one such tool. And it is a grim one indeed - wrought from scrap, pulled from the station's walls and floors and the very nails holding it together.
\ +
\ + It is a symbol that the true masters of this place are not those who merely inhabit it, but the one willing to twist it towards a killing intent." \ ) /obj/item/gun/ballistic/rifle/boltaction/pipegun/add_bayonet_point() @@ -370,7 +414,7 @@ /obj/item/gun/ballistic/rifle/boltaction/pipegun/prime name = "regal pipegun" - desc = "To call this 'regal' is a cruel irony. For the only noteworthy quality of nobility is in how it is wielded to kill. \ + desc = "To call this 'regal' is a cruel irony, for the only noteworthy quality of nobility is in how it is wielded to kill. \ All monarchs deserve to be crowned. But none will remember the dead tyrant for the red stain they left on the carpet." icon_state = "regal_pipegun" inhand_icon_state = "regal_pipegun" @@ -444,9 +488,9 @@ /obj/item/gun/ballistic/rifle/sniper_rifle name = "anti-materiel sniper rifle" - desc = "A boltaction anti-materiel rifle, utilizing .50 BMG cartridges. While technically outdated in modern arms markets, it still works exceptionally well as \ - an anti-personnel rifle. In particular, the employment of modern armored MODsuits utilizing advanced armor plating has given this weapon a new home on the battlefield. \ - It is also able to be suppressed... somehow." + desc = "A surprisingly compact bolt-action anti-materiel rifle, utilizing .50 BMG cartridges. \ + While technically outdated in modern arms markets, it still works exceptionally well \ + in anti-personnel and light anti-armor roles. Also, somehow, capable of fitting a suppressor." icon = 'icons/obj/weapons/guns/ballistic.dmi' icon_state = "sniper" lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' @@ -482,6 +526,21 @@ . = ..() AddComponent(/datum/component/scope, range_modifier = 4) //enough range to at least make extremely good use of the penetrator rounds +/obj/item/gun/ballistic/rifle/sniper_rifle/add_deep_lore() + AddElement(/datum/element/examine_lore, \ + lore = "Modern arms and armor technology has come a long way since man's first strides into space, \ + from high-throughput directed energy weaponry to advanced energy shielding and everything in between. \ + Such advancements in technology, then, should have put the role of the anti-materiel rifle to rest, \ + as other solutions to the armor problems it solves would have become more accessible - in theory, anyway.
\ +
\ + In practice, the problem with advanced armor is that it's expensive and/or heavy - \ + both of which are constraints that limit the usage of such defensive solutions, \ + especially on smaller targets such as station-ready exosuits or human-scale MODsuits.
\ +
\ + These constraints gave the anti-materiel rifle concept a new lease on life, \ + which Scarborough Arms is happy to provide to prospective buyers." \ + ) + /obj/item/gun/ballistic/rifle/sniper_rifle/reset_fire_cd() . = ..() if(suppressed) @@ -490,20 +549,16 @@ playsound(src, 'sound/machines/eject.ogg', 50, TRUE) /obj/item/gun/ballistic/rifle/sniper_rifle/syndicate - desc = "A boltaction anti-materiel rifle, utilizing .50 BMG cartridges. While technically outdated in modern arms markets, it still works exceptionally well as \ - an anti-personnel rifle. In particular, the employment of modern armored MODsuits utilizing advanced armor plating has given this weapon a new home on the battlefield. \ - It is also able to be suppressed... somehow. This one seems to have a little picture of someone in a blood-red MODsuit stenciled on it, pointing at a green floppy disk. \ - Who knows what that might mean." + desc = parent_type::desc + " This one seems to have a little picture of someone in a Gorlex Marauders blood-red MODsuit stenciled on it, \ + pointing at a green floppy disk. Who knows what that might mean." pin = /obj/item/firing_pin/implant/pindicate // SKS semi-automatic rifle // /obj/item/gun/ballistic/rifle/sks name = "\improper Sakhno SKS semi-automatic rifle" - desc = "A revival of the ancient SKS semi-automatic rifle, redesigned to utilize .310 Strilka rounds. Produced to celebrate the \ - establishment of the Third Soviet Union in the Spinward Sector. In the wake of the union's collapse, these weapons now hold a \ - unique place in history amongst the populace of the sector. However, they are strangely rarer than the Sakhno M2442 Army. \ - Frontier settlers are known for owning one of these for hunting purposes. Or fighting off annoying tax collectors." + desc = "A revival of the ancient SKS semi-automatic rifle, redesigned to utilize .310 Strilka rounds. \ + Loses some per-cartridge stopping power due to quirks in the gas-operation system." icon = 'icons/obj/weapons/guns/wide_guns.dmi' icon_state = "sks" worn_icon_state = "sks" @@ -522,14 +577,27 @@ SET_BASE_PIXEL(-8, 0) +/obj/item/gun/ballistic/rifle/sks/add_deep_lore() + AddElement(/datum/element/examine_lore, \ + lore = "The Sakhno Concern's modernized reproductions of the SKS were commissioned by the \ + leadership of the Third Soviet Union to celebrate their establishment in the Spinward Sector.
\ +
\ + In the wake of the Union's collapse, however, these weapons now hold a unique place in history \ + amongst the populace of the sector as a reminder of what came before - and, possibly, the hope of what may come after. \ + However, both parts kits and completed rifles are, strangely, rarer than the Sakhno M2442 Army.
\ +
\ + Frontier settlers are known to own these for hunting purposes, or fighting off annoying tax collectors. \ + Some argue that these are the same activity." \ + ) + /obj/item/gun/ballistic/rifle/sks/add_bayonet_point() AddComponent(/datum/component/bayonet_attachable, offset_x = 38, offset_y = 12) /obj/item/gun/ballistic/rifle/sks/chekhov name = "\improper Chekhov's SKS semi-automatic rifle" - desc = "A revival of the ancient SKS semi-automatic rifle, redesigned to utilize .310 Strilka rounds. The name \ - 'Chekhov' is engraved in the side of the stock. You feel like this had some kind of significance at one point, \ - but you cannot be sure as to what that might have been. Or whether that true meaning has yet to reveal itself." + desc = parent_type::desc + " The name 'Chekhov' is engraved in the side of the stock. \ + You feel like this had some kind of significance at one point, but you cannot be sure as to what that might have been, \ + or whether that true meaning has yet to reveal itself." /obj/item/gun/ballistic/rifle/sks/empty spawn_magazine_type = /obj/item/ammo_box/magazine/internal/sks/empty @@ -537,9 +605,10 @@ // lahti-l39 anti material rifle // /obj/item/gun/ballistic/automatic/lahti - name = "\improper Lahti L-39" - desc = "The Lahti L-39, now manufactured in space with better materials making it more portable and reliable- still loaded in the same massive cartridge, \ - this thing was made to go through a tank and come out the other end- imagine what it could do to an exosuit, there's also a completely useless sight which is totally obstructed by the magazine." + name = "anti-tank rifle" + desc = "A reproduction of an ancient anti-tank rifle, manufactured in space with lighter, stronger materials, making it more portable and reliable. \ + Chambered for a concerningly large 20x138mm cartridge, which, too, has received a modern facelift, \ + making it terrifyingly effective, especially against exosuits." icon = 'icons/obj/weapons/guns/lahtil39.dmi' icon_state = "lahtil" inhand_icon_state = "sniper" From a6a3eb718a01f3872f3eaa737625480e776fb39a Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 05:48:18 +0000 Subject: [PATCH 235/445] Automatic changelog for PR #97020 [ci skip] --- html/changelogs/AutoChangeLog-pr-97020.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97020.yml diff --git a/html/changelogs/AutoChangeLog-pr-97020.yml b/html/changelogs/AutoChangeLog-pr-97020.yml new file mode 100644 index 000000000000..f9be299bdfba --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97020.yml @@ -0,0 +1,4 @@ +author: "Hatterhat" +delete-after: True +changes: + - spellcheck: "Adjusted some descriptions on some rifles (the Sakhno bolt-action, the SKS, the anti-materiel sniper) and nudged extended lore bits to examining twice." \ No newline at end of file From 43e7628b03a24fbfb499d5f1369ab3e5dd5c4a4c Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 06:00:25 +0000 Subject: [PATCH 236/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-96785.yml | 7 ------- html/changelogs/AutoChangeLog-pr-96809.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97020.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97065.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97167.yml | 4 ---- html/changelogs/archive/2026-07.yml | 17 +++++++++++++++++ 6 files changed, 17 insertions(+), 23 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-96785.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96809.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97020.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97065.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97167.yml diff --git a/html/changelogs/AutoChangeLog-pr-96785.yml b/html/changelogs/AutoChangeLog-pr-96785.yml deleted file mode 100644 index 1f3d3a9f1cda..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96785.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "lelandkemble" -delete-after: True -changes: - - bugfix: "the firepit structure that spawns somewhere(probably on icebox) can now be lit" - - bugfix: "you can hit a griddle on combat mode" - - bugfix: "you no longer succeed at digging up a hydroponics tray after failing the do_after()" - - code_imp: "50 files have been converted from attackby() to item_interaction" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96809.yml b/html/changelogs/AutoChangeLog-pr-96809.yml deleted file mode 100644 index 992616d54dfb..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96809.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "CabinetOnFire" -delete-after: True -changes: - - code_imp: "Adds debug flags that can be selected when building in VS code" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97020.yml b/html/changelogs/AutoChangeLog-pr-97020.yml deleted file mode 100644 index f9be299bdfba..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97020.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Hatterhat" -delete-after: True -changes: - - spellcheck: "Adjusted some descriptions on some rifles (the Sakhno bolt-action, the SKS, the anti-materiel sniper) and nudged extended lore bits to examining twice." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97065.yml b/html/changelogs/AutoChangeLog-pr-97065.yml deleted file mode 100644 index 96086a16edad..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97065.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "hry-gh" -delete-after: True -changes: - - qol: "the escape menu now runs clientside, and should be considerably more responsive" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97167.yml b/html/changelogs/AutoChangeLog-pr-97167.yml deleted file mode 100644 index 80c66841289a..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97167.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "PapaMichael" -delete-after: True -changes: - - bugfix: "Departmental photo albums now properly save their photos between rounds" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index edf4bf6dbf8e..2b5f461fd758 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -774,12 +774,18 @@ - map: Add Tombstone Escape Shuttle that can be unlocked via a biohazard event. (Blob, zombies, or a severe enough disease) 2026-07-26: + CabinetOnFire: + - code_imp: Adds debug flags that can be selected when building in VS code Fghj240: - balance: Disease stage speed and advanced disease symptom delay RNG has been reduced - balance: Recovering from an advanced disease makes you more resistant to similar advanced diseases. - spellcheck: Viral adaptation and Viral evolution are always neutered. - qol: Spacers can choose their height a little + Hatterhat: + - spellcheck: Adjusted some descriptions on some rifles (the Sakhno bolt-action, + the SKS, the anti-materiel sniper) and nudged extended lore bits to examining + twice. Melbert: - rscadd: The AI and Cyborg upload computers have been replaced with module racks, a physical manifestation of AI laws. @@ -798,3 +804,14 @@ - rscadd: TGUI "state laws" window - refactor: Refactored silicon laws in its entirely, report any oddities - bugfix: Fix perma stun from grabs + PapaMichael: + - bugfix: Departmental photo albums now properly save their photos between rounds + hry-gh: + - qol: the escape menu now runs clientside, and should be considerably more responsive + lelandkemble: + - bugfix: the firepit structure that spawns somewhere(probably on icebox) can now + be lit + - bugfix: you can hit a griddle on combat mode + - bugfix: you no longer succeed at digging up a hydroponics tray after failing the + do_after() + - code_imp: 50 files have been converted from attackby() to item_interaction From 6cbfa8898c0f4fea7b858d48eb8002d4d2b3dca0 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 26 Jul 2026 20:00:37 +0200 Subject: [PATCH 237/445] [NO GBP] Actually fixes hair gradients this time (#97172) ## About The Pull Request I forgot that making gradient a sub-overlay would color them, making them invisible on characters with darker hair. By having a shared overlay which applies the alpha and moving both hair and gradient to it we can solve this. Also moved emissives from sub-overlays because that breaks their rotation due to a BYOND bug ## Changelog :cl: fix: Fixed hair gradients, again /:cl: --- .../surgery/bodyparts/head_hair_and_lips.dm | 59 +++++++++++++----- code/modules/unit_tests/_unit_tests.dm | 1 + .../unit_tests/screenshot_hair_gradient.dm | 18 ++++++ ...screenshot_hair_gradient_hair_gradient.png | Bin 0 -> 674 bytes ...t_hair_gradient_hair_gradient_ethereal.png | Bin 0 -> 1052 bytes 5 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 code/modules/unit_tests/screenshot_hair_gradient.dm create mode 100644 code/modules/unit_tests/screenshots/screenshot_hair_gradient_hair_gradient.png create mode 100644 code/modules/unit_tests/screenshots/screenshot_hair_gradient_hair_gradient_ethereal.png diff --git a/code/modules/surgery/bodyparts/head_hair_and_lips.dm b/code/modules/surgery/bodyparts/head_hair_and_lips.dm index 3b01a879d347..51d42e0bd382 100644 --- a/code/modules/surgery/bodyparts/head_hair_and_lips.dm +++ b/code/modules/surgery/bodyparts/head_hair_and_lips.dm @@ -78,10 +78,11 @@ set_overlay_hair_color(facial_hair_overlay, facial_hair_color) // Emissive blocker if(blocks_emissive != EMISSIVE_BLOCK_NONE) - var/mutable_appearance/em_block = emissive_blocker(facial_hair_overlay.icon, facial_hair_overlay.icon_state, location, alpha = facial_hair_alpha) + var/mutable_appearance/em_block = emissive_blocker(facial_hair_overlay.icon, facial_hair_overlay.icon_state, location, -HAIR_LAYER, alpha = facial_hair_alpha) if (dropped) em_block = image(em_block, dir = SOUTH) - facial_hair_overlay.overlays += em_block + worn_face_offset?.apply_offset(em_block) + . += em_block //Offsets worn_face_offset?.apply_offset(facial_hair_overlay) @@ -89,13 +90,25 @@ //Gradients var/facial_hair_gradient_style = get_hair_gradient_style(GRADIENT_FACIAL_HAIR_KEY) - if(facial_hair_gradient_style != SPRITE_ACCESSORY_NONE) - var/facial_hair_gradient_color = get_hair_gradient_color(GRADIENT_FACIAL_HAIR_KEY) - var/image/facial_hair_gradient_overlay = get_gradient_overlay(icon(sprite_accessory.icon, sprite_accessory.icon_state), -HAIR_LAYER, SSaccessories.facial_hair_gradients_list[facial_hair_gradient_style], facial_hair_gradient_color, dropped) - facial_hair_gradient_overlay.alpha = facial_hair_alpha - facial_hair_overlay.appearance_flags |= KEEP_TOGETHER - facial_hair_overlay.overlays += facial_hair_gradient_overlay + if(facial_hair_gradient_style == SPRITE_ACCESSORY_NONE) + return . + + var/facial_hair_gradient_color = get_hair_gradient_color(GRADIENT_FACIAL_HAIR_KEY) + var/image/facial_hair_gradient_overlay = get_gradient_overlay(icon(sprite_accessory.icon, sprite_accessory.icon_state), -HAIR_LAYER, SSaccessories.facial_hair_gradients_list[facial_hair_gradient_style], facial_hair_gradient_color, dropped) + if (facial_hair_alpha == 255) + . += facial_hair_gradient_overlay + return . + // If we have a gradient and hair alpha, we need to merge them into a single appearance by having a shared KEEP_TOGETHER holder + // since adding our gradient as an overlay would cause it to get colored + . -= facial_hair_overlay + facial_hair_overlay.alpha = 255 + var/image/shared_holder = image(layer = -HAIR_LAYER, dir = image_dir) + shared_holder.alpha = facial_hair_alpha + shared_holder.appearance_flags |= KEEP_TOGETHER + shared_holder.overlays += facial_hair_overlay + shared_holder.overlays += facial_hair_gradient_overlay + . += shared_holder return . /// Used in constructing the hair overlays - handles just the hair on top of the head @@ -131,22 +144,34 @@ hair_overlay.pixel_z = hair_sprite_accessory.y_offset // Emissive blocker if(blocks_emissive != EMISSIVE_BLOCK_NONE) - var/mutable_appearance/em_block = emissive_blocker(hair_overlay.icon, hair_overlay.icon_state, location, alpha = hair_alpha) + var/mutable_appearance/em_block = emissive_blocker(hair_overlay.icon, hair_overlay.icon_state, location, -HAIR_LAYER, alpha = hair_alpha) if (dropped) em_block = image(em_block, dir = SOUTH) - hair_overlay.overlays += em_block + em_block.pixel_z = hair_sprite_accessory.y_offset + . += em_block // Offsets worn_face_offset?.apply_offset(hair_overlay) . += hair_overlay // Gradients var/hair_gradient_style = get_hair_gradient_style(GRADIENT_HAIR_KEY) - if(hair_gradient_style != SPRITE_ACCESSORY_NONE) - var/hair_gradient_color = get_hair_gradient_color(GRADIENT_HAIR_KEY) - var/image/hair_gradient_overlay = get_gradient_overlay(base_icon, hair_overlay.layer, SSaccessories.hair_gradients_list[hair_gradient_style], hair_gradient_color, dropped) - hair_gradient_overlay.pixel_z = hair_sprite_accessory.y_offset - hair_overlay.appearance_flags |= KEEP_TOGETHER - hair_overlay.overlays += hair_gradient_overlay - + if(hair_gradient_style == SPRITE_ACCESSORY_NONE) + continue + + var/hair_gradient_color = get_hair_gradient_color(GRADIENT_HAIR_KEY) + var/image/hair_gradient_overlay = get_gradient_overlay(base_icon, hair_overlay.layer, SSaccessories.hair_gradients_list[hair_gradient_style], hair_gradient_color, dropped) + hair_gradient_overlay.pixel_z = hair_sprite_accessory.y_offset + if (hair_alpha == 255) + . += hair_gradient_overlay + continue + + . -= hair_overlay + hair_overlay.alpha = 255 + var/image/shared_holder = image(layer = -HAIR_LAYER, dir = image_dir) + shared_holder.alpha = facial_hair_alpha + shared_holder.appearance_flags |= KEEP_TOGETHER + shared_holder.overlays += hair_overlay + shared_holder.overlays += hair_gradient_overlay + . += shared_holder return . /// Helper for setting hair color of an overlay appropriately diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index b6aa9aa7fee8..02505eaa9848 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -306,6 +306,7 @@ #include "screenshot_debrain.dm" #include "screenshot_digi.dm" #include "screenshot_dynamic_human_icons.dm" +#include "screenshot_hair_gradient.dm" #include "screenshot_high_luminosity_eyes.dm" #include "screenshot_humanoids.dm" #include "screenshot_husk.dm" diff --git a/code/modules/unit_tests/screenshot_hair_gradient.dm b/code/modules/unit_tests/screenshot_hair_gradient.dm new file mode 100644 index 000000000000..5d1ae6d64334 --- /dev/null +++ b/code/modules/unit_tests/screenshot_hair_gradient.dm @@ -0,0 +1,18 @@ +/datum/unit_test/screenshot_hair_gradient + +/datum/unit_test/screenshot_hair_gradient/Run() + // Check if hair gradients render properly + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human/dummy/consistent) + human.set_hairstyle(/datum/sprite_accessory/hair/bedheadv4::name) + human.set_haircolor(COLOR_BLACK) + human.set_hair_gradient_color(COLOR_PINK) + human.set_hair_gradient_style(/datum/sprite_accessory/gradient/reflected_inverse::name) + test_screenshot("hair_gradient", getFlatIcon(human, no_anim = TRUE, defdir = SOUTH)) + + // Now check it on an ethereal for hair alpha + var/mob/living/carbon/human/ethereal = allocate(/mob/living/carbon/human/dummy/consistent) + ethereal.set_species(/datum/species/ethereal) + ethereal.set_hairstyle(/datum/sprite_accessory/hair/bedheadv4::name) + ethereal.set_hair_gradient_color(COLOR_PINK) + ethereal.set_hair_gradient_style(/datum/sprite_accessory/gradient/reflected_inverse::name) + test_screenshot("hair_gradient_ethereal", getFlatIcon(ethereal, no_anim = TRUE, defdir = SOUTH)) diff --git a/code/modules/unit_tests/screenshots/screenshot_hair_gradient_hair_gradient.png b/code/modules/unit_tests/screenshots/screenshot_hair_gradient_hair_gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..193ba8db2788f727cd9da1f192d2a4389f196770 GIT binary patch literal 674 zcmV;T0$u%yP)}V@gkBOHY1ZWq)90n{|ANXKZ;~VQf-XZ&XM zucrUt(VcgF$d{kwtF`R1yugu}tb&QWdvV&HlIp*-BHt?Y00001bW%=J06^y0W&i*H zWO`IsbVOxyV{&P5bZKvH004NLQ&wDl7~{mFbswbr40oNWt1{o#NM+qw2q|re=DRga!2GlK)E>M z|Ds=#6T+WjZobu8$g1sjrzjSSZnu*omX=p~z16k;`o^ZKZwdTiyWW@qPCOS ztKR@56_B@}ssZ^wpe3mN$Li|Qb~4zGx_?x#`4R;^b?Wu&ce0R0NJ8w2O#lD@07*qo IM6N<$g3n?nOaK4? literal 0 HcmV?d00001 diff --git a/code/modules/unit_tests/screenshots/screenshot_hair_gradient_hair_gradient_ethereal.png b/code/modules/unit_tests/screenshots/screenshot_hair_gradient_hair_gradient_ethereal.png new file mode 100644 index 0000000000000000000000000000000000000000..d3850e1e82efb746ebd6138fcf93071d8aad3d35 GIT binary patch literal 1052 zcmV+%1mpXOP)V=-0C=1w$Gr-GAQT4B+4~e9x<`MjODN%>?;vRDP=QLleuLXN0-N5AgR+L+ zSZaQ5NUXz1?8~7gNGvn46!V!{Pi?+MNN`Rp%Vz+>H3d$P#0h+fM3zl+5 zLI3~*Zb?KzR9J<@m%(ltR}_Xnw-~pMm66kGXw`Uu(_Vzaar()Awl>YWs0f50Ew29iQ^=a0cQH{Wt3t9nS>FQM&40!QtEdeY7 zjXO6|b%5XByy0!ooCri20N}gS?E=7$&Xd%^!9iBquV4Fm>+a3eFAU)I{vH5t-yJD| zD5oIv#ZsZu@cHS*^Mt$iTXuPQIUIZX)X4_E-?(? zuz&D3L94~PZg-K+5t-vTC5W*AJA?LmyV=BVHXE1Xb9@#11jljc^?LNLX!LqL9LK@; zeJ;i4@DH1fb`Y@haaXAhlv1#JIkv77t0$Amt#-RTbk40SI>}^mOD9&}y&SvvA{2v8 z<-y}eSpeVnaU2IAy!)Wj;lblaSuyBTRM!N>J|Kk1*|uHObsbI9kPuQ)o9B6$rn#0* zryr^SjO_zLi0^IN{z2DuR#sL*yNm#UVHn|YsZ_$Y?IM5*#K;Ip=lOg-bQpDZR?#_@ zWhq5MxdvR<1y1g8n513KQ`LI`hTXmEx@ekq4x&^lp=ny^+;JT8`8>957ljbHsQ{vK zT^G|d*W|gTX*iA({$vpXn5MZlNfgxI1rZsB5!OQVcp8lYXu;`l$^n@I={TAa+qR3* zw0#Hw@Nd(}=kuX0S_d-!Qqp;}Pka!7gs?2@lugz_)H#4Fv1Xfo(G)NiD4MwV<=%&I^oz>EEf?%!^pqf*%s<*L Date: Sun, 26 Jul 2026 18:00:58 +0000 Subject: [PATCH 238/445] Automatic changelog for PR #97172 [ci skip] --- html/changelogs/AutoChangeLog-pr-97172.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97172.yml diff --git a/html/changelogs/AutoChangeLog-pr-97172.yml b/html/changelogs/AutoChangeLog-pr-97172.yml new file mode 100644 index 000000000000..109cd400d24c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97172.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed hair gradients, again" \ No newline at end of file From 9e3cf63e0dfefe51afcee9b7a2b628163da37394 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 18:15:27 +0000 Subject: [PATCH 239/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97172.yml | 4 ---- html/changelogs/archive/2026-07.yml | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97172.yml diff --git a/html/changelogs/AutoChangeLog-pr-97172.yml b/html/changelogs/AutoChangeLog-pr-97172.yml deleted file mode 100644 index 109cd400d24c..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97172.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed hair gradients, again" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index 2b5f461fd758..13924eb51264 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -806,6 +806,8 @@ - bugfix: Fix perma stun from grabs PapaMichael: - bugfix: Departmental photo albums now properly save their photos between rounds + SmArtKar: + - bugfix: Fixed hair gradients, again hry-gh: - qol: the escape menu now runs clientside, and should be considerably more responsive lelandkemble: From 5228664898e3c0bea5443204e0a7cf7c5dbcefe2 Mon Sep 17 00:00:00 2001 From: Iajret <8430839+Iajret@users.noreply.github.com> Date: Sun, 26 Jul 2026 21:21:10 +0300 Subject: [PATCH 240/445] Fix for vent taping and hierophant kill not granting style points multiplier (#97173) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## About The Pull Request Vent tapping supposed to send a signal, but apparently that line of code was lost during some silly merge skew as of Hiero, it had wrong faction set on it, and signal handler on style component ignored it altogether изображение изображение ## Why It's Good For The Game always good when code works as intended ## Changelog :cl: fix: Defeating Hiero now gives style point multiplier fix: Successfully tapping a vent will grant a style point multiplication too, as it was supposed to /:cl: --- code/datums/components/style/style.dm | 2 +- code/datums/components/style/style_meter.dm | 2 +- code/game/objects/structures/lavaland/ore_vent.dm | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/code/datums/components/style/style.dm b/code/datums/components/style/style.dm index 3c49c9f11727..e5a4de576177 100644 --- a/code/datums/components/style/style.dm +++ b/code/datums/components/style/style.dm @@ -444,7 +444,7 @@ if(died == parent) change_points(-500, use_multiplier = FALSE) return - else if(mob_parent.faction_check_atom(died) || !died.has_faction(FACTION_MINING) || (died.z != mob_parent.z) || !(died in view(mob_parent.client?.view, get_turf(mob_parent)))) + else if(mob_parent.faction_check_atom(died) || !ismining(died) || (died.z != mob_parent.z) || !(died in view(mob_parent.client?.view, get_turf(mob_parent)))) return if(ismegafauna(died)) diff --git a/code/datums/components/style/style_meter.dm b/code/datums/components/style/style_meter.dm index 83a27218cd82..983b0a0343dd 100644 --- a/code/datums/components/style/style_meter.dm +++ b/code/datums/components/style/style_meter.dm @@ -145,7 +145,7 @@ return // If we have an active style meter, we're on someone's face. Use them to check if the dead megafauna could be credited to them... var/mob/mob_parent = style_meter.parent - if(mob_parent.faction_check_atom(died) || !died.has_faction(FACTION_MINING) || (died.z != mob_parent.z) || !(died in view(mob_parent.client?.view, get_turf(mob_parent)))) + if(mob_parent.faction_check_atom(died) || !ismining(died) || (died.z != mob_parent.z) || !(died in view(mob_parent.client?.view, get_turf(mob_parent)))) return if(ismegafauna(died)) diff --git a/code/game/objects/structures/lavaland/ore_vent.dm b/code/game/objects/structures/lavaland/ore_vent.dm index f70db6870f6b..ff1f18cb5a1f 100644 --- a/code/game/objects/structures/lavaland/ore_vent.dm +++ b/code/game/objects/structures/lavaland/ore_vent.dm @@ -354,6 +354,7 @@ return for(var/mob/living/miner in range(7, src)) //Give the miners who are near the vent points and xp. + SEND_SIGNAL(miner, COMSIG_LIVING_ON_VENT_WIN, src) var/obj/item/card/id/user_id_card = miner.get_idcard(TRUE) if(miner.stat <= SOFT_CRIT) miner.mind?.adjust_experience(/datum/skill/mining, MINING_SKILL_BOULDER_SIZE_XP * boulder_size) From a5dedcbce96f33c54d6593d347cc7f760bb33aca Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 18:21:30 +0000 Subject: [PATCH 241/445] Automatic changelog for PR #97173 [ci skip] --- html/changelogs/AutoChangeLog-pr-97173.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97173.yml diff --git a/html/changelogs/AutoChangeLog-pr-97173.yml b/html/changelogs/AutoChangeLog-pr-97173.yml new file mode 100644 index 000000000000..d34812d58a82 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97173.yml @@ -0,0 +1,5 @@ +author: "Iajret" +delete-after: True +changes: + - bugfix: "Defeating Hiero now gives style point multiplier" + - bugfix: "Successfully tapping a vent will grant a style point multiplication too, as it was supposed to" \ No newline at end of file From 5bd6ab033aa6c31a658b1f1bac9ace57e5e46006 Mon Sep 17 00:00:00 2001 From: YesterdaysPromise <122572637+YesterdaysPromise@users.noreply.github.com> Date: Sun, 26 Jul 2026 20:23:17 +0200 Subject: [PATCH 242/445] Resolves a whole bunch of missing worn icon issues. [Materials, tiles, surgery tools, flash, lighting tubes] (#97015) ## About The Pull Request Randomly recalled I've seen the "missing worn icon" sprite pop up (worn icons are sprites for items when worn on belt/in suit storage), but forgot for which item. So I loaded up the game on the branch and did some digging... Turns out whole bunch of relatively common items displays it, so it could have been any of these. Thus, I fixed all the most egregious ones anyway. And while I was in the file I noticed that several items from my testing actually had unique sprites in it already, so I hooked those up. There's probably several items that still have missing worn sprites, have unused ones in the file, or require distinct ones... But you know, small steps, and I'd like to think I carved a good chunk. All materials and then some might be too boresome to look at, but here's at least some examples in game, and most of the stuff from the file. Interdyne 1 Interdyne 1 ## Why It's Good For The Game Missing textures, even small ones aren't pretty to look at. ## Changelog :cl: fix: Scythes, shovels, flasks, shears and secutors will now display their worn sprites when in belt/suit storage. fix: Tiles, circuit components, wands, types of leather and door remotes no longer display missing/incorrect sprites when worn on belt/suit storage. image: Added missing metalhydrogen inhand sprite, and fixed the colours of the adamantine one. image: Added missing belt/suit storage sprites for all basic material and glass types, flash, shards, light tubes, light bulbs, drapes, and surgery tools which can be put in those slots. image: Hatchets, beakers, secutors, shears etc. had their sprites corrected, as despite showing on left side of front-facing sprite, they also showed up on left facing profile (with right side of the body to the front) image: Added unique belt/suit storage sprites for carp, ratvar, and narsie plushies. /:cl: --- code/game/objects/items/plushes.dm | 3 +++ .../game/objects/items/stacks/sheets/glass.dm | 10 ++++++++ .../objects/items/stacks/sheets/leather.dm | 4 ++++ .../objects/items/stacks/sheets/mineral.dm | 22 ++++++++++++++++-- .../items/stacks/sheets/runed_metal.dm | 1 + .../items/stacks/sheets/sheet_types.dm | 7 ++++++ .../objects/items/stacks/tiles/tile_types.dm | 1 + code/game/objects/items/tools/control_wand.dm | 1 + code/modules/assembly/flash.dm | 1 + code/modules/hydroponics/hydroitemdefines.dm | 6 +++-- code/modules/mining/equipment/mining_tools.dm | 2 ++ code/modules/power/lighting/light_items.dm | 2 ++ code/modules/projectiles/guns/magic/wand.dm | 1 + .../reagent_containers/cups/bottle.dm | 1 + .../reagent_containers/cups/drinkingglass.dm | 1 + .../reagent_containers/cups/drinks.dm | 3 +++ .../reagent_containers/cups/glassbottle.dm | 1 + code/modules/surgery/surgery_tools.dm | 5 ++++ code/modules/wiremod/core/component.dm | 1 + icons/mob/clothing/belt.dmi | Bin 53145 -> 58797 bytes icons/mob/clothing/belt_mirror.dmi | Bin 98807 -> 114829 bytes icons/mob/inhands/items/sheets_lefthand.dmi | Bin 9015 -> 10001 bytes icons/mob/inhands/items/sheets_righthand.dmi | Bin 9664 -> 9814 bytes 23 files changed, 69 insertions(+), 4 deletions(-) diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm index e0f5d1faa7a8..cfe728e7f861 100644 --- a/code/game/objects/items/plushes.dm +++ b/code/game/objects/items/plushes.dm @@ -396,6 +396,7 @@ desc = "An adorable stuffed toy that resembles a space carp." icon = 'icons/map_icons/items/_item.dmi' icon_state = "/obj/item/toy/plush/carpplushie" + worn_icon = "carp" post_init_icon_state = "map_plushie_carp" greyscale_config = /datum/greyscale_config/plush_carp greyscale_colors = "#cc99ff#000000" @@ -416,6 +417,7 @@ name = "\improper Ratvar plushie" desc = "An adorable plushie of the clockwork justiciar himself with new and improved spring arm action." icon_state = "plushvar" + worn_icon = "ratvar" divine = TRUE var/obj/item/toy/plush/narplush/clash_target gender = MALE //he's a boy, right? @@ -503,6 +505,7 @@ name = "\improper Nar'Sie plushie" desc = "A small stuffed doll of the elder goddess Nar'Sie. Who thought this was a good children's toy?" icon_state = "narplush" + worn_icon = "narsie" divine = TRUE var/clashing gender = FEMALE //it's canon if the toy is diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm index 1fb239c1b330..9df6c8d672ce 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -20,6 +20,7 @@ GLOBAL_LIST_INIT(glass_recipes, list ( \ desc = "HOLY SHEET! That is a lot of glass." singular_name = "glass sheet" icon_state = "sheet-glass" + worn_icon_state = "sheet-glass" inhand_icon_state = "sheet-glass" mats_per_unit = list(/datum/material/glass=SHEET_MATERIAL_AMOUNT) armor_type = /datum/armor/sheet_glass @@ -99,6 +100,7 @@ GLOBAL_LIST_INIT(pglass_recipes, list ( \ desc = "A glass sheet made out of a plasma-silicate alloy. It looks extremely tough and heavily fire resistant." singular_name = "plasma glass sheet" icon_state = "sheet-pglass" + worn_icon_state = "sheet-pglass" inhand_icon_state = "sheet-pglass" mats_per_unit = list(/datum/material/alloy/plasmaglass=SHEET_MATERIAL_AMOUNT) material_type = /datum/material/alloy/plasmaglass @@ -161,6 +163,7 @@ GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \ desc = "Glass which seems to have rods or something stuck in them." singular_name = "reinforced glass sheet" icon_state = "sheet-rglass" + worn_icon_state = "sheet-rglass" inhand_icon_state = "sheet-rglass" mats_per_unit = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT * 0.5, /datum/material/glass=SHEET_MATERIAL_AMOUNT) armor_type = /datum/armor/sheet_rglass @@ -201,6 +204,7 @@ GLOBAL_LIST_INIT(prglass_recipes, list ( \ desc = "A glass sheet made out of a plasma-silicate alloy and a rod matrix. It looks hopelessly tough and nearly fire-proof!" singular_name = "reinforced plasma glass sheet" icon_state = "sheet-prglass" + worn_icon_state = "sheet-prglass" inhand_icon_state = "sheet-prglass" mats_per_unit = list(/datum/material/alloy/plasmaglass=SHEET_MATERIAL_AMOUNT, /datum/material/iron = SHEET_MATERIAL_AMOUNT * 0.5) armor_type = /datum/armor/sheet_plasmarglass @@ -238,6 +242,7 @@ GLOBAL_LIST_INIT(titaniumglass_recipes, list( desc = "A glass sheet made out of a titanium-silicate alloy." singular_name = "titanium glass sheet" icon_state = "sheet-titaniumglass" + worn_icon_state = "sheet-tglass" inhand_icon_state = "sheet-titaniumglass" mats_per_unit = list(/datum/material/alloy/titaniumglass=SHEET_MATERIAL_AMOUNT) material_type = /datum/material/alloy/titaniumglass @@ -269,6 +274,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( desc = "A glass sheet made out of a plasma-titanium-silicate alloy." singular_name = "plastitanium glass sheet" icon_state = "sheet-plastitaniumglass" + worn_icon_state = "sheet-ptglass" inhand_icon_state = "sheet-plastitaniumglass" mats_per_unit = list(/datum/material/alloy/plastitaniumglass=SHEET_MATERIAL_AMOUNT) material_type = /datum/material/alloy/plastitaniumglass @@ -300,6 +306,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( w_class = WEIGHT_CLASS_TINY force = 5 throwforce = 10 + worn_icon_state = "purified_soulstone" inhand_icon_state = "shard-glass" lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' @@ -420,6 +427,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( force = 6 throwforce = 11 icon_state = "plasmalarge" + worn_icon_state = "mystic_soulstone" inhand_icon_state = "shard-plasma" custom_materials = list(/datum/material/alloy/plasmaglass=SHEET_MATERIAL_AMOUNT) icon_prefix = "plasma" @@ -432,6 +440,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( desc = "A nasty looking shard of titanium infused glass." throwforce = 12 icon_state = "titaniumlarge" + worn_icon_state = "shard_white" inhand_icon_state = "shard-titanium" custom_materials = list(/datum/material/alloy/titaniumglass=SHEET_MATERIAL_AMOUNT) icon_prefix = "titanium" @@ -445,6 +454,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( force = 7 throwforce = 12 icon_state = "plastitaniumlarge" + worn_icon_state = "mystic_soulstone" inhand_icon_state = "shard-plastitanium" custom_materials = list(/datum/material/alloy/plastitaniumglass=SHEET_MATERIAL_AMOUNT) icon_prefix = "plastitanium" diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index 0510d73bf49d..c5a2945bb16e 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -3,6 +3,7 @@ desc = "Something went wrong." icon_state = "sheet-hide" inhand_icon_state = null + worn_icon_state = null novariants = TRUE merge_type = /obj/item/stack/sheet/animalhide pickup_sound = 'sound/items/handling/materials/skin_pick_up.ogg' @@ -224,6 +225,7 @@ GLOBAL_LIST_INIT(carp_recipes, list ( \ desc = "The by-product of mob grinding." singular_name = "leather piece" icon_state = "sheet-leather" + worn_icon_state = null inhand_icon_state = null merge_type = /obj/item/stack/sheet/leather pickup_sound = 'sound/items/handling/materials/skin_pick_up.ogg' @@ -424,6 +426,7 @@ GLOBAL_LIST_INIT(bear_pelt_recipes, list ( \ desc = "This hide was stripped of its hair, but still needs washing and tanning." singular_name = "hairless hide piece" icon_state = "sheet-hairlesshide" + worn_icon_state = null inhand_icon_state = null merge_type = /obj/item/stack/sheet/hairlesshide pickup_sound = 'sound/items/handling/materials/skin_pick_up.ogg' @@ -439,6 +442,7 @@ GLOBAL_LIST_INIT(bear_pelt_recipes, list ( \ desc = "This hide has been cleaned but still needs to be dried." singular_name = "wet hide piece" icon_state = "sheet-wetleather" + worn_icon_state = null inhand_icon_state = null merge_type = /obj/item/stack/sheet/wethide pickup_sound = 'sound/items/handling/materials/skin_pick_up.ogg' diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm index 6b6f893ed8fa..36b76a5c3d67 100644 --- a/code/game/objects/items/stacks/sheets/mineral.dm +++ b/code/game/objects/items/stacks/sheets/mineral.dm @@ -37,6 +37,7 @@ GLOBAL_LIST_INIT(sandstone_recipes, list ( \ desc = "This appears to be a combination of both sand and stone." singular_name = "sandstone brick" icon_state = "sheet-sandstone" + worn_icon_state = null inhand_icon_state = null throw_speed = 3 throw_range = 5 @@ -103,6 +104,7 @@ GLOBAL_LIST_INIT(sandbag_recipes, list ( \ /obj/item/stack/sheet/mineral/diamond name = "diamond" icon_state = "sheet-diamond" + worn_icon_state = "purified_soulstone" inhand_icon_state = "sheet-diamond" singular_name = "diamond" construction_path_type = "diamond" @@ -133,6 +135,7 @@ GLOBAL_LIST_INIT(diamond_recipes, list ( \ /obj/item/stack/sheet/mineral/uranium name = "uranium" icon_state = "sheet-uranium" + worn_icon_state = "sheet-uranium" inhand_icon_state = "sheet-uranium" singular_name = "uranium sheet" construction_path_type = "uranium" @@ -167,6 +170,7 @@ GLOBAL_LIST_INIT(uranium_recipes, list ( \ /obj/item/stack/sheet/mineral/plasma name = "solid plasma" icon_state = "sheet-plasma" + worn_icon_state = "sheet-plasma" inhand_icon_state = "sheet-plasma" singular_name = "plasma sheet" construction_path_type = "plasma" @@ -206,6 +210,7 @@ GLOBAL_LIST_INIT(plasma_recipes, list ( \ /obj/item/stack/sheet/mineral/gold name = "gold" icon_state = "sheet-gold" + worn_icon_state = "sheet-gold" inhand_icon_state = "sheet-gold" singular_name = "gold bar" construction_path_type = "gold" @@ -236,6 +241,7 @@ GLOBAL_LIST_INIT(gold_recipes, list ( \ /obj/item/stack/sheet/mineral/silver name = "silver" icon_state = "sheet-silver" + worn_icon_state = "sheet-silver" inhand_icon_state = "sheet-silver" singular_name = "silver bar" construction_path_type = "silver" @@ -265,7 +271,9 @@ GLOBAL_LIST_INIT(silver_recipes, list ( \ /obj/item/stack/sheet/mineral/bananium name = "bananium" icon_state = "sheet-bananium" - inhand_icon_state = null + lefthand_file = 'icons/mob/inhands/items/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items/food_righthand.dmi' + inhand_icon_state = "banana_peel" singular_name = "bananium sheet" construction_path_type = "bananium" mats_per_unit = list(/datum/material/bananium=SHEET_MATERIAL_AMOUNT) @@ -291,6 +299,7 @@ GLOBAL_LIST_INIT(bananium_recipes, list ( \ /obj/item/stack/sheet/mineral/titanium name = "titanium" icon_state = "sheet-titanium" + worn_icon_state = "sheet-titanium" inhand_icon_state = "sheet-titanium" singular_name = "titanium sheet" force = 5 @@ -348,6 +357,7 @@ GLOBAL_LIST_INIT(titanium_recipes, list ( \ /obj/item/stack/sheet/mineral/plastitanium name = "plastitanium" icon_state = "sheet-plastitanium" + worn_icon_state = "sheet-plastitanium" inhand_icon_state = "sheet-plastitanium" singular_name = "plastitanium sheet" force = 5 @@ -381,6 +391,7 @@ GLOBAL_LIST_INIT(plastitanium_recipes, list ( \ /obj/item/stack/sheet/mineral/snow name = "snow" icon_state = "sheet-snow" + worn_icon_state = null inhand_icon_state = null mats_per_unit = list(/datum/material/snow = SHEET_MATERIAL_AMOUNT) singular_name = "snow block" @@ -421,6 +432,7 @@ GLOBAL_LIST_INIT(adamantine_recipes, list( /obj/item/stack/sheet/mineral/adamantine name = "adamantine" icon_state = "sheet-adamantine" + worn_icon_state = "sheet-adamantine" inhand_icon_state = "sheet-adamantine" singular_name = "adamantine sheet" mats_per_unit = list(/datum/material/adamantine=SHEET_MATERIAL_AMOUNT) @@ -439,6 +451,7 @@ GLOBAL_LIST_INIT(adamantine_recipes, list( desc = "Rare material found in distant lands." singular_name = "runite bar" icon_state = "sheet-runite" + worn_icon_state = "sheet-runite" inhand_icon_state = "sheet-runite" mats_per_unit = list(/datum/material/runite=SHEET_MATERIAL_AMOUNT) merge_type = /obj/item/stack/sheet/mineral/runite @@ -451,6 +464,7 @@ GLOBAL_LIST_INIT(adamantine_recipes, list( /obj/item/stack/sheet/mineral/mythril name = "mythril" icon_state = "sheet-mythril" + worn_icon_state = "sheet-mythril" inhand_icon_state = "sheet-mythril" singular_name = "mythril sheet" novariants = TRUE @@ -464,6 +478,7 @@ GLOBAL_LIST_INIT(adamantine_recipes, list( name = "alien alloy" icon = 'icons/obj/antags/abductor.dmi' icon_state = "sheet-abductor" + worn_icon_state = "sheet-abductor" inhand_icon_state = "sheet-abductor" singular_name = "alien alloy sheet" construction_path_type = "abductor" @@ -534,7 +549,9 @@ GLOBAL_LIST_INIT(metalhydrogen_recipes, list( /obj/item/stack/sheet/mineral/metal_hydrogen name = "metal hydrogen" icon_state = "sheet-metalhydrogen" - inhand_icon_state = null + worn_icon_state = "sheet-metalhydrogen" + worn_icon_state = "sheet-metalhydrogen" + inhand_icon_state = "sheet-metalhydrogen" singular_name = "metal hydrogen sheet" w_class = WEIGHT_CLASS_NORMAL resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF | BOMB_PROOF @@ -566,6 +583,7 @@ GLOBAL_LIST_INIT(zaukerite_recipes, list( name = "zaukerite" icon_state = "zaukerite" inhand_icon_state = "sheet-zaukerite" + worn_icon_state = "shard_white" singular_name = "zaukerite crystal" w_class = WEIGHT_CLASS_NORMAL gulag_value = 100 diff --git a/code/game/objects/items/stacks/sheets/runed_metal.dm b/code/game/objects/items/stacks/sheets/runed_metal.dm index 994ea7d9a079..856921587679 100644 --- a/code/game/objects/items/stacks/sheets/runed_metal.dm +++ b/code/game/objects/items/stacks/sheets/runed_metal.dm @@ -71,6 +71,7 @@ GLOBAL_LIST_INIT(runed_metal_recipes, list( \ desc = "Sheets of cold metal with shifting inscriptions writ upon them." singular_name = "runed metal sheet" icon_state = "sheet-runed" + worn_icon_state = "sheet-runed" inhand_icon_state = "sheet-runed" icon = 'icons/obj/stack_objects.dmi' mats_per_unit = list(/datum/material/runedmetal = SHEET_MATERIAL_AMOUNT) diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 7556067c86fe..c055e52b88c2 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -163,6 +163,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \ desc = "Sheets made out of iron." singular_name = "iron sheet" icon_state = "sheet-metal" + worn_icon_state = "sheet-metal" inhand_icon_state = "sheet-metal" mats_per_unit = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT) throwforce = 10 @@ -309,6 +310,7 @@ GLOBAL_LIST_INIT(plasteel_recipes, list ( \ singular_name = "plasteel sheet" desc = "This sheet is an alloy of iron and plasma." icon_state = "sheet-plasteel" + worn_icon_state = "sheet-plasteel" inhand_icon_state = "sheet-plasteel" mats_per_unit = list(/datum/material/alloy/plasteel=SHEET_MATERIAL_AMOUNT) material_type = /datum/material/alloy/plasteel @@ -408,6 +410,7 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \ desc = "One can only guess that this is a bunch of wood." singular_name = "wood plank" icon_state = "sheet-wood" + worn_icon_state = "sheet-wood" inhand_icon_state = "sheet-wood" icon = 'icons/obj/stack_objects.dmi' mats_per_unit = list(/datum/material/wood=SHEET_MATERIAL_AMOUNT) @@ -719,6 +722,7 @@ GLOBAL_LIST_INIT(cardboard_recipes, list ( \ desc = "Large sheets of card, like boxes folded flat." singular_name = "cardboard sheet" icon_state = "sheet-card" + worn_icon_state = "sheet-card" inhand_icon_state = "sheet-card" mats_per_unit = list(/datum/material/cardboard = SHEET_MATERIAL_AMOUNT) resistance_flags = FLAMMABLE @@ -790,6 +794,7 @@ GLOBAL_LIST_INIT(bronze_recipes, list ( \ desc = "On closer inspection, what appears to be wholly-unsuitable-for-building brass is actually more structurally stable bronze." singular_name = "bronze sheet" icon_state = "sheet-brass" + worn_icon_state = "sheet-brass" inhand_icon_state = "sheet-brass" icon = 'icons/obj/stack_objects.dmi' mats_per_unit = list(/datum/material/bronze = SHEET_MATERIAL_AMOUNT) @@ -901,6 +906,7 @@ GLOBAL_LIST_INIT(plastic_recipes, list( desc = "Compress dinosaur over millions of years, then refine, split and mold, and voila! You have plastic." singular_name = "plastic sheet" icon_state = "sheet-plastic" + worn_icon_state = "sheet-plastic" inhand_icon_state = "sheet-plastic" mats_per_unit = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT) throwforce = 7 @@ -930,6 +936,7 @@ GLOBAL_LIST_INIT(paperframe_recipes, list( desc = "A thin wooden frame with paper attached." singular_name = "paper frame" icon_state = "sheet-paper" + worn_icon_state = "sheet-paper" inhand_icon_state = "sheet-paper" mats_per_unit = list(/datum/material/paper = SHEET_MATERIAL_AMOUNT) merge_type = /obj/item/stack/sheet/paperframes diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm index e4e076b279bc..5f804a838aac 100644 --- a/code/game/objects/items/stacks/tiles/tile_types.dm +++ b/code/game/objects/items/stacks/tiles/tile_types.dm @@ -10,6 +10,7 @@ lefthand_file = 'icons/mob/inhands/items/tiles_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/tiles_righthand.dmi' icon = 'icons/obj/tiles.dmi' + worn_icon_state = null w_class = WEIGHT_CLASS_NORMAL force = 1 throwforce = 1 diff --git a/code/game/objects/items/tools/control_wand.dm b/code/game/objects/items/tools/control_wand.dm index 92b62d7caa1b..2c76191a7da9 100644 --- a/code/game/objects/items/tools/control_wand.dm +++ b/code/game/objects/items/tools/control_wand.dm @@ -7,6 +7,7 @@ /obj/item/door_remote icon_state = "remote" base_icon_state = "remote" + worn_icon = null inhand_icon_state = "electronic" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index 99d6273481cc..d2e2813521ba 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -5,6 +5,7 @@ desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production." icon = 'icons/obj/devices/flash.dmi' icon_state = "flash" + worn_icon_state = "flash" inhand_icon_state = "flashtool" lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm index 66a38bd1edca..a33556ceb700 100644 --- a/code/modules/hydroponics/hydroitemdefines.dm +++ b/code/modules/hydroponics/hydroitemdefines.dm @@ -99,6 +99,7 @@ icon = 'icons/obj/service/hydroponics/equipment.dmi' icon_state = "hatchet" inhand_icon_state = "hatchet" + worn_icon_state = "hatchet" icon_angle = -135 lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' @@ -151,6 +152,7 @@ icon = 'icons/obj/service/hydroponics/equipment.dmi' icon_state = "scythe0" inhand_icon_state = "scythe0" + worn_icon_state = "scythe0" icon_angle = -45 lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi' @@ -213,7 +215,7 @@ icon = 'icons/obj/service/hydroponics/equipment.dmi' icon_state = "secateurs" inhand_icon_state = null - worn_icon_state = "cutters" + worn_icon_state = "secateurs" icon_angle = -135 lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' @@ -251,7 +253,7 @@ icon = 'icons/obj/service/hydroponics/equipment.dmi' icon_state = "genesheers" inhand_icon_state = null - worn_icon_state = "cutters" + worn_icon_state = "geneshears" lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' obj_flags = CONDUCTS_ELECTRICITY diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm index b237ddc415a3..a3ff4a505ebc 100644 --- a/code/modules/mining/equipment/mining_tools.dm +++ b/code/modules/mining/equipment/mining_tools.dm @@ -114,6 +114,7 @@ desc = "A large tool for digging and moving dirt." icon = 'icons/obj/mining.dmi' icon_state = "shovel" + worn_icon_state = "shovel" inhand_icon_state = "shovel" icon_angle = 135 lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi' @@ -151,6 +152,7 @@ name = "spade" desc = "A small tool for digging and moving dirt." icon_state = "spade" + worn_icon_state = "spade" inhand_icon_state = "spade" icon_angle = -135 lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' diff --git a/code/modules/power/lighting/light_items.dm b/code/modules/power/lighting/light_items.dm index c7cf85b98a50..69ab00ab2197 100644 --- a/code/modules/power/lighting/light_items.dm +++ b/code/modules/power/lighting/light_items.dm @@ -55,6 +55,7 @@ desc = "A replacement light tube." icon_state = "ltube" base_state = "ltube" + worn_icon = "ltube" inhand_icon_state = "ltube" icon_angle = -45 brightness = 8 @@ -78,6 +79,7 @@ icon_state = "lbulb" base_state = "lbulb" icon_angle = -90 + worn_icon = "lbulb" inhand_icon_state = "contvapour" lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm index 31d51d7b89ee..03161e7efa26 100644 --- a/code/modules/projectiles/guns/magic/wand.dm +++ b/code/modules/projectiles/guns/magic/wand.dm @@ -3,6 +3,7 @@ desc = "You shouldn't have this." ammo_type = /obj/item/ammo_casing/magic icon_state = "nothingwand" + worn_icon = null inhand_icon_state = "wand" icon_angle = -45 lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' diff --git a/code/modules/reagents/reagent_containers/cups/bottle.dm b/code/modules/reagents/reagent_containers/cups/bottle.dm index 1ca466bb9e3e..b692f7bef3f9 100644 --- a/code/modules/reagents/reagent_containers/cups/bottle.dm +++ b/code/modules/reagents/reagent_containers/cups/bottle.dm @@ -129,6 +129,7 @@ desc = "A small bottle. Contains the liquid essence of the gods." icon = 'icons/obj/drinks/bottles.dmi' icon_state = "holyflask" + worn_icon_state = "holyflask" inhand_icon_state = "holyflask" list_reagents = list(/datum/reagent/medicine/adminordrazine = 30) diff --git a/code/modules/reagents/reagent_containers/cups/drinkingglass.dm b/code/modules/reagents/reagent_containers/cups/drinkingglass.dm index 103a8cf7df56..88266a7ebb16 100644 --- a/code/modules/reagents/reagent_containers/cups/drinkingglass.dm +++ b/code/modules/reagents/reagent_containers/cups/drinkingglass.dm @@ -2,6 +2,7 @@ name = "drinking glass" desc = "Your standard drinking glass." icon_state = "glass_empty" + worn_icon_state = "bottle" base_icon_state = "glass_empty" amount_per_transfer_from_this = 10 fill_icon_thresholds = list(0) diff --git a/code/modules/reagents/reagent_containers/cups/drinks.dm b/code/modules/reagents/reagent_containers/cups/drinks.dm index 34190f66aab8..3216a4c7a876 100644 --- a/code/modules/reagents/reagent_containers/cups/drinks.dm +++ b/code/modules/reagents/reagent_containers/cups/drinks.dm @@ -504,6 +504,7 @@ desc = "Every good spaceman knows it's a good idea to bring along a couple of pints of whiskey wherever they go." custom_price = PAYCHECK_COMMAND * 2 icon = 'icons/obj/drinks/bottles.dmi' + worn_icon_state = "flask" icon_state = "flask" custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*2.5) volume = 60 @@ -512,12 +513,14 @@ /obj/item/reagent_containers/cup/glass/flask/gold name = "captain's flask" desc = "A gold flask belonging to the captain." + worn_icon_state = "flask_gold" icon_state = "flask_gold" custom_materials = list(/datum/material/gold=SMALL_MATERIAL_AMOUNT*5) /obj/item/reagent_containers/cup/glass/flask/det name = "detective's flask" desc = "The detective's only true friend." + worn_icon_state = "detflask" icon_state = "detflask" list_reagents = list(/datum/reagent/consumable/ethanol/whiskey = 30) diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm index 2e0d36837449..cbe96febdb6b 100644 --- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm +++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm @@ -383,6 +383,7 @@ desc = "A flask of the chaplain's holy water." icon = 'icons/obj/drinks/bottles.dmi' icon_state = "holyflask" + worn_icon_state = "holyflask" inhand_icon_state = "holyflask" broken_inhand_icon_state = "broken_holyflask" list_reagents = list(/datum/reagent/water/holywater = 100) diff --git a/code/modules/surgery/surgery_tools.dm b/code/modules/surgery/surgery_tools.dm index 0db69a886e47..cca6db3273a4 100644 --- a/code/modules/surgery/surgery_tools.dm +++ b/code/modules/surgery/surgery_tools.dm @@ -3,6 +3,7 @@ desc = "Retracts stuff." icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "retractor" + worn_icon = "retractor" inhand_icon_state = "retractor" icon_angle = 45 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' @@ -33,6 +34,7 @@ desc = "You think you have seen this before." icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "hemostat" + worn_icon = "hemostat" inhand_icon_state = "hemostat" icon_angle = 135 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' @@ -65,6 +67,7 @@ desc = "This stops bleeding." icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "cautery" + worn_icon = "cautery" inhand_icon_state = "cautery" icon_angle = 135 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' @@ -207,6 +210,7 @@ desc = "Cut, cut, and once more cut." icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "scalpel" + worn_icon = "scalpel" inhand_icon_state = "scalpel" icon_angle = 180 lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' @@ -325,6 +329,7 @@ desc = "Nanotrasen brand surgical drapes provide optimal safety and infection control." icon = 'icons/obj/medical/surgery_tools.dmi' icon_state = "surgical_drapes" + worn_icon = "drapes" lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' inhand_icon_state = "drapes" diff --git a/code/modules/wiremod/core/component.dm b/code/modules/wiremod/core/component.dm index 7dda865fb640..79c97b46a368 100644 --- a/code/modules/wiremod/core/component.dm +++ b/code/modules/wiremod/core/component.dm @@ -11,6 +11,7 @@ name = COMPONENT_DEFAULT_NAME icon = 'icons/obj/devices/circuitry_n_data.dmi' icon_state = "component" + worn_icon = null inhand_icon_state = "electronic" lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' diff --git a/icons/mob/clothing/belt.dmi b/icons/mob/clothing/belt.dmi index ef94644898e5364f7a05c050b7c57307c5eb14c2..2c3dd9972637a324109659c617859af07ef31231 100644 GIT binary patch literal 58797 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 z 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 P_8B}~{an^LB{Ts5kV0@S literal 53145 zcmdqJc|4Tw-#2{PLJCDmWlLm9NGRKo${N{23`xqG?CVI2QlYYD8QG0}8@o}F%9@OA zFlFDy7=tlpIiFGA-*w;DbuZ8JdOi1j-OuZpf6m2m9{cC>et(w3iyJ0-2lpS}4*^pmoOd zP_2#6kJDa0VPjrZi_R}|uhIu>0HyfCA zR2!AJh3Q>@p&apE#@wHK;i@82t*AD&2Fak!Ir`czqNW_T@iQe+%zk%gzIV%N=5|iJ zNO=YFP^fxDTWjRLI~bmo{^~zxCU-Jq3C|D+Kb~`(=TKvk?+l0gXj{?T;v0SL}iq>5J{@mC_ZIONB!l4G; zCgBrfWj~KeK6Wd~lfCd{Un}2NWc=fYh2^N zNh}E78g+P(^`YYpE2{xz#B`1??w)IrOfo13B!dXTK0ZiSqkZq1wl z{aLZpJjGe7Eu`#~UxK^7Z@%l4xI7%-d03oeCu=THcvQ;zQ3zT_q2dU z+>MP+X2%}_T*0o;v%ii+Rz=yozpS6V^kf>Hext>NFV^PHWkO+n>+7$~c% zBsbi2p5mIgF*h&!f{Q0{<4vL0V0P+%=XfiZ&#l*H zB?dc~Ez76RY?!8-${&4}qzP%QUYPf`T~#>tyrUEs7C1ubrFn3XWg0!rpOOt(I=AGOx4zy`}Rx*hUQj?blTjU%=@b%Ea7t zb=P7{P*D0Z?3>VRK@4}OfgxnTsndV;pirp~t5=*(mrdcFqTKsA7%tYk15@RBQ!?ia z-nXN#e;X>1upVQ-czPnVevajB&t(?HpVGMd8l8s2$4Ytk8^~NpPJDW~ek!(8g*WJO z6=(HQsT=ytjfzKbMY8}7TjuauCW(uJduNgUG zpy#8$aTB#rEfEwg1xuC>V^trd~vKP5cfE6Tx_Fsu?yP*(sue> zLKRx-_9Yw~r1~#0yNNjQW*6~)$bN9|J6EamLY{=A@cris$C=bqA078Q+GCxsz@`4k zSM8NXP^R|&lOlAb%A|q$0*>1Qi7%!W4+leruRLZu87)=We&o5;hoc7`y!K${UHtKy z1-@3Gs&Txf@?4RFwuO{B;bdW#lb%7DQE+(5ogfiIe|xtR641eTi!ZHCIvzqvYOa;6 zIS*Ig%zJ*w$sfJI9A8K(c$Ab@R&TjklUMaQK|J-9S%c`z=$jbMIQKcjVJZX=E-OnA#|Pv#n3;y)!8{qR+VweayVOJ->)&z|RpuPvGPS}dCs zAHJvdz}eaxH$3bg))ZG~g+BVI+PLFLw^f5fcH8AN-{sAt(2{<+rS-eg-4E@mU!&;j zIu?>iViqsL>e=OW^*TzA3$6?$-pd=krz^fr(^n3A7!msU_H}K7YPBe?sQZpt4Oog= zDi6DHlK1g$MMJB0BR5Uz2{-SDk9r12@gGAt<9d?RIB!sHcm9fM%;mB_d#gF@$cYOa zCI;Wac@4jay_PxjBIn1yH*=Y3gS|$9N{P!DXP%$ZVL*{|HG_XXFzeC^l zHuv#Bqh)cIWF5b}pnQ`|n;4>fIQ_Q`JKyztcWTn4A9cr7>WxUH-FRFK&+2W>W zlS1klN`2x>Onv_DTl){m1KQzFTdUmiWX=Z0p4<c~^`c`vF*`DUQQ>({_qHgL#{mtRM_XPPONiI+`>|H{=X#zj)0 zF-sFk>RXmQOgdK{_8DDU$t?UZe0TKeK?}`E3lyG0z-8HbBbwX;gR)>Yq`Pi zlIO1!UEn_^n;g@ek@MuRF#o~3V4vBHl4q@)*H@1-$js4t%Q&M1ulI`e%2VX~^j(M0 zZ8GUjYFS9+CnNl9FRaU`4><^rZ=)K(ANG6%+X9UCB8J{sA6)^1=4?;r&sP2N831nO`-qzqfx6Yw=!h zLDidtM}+i2ghzs!KP|IY>vd_A&|I=EPLa11Z4DuTC62-091y>aNK#{M=gG@GbITngDGSt@LmgB~mxgZ(amD34 z3XHcMu#Fg7G?2hpRl!Q$kHO6S+@}P;l3+Dfr1`y0!N!OOOJ9RrxT8z|&G*%3mjg1h z+FIahW!s_>_DbN&`8BJB;qf%43&#klbwD|)&2kP)-1Zlj z4sjm)Owdtv^T+x5g5D17*4}iSUX0v)LP2lAr^@e8FhOJPsLw9TxnM*V$!A~frEx=y ze9uIVhh=Y%hx(fk`f8{;V=(GQhAV#uLl~){_b*eyl}Yd(n0h8IC^Eu${$x)mK?glO zn4akAG3=FNEtgq1-$#?cV>LAC(z0INl(xIQ??b%KobLy7VGxfQ7^z)Hjj%O$2@K}n zT7bP_j=Ep9uti&*pUi5u)yzf~((S7jYEM#@gDDT3qe{;#Un@wrnLyn2ktw{>Tx+ar6-2pQ z=a0J^Fzvk60&^+ZSsnXr?oe;{^v^c&$~vUvCdLOR#&>;(maX?qrN*=%zirXwGe?%Y!SI4}?$|FBRb;H`rJ0BuM9Dw%(k;Ve@8V-Ec3Nve zBwlnL3t_B#CRF4dnFgcoat*mzIa*X_jx9nEbJ3uMQa411vEjPqZ2m|Aj5h1XSfgC8 z!vD^O3m?oc%&OYPBdqG`6FqyW@*@=`C~J8fE&bh>*R_Lz;LGx#bxM8xdyB1ggoqc3 z&z{G=3YiYZyoX7jEkWDJ_yr0{pkg}v8}o;B(okWP4T@Af`FveqRkit(g^@Ps4IE?= zqAreF$e(n#Pq&kedR3z4P&SfGxtgZJ7r%vgr0C^wV3xJLLuns^TV&Iw+98QpjYtcq z+I8POvuwIRAKROZekO~yK{M-A@8PkdGVOB_(0oJm-zD1j;PBl(~A1$a8-Xe z{c?Ce5Q3wQZiQl^F9FxM;BGkaDP_v0oYz*L1)5RXT zPYeC!%d;g3_ovHnnzrHY);-Tk$3yX=r^An>X(q1_06^Pr%%=U0denXo3KDZL3<~g@ z4l)BalY-4ZxPYwhk;x*VdteteAY%=z-&hr{SQL6sdrrJ6@B9Z9Mfb-Z0XAbDO6J~^ z1gg*Q0so-rsqC9f?Gja7TSj9Q=Ib)eL6huz)ri_MivRgV-4pwJ^S=y4|5W$?9L}E% zcTmDsxa)cPrB;dG0)665nhYMhf5#@*YWn=J3s@*33}aLy61;BUo)$w|y%%%!G&UYw zbB3ED_r$5hvKT@2GQg~iR9KkC!(r+*^DLCHSxkGV9x;@HWyubR!zljLjE(6#eDNpW3v?$84%R znvW%af+7!J;JDvC0aRo=ecBFAjVy{X07NmOx=|5Fn!hzjM*3(i4O&2;Rkd%kb@c7$ zpM|rr@u|GNkDc^r#^n9@%Z>)Sr`9&*q3OG0o@j<6!g{|iahL5#OPGD8lab80@yU0h z@~sKX)I48trTx3(i%(x#k)CZ?>3{`!UfnBrn|k-Qk^`qX7Ok7!CLy^PVqT!N!?)+OHWD6v~~6EPh0^b zHqh&1>}yO5otsgr)w&Lgk8q%#LT^I=|Z|u3~<+YCq8?HFvf$CXx zb)QOsg!ZFLZS2bIs&ymkknc%5$gaoGI2_NFIK1+hI|cswUJ0i|)aH#@Kzl8+!F@?X zIcQCOYNlh3H;CtC9{2+VPZBvWe~ssId7}cgf2@UDcU%=Gf%Fl^ctaSfxN+D^-pt|* z>P~9}cq~fD#2uZMtNhLyFeI|{vXW53tJ zziP-wRH4}~(J6Srx-K}=o3hR8U+&C)eq7oI>(`>y!g~1Pl~G!fe%$UOTH10@ddb_e zga}AFQQrP%j|)%nV`K-qdg@aG6qYQ>}d+bA#BQT6(eja%7;sB z-;r8E&SK8M!{73BCTj#O35JrlA;Of;K-FE052C+BIQvvO1QF%*uphiC!4b zES87SY0OZ_^C%~%*J??;gxJ$HSD~Ybje~vWP>ro;YTL`i4P+y}t>T$xqe&*Qo!_Gi zY_H4Q^!Q27L!J$_sat7*htj`cTYi1ORlRJjfGVE-YzK&TWkDaw_fuHj5WtCha;yo{ zsGnGw5DI#n+iGSlI5?iSot5@cSS!9#Uxz+zK2+ z+c`oArY5Ni`OsD+9Q5(2Zj)yV-j)Tu0*<4!n-Zem@#H%VySuVm`1I})^CQTj>m`Aa zJ0&-Xs&&p|*YK@Uo=;5$>b**2M~d@|DKC16gDELzFl4r~UEok||LUQJ2nx=|a97gh zw#x(`)`zZWdJ<3FP3^fkV>@!_(@z3$iS@oDZKquCHFa^kY-?*=l|-*Rz5nQ_mU%)tZ9Zw+0vU^MA;b&pFqq)B9)+n~KVjM& z>EMm{r8z~=mG+S>0rrt5f8j9PKwLSJxbMOIE7-J4I#bjW5vUI=uzm!B=s7Yj^8D2@ z@fjM(?)nUSa_;bx*%1-Ws~kYoy6g`LU|zS1-0nvF4CfcFTSnW+CZFxA|2j4@^0O`i zgL_scZyq&kGkdyC;9YM^J(oLH!-p6qy6AGwouYG6ohUM)7;~Ralx^oxTNK?Q_Cwgs zql_pUO;0DskFiLX)4JS*DP7{v0a4$c>mTL2)?gW)^9P#8b{X5=H!Q%Fc%Vf9NVJ`) zt1&AxtLsksP8m0JkX)aJ#`=OTm`)f1qioFU^!0Ff`cv4kR2P%8YjYtm(Ze140l%@% z26oj244k^lDH?t%fCrFPAX(pBTUl8eZ^H$Co)Q3-@V~I|0nF{z0Ul5 zOa6IcGi9;>R9t-YDq6VEkMd3!c7q~PkJ>J1k5fNOTf(AH!WSPnUDJvG*+@Gier;7{ zsx@@WaEV%&1L;9wh&+H)V;(DD6VNnHkyzi{DKy{xjM9r24jn2o6Eus6SjA-9hX&*8 zB6aDhG1M%hk!_0~j;Xuio!dGGNj4DqTWO?>kds#qWqseyAKk8}jJ&f)z#=p(*aRa_ z$30zo3_I)j3^4aZ7n$jnIku|dFA*<3KXo<oA{`n|cp5sN=2jZ7D}zxbf-4oM(D;5$(yguebf0|>C)i#yTDbTt zilww40*GERJPGI%eD=e@$bsWM&zEYh31>g_^Fz;md-rZC2I+-a?&&e!^6rPCbF9ax z(cNxT`QY6Lu1cbBE)yF*uN3v8?2&p*Sy&=+cc9kVcB?zqE2KZIONYPRK`mf276%bR z!koO0-qoPfXnmjjCw4fkDdJZPJ@W@H7@M861S%g+UJDp>zP^z~nWcodgNUTkdY|#g zJF{O85JyF{SgQ_ytbH?z497jo(yYVzCgfNk&F>v6IL0LdBy4l5EoMWeU=VP1t1FOF zfP2$Cj}^J5kI~6^q2hHKDF!tNDOrv6J-nY7#W5Vc=AH2=Ep?qPqib1px&O?z7hKEHbGiNR76CTj+CeTE9;#thfbld&}{ft{?S8#5{OJeI;6Z9`>!dLYni` z>F1oGbSB{2n@vFU2=(e>SA9k8!)K1;7s+(mkrE&<=rSm4x z;*i!(mqTHDBtdGvgB` z7|E#ap7C|~Xv1mKC-_Tuo4D~W#&*p?!z}||y zF+jz%91ue(Zeim^t%z_ahc-h){kF*gw2_6wMY&Dak7rh|(C3=`{PykEm9*8^J=r(u zXqKu(P+5aj)^A6*ozBU=t^W0t##vb)T5HV`=~WIxO=nn7rFr`iFM-=*_mxT=U*aK{ ztb0Cg9q$6DWH#Vo%L^B0r6>BHvZ#U1Yk*TZ3_{!d*{U=-KkrA!ZSp`Y(Q=ND*Z$o% z=1(Nzp7!#ds*(dWIk$&L-oCwEZ$mq6{pZGJ_G9U3EL=SfF2lzDdaw2}-<^3H4 zmeyoMq}1Tpyu30wO;>dzWdYBur~MU0DkC$6=Yg;IHkNPP0M$)sZnY%COYWY>it)YM zA{*)~p2V}Bkyn!%g&|37PLU)b{XnKg$WMn85p_JEO2wUpSdq!10sW;lK zDPa^@b{*^sEJT>h#*L(((v5^g%A4J|`T#L2ppZ%dM%$QfJ7a2(M9-2IMh>%Cu@)Le zGCgkO(T*IM#;oe;c#ZGZNd>80!xQZ7^9MQjaAnsqVyO-FdS@o{bn05Vh)2t;g5vgL zuctkp7U4;EIb^9$slGQRIXRuZFC^ZL)}pUmWbtE+Op1@`t>QprM!Z33+3fkLx)>%s zS7+~{MJGh~s?bY)HekLqEY-X1OpgRp@y*wwMjMY0=-%NrKhm!lQzEhDF-ynJ)oUec{|z7-JouxUhqeH?gmUUn0yb_ z5_^%8xmkik_Gw)KC06U(UPto_Xsw)0Qps8paWTC>`)ax(&qi=Qkc3NthnG+FMe0ok zVuZeHXwB!%v_zy|Exr%)i9Q#bZ~9`!SqV^}-0gX+;VA9z+8ls6syzGQ$8tHw+j8Ew zv3jmGB3&nHuQd0mJVxm^NG5>y=w0+9jvOh|Ip}c?U8t9woGDV2BKS;^6TxF6frIP|4i>g5k%Ra4Bz89m!-Tkk&p=Kwx1i;HtCp zu2>4wsMcM--nzzb>AmWiDYdZb1C!fW7gD{==V`d+S!2lTO)n88A{-X>P^j%}r|BHE z+ZC9v5N93Z0mts8yaYQ4m6}I&>T2ZaH9yV?fQ+$)osD*PgHqjQ9L~}Cx^jzL81W;~ zAu3yi?X*bp9GxDSO5q>n1`cBkI&Yqy;{7q-DnMl${+iH>0;^g4GMjL>rK&P5Z|x=o z>t0D8nNkA7;NaiK4*xb1Gmt0F@VI;sNLryhUM?h;a1;x_%icZo_pwnn647`BxBV|+gceZF6@FNy@@q}X>Es2W*gj{ znz>4Ri1u|5xo!ac)D96=={i*uxb138S=%j&rOav4Lx1`p7~Y@0K1RsIGw$jQ+^W$? zIvnkKLZvsX#p}1c`|5yfMkWjl9zFRodweX#k#FkfT)gH5JRw;=@`kt?eeAyy%QhZ( ztD}wguHMfRkw4s5kF3$@|0c3zpNbWKT2s=7u)JfFS*F^wqT2#Pq5SR8+@!4IaAAr^ zB5CJy8P||)zvJpEBX0D0{w6EBukFcu@@_iIm@r^L}7Yzc6l* zUD1qZhQa&aKC@OcYo}TP4Dac||2vJ5`$uDZ-u|=dJN?(1Z&A<0#o+#^{5GBlxBuDS z3Gml}jXS0%c`$$AV;XV-|BwK`$bbc|KPEE6E&jhhHoPbM|A(}}^9+o4<5h^l9sPac zzvojSKgu)P=E)0bFul%lYD{0`y!Sj7O~$Jmxbb-OQw1!>S%IpX#@#BB8ZCmyd@i1r zl{H$JoxPK1Z!dL{Y}?TC6NM=!BN@f|iH#umEo#|D`u`gjRg(jhc z!(~%+X+go|sA^ZMe8LQ)v5qgjgYLUme83tTVT*&uhsj$r2 z@Xrobwg`F&%DaK8aW7v4W^rIp476?ej041r&R)Gn5&jAW6u4DbZp0OifRMPb{9sVN+jn{ zo-Yeefp*POKz}l|h8ZXxp8oYYfcEa(1o_ujZbPfrFk#$aG&zV>7BD=wAq#9w9bg4a z;#FmVLD(u${A%ME&nbWdA;(W|b=xMl`FBS#wQd#{p3EZcphO}BVBW0wM^Rri&@*x) zpeh+x&tOd37*k&e5M&JYr|cHx##=2MB;Ox7;gmBS39KcofL0}nFb&1rbjIUVi`g)M zA%M;@bm9~*zne*X9ZritzwOD`s!>ui6@aK*D)W%Fm^)=lFl=xCA{og9UX{$E5naNJ zC+%hvXrw%icGw zR(Geu*v%5oPd%_Wd-P}ZYgqDc+SiuHA?!Ce-tYkXctqB4&?#h@`)H*YQA_CHIlX@nX=M6V7y9+KS$u4L*);K@@XHTSG zxc`FnCUDfL{p4258S70vgn(a<9D})C}Ep zS`)g)(XlrOJe$q9$u$uMrMe=_1k4;{a7w3GtR??+v8TFigeb%O58=;yr3wHK*kys6 z|D@k+NAIl|-^{(ZDDO5eWG3u8n{kqhG#|44mIX0Hs|3sZ>>E-QO%55B$R zCk3R!6hL76(cafrjjO{q_G-C|E4TL^tahjme2uUxYa&f(>I(NH@PO6$+e`@;CC>{ZF*TFd zoa2+oZuB%*(7ebuJs{yULrr&eo&TJKbWopCsBKlXwY60{bLPyyUL4GOOHHe24!afP z`|dHfH8GNI&`OQm?%rtWcB4-P<27khM*Xr%lJT!12ABJvooEUiMq-@kX(01FOedUv zA~G@%x;5BPw!Aaul?jItHJ!iY{jPNx45Ysam)H+Tby1mBHF_2Ir}8iqwB;oBp0z&2Qb8_q}4bG&LK*wt=<=>xLBZ9>1! zSX)2jMZAOMgrXE-KX5q;A}MP1)Oo-w{qn^lSpRZlt2|77S*o?lt}c=PnNGdj(+Bx= z9%u-PuD0$HP=||%gm&c5xM&XSolUzll2j!>V|!qGa_1LXpwO7q!aa!ylyuN&iZNX+0lOeO%!|Huc&k)X*E`38#B*6`y9jz4{6$n;HUy4sbk zHUAS#+WWtpMl)$sl3>HCjH&8K!mw z65-aLHB6CU3(|pdHSNed8Ao#gtc;!8;Kgv0H!c1fu@&Ke(&D@O?RxPjSwEpIh+-L@q(^#^fk ze{vBx5_I5gHM3}vn-d2^AoO7m4=!(8w_2tNit`8Maz`2)+wm7iYhiIE8W3SxyAQ5QDi+e75UK4!Y`l2njue7`oB!g z@&k>{X@$SmTJoBjNcqaF+S14Pep-`f){MW6!h-vhcgy8!(3*4UB|i%_KT_E>Bn!$; zE|=G~!}k&bwHFTodlhKX&7YW43%!kxftxyeqWb?2y2yLURd!?uqf+|a>GYm9`-4km zc6H{TpB<>L<|@_$6%1a7RCr2#F_^+=_Q??|~5UqtuGU4G}Ak2~ZJ? zb79ja@Z%3h-K4VE1e!?2!Wv@!sN;F~n+F)G>&m7MyRsDoq`q{&RVagJ@0zfniB7hf zT~`kMC=)R99-an^(FYW!f9P!plbY!Jn1Pr>06z}{R~ab69mEubL5Q0UDWk|DM0_f& zRo&r?7HOo$WG3R5)Fo+!f&k;Wj(zLGQp|s|O5Qx^Oo?G|%YI>^&{O#5>T32yZ;P5E zvMev3{n?}duQX#`h5r@?{u8rc|Fr(^A(ip!f6C0X-#0eSTn$}(Vsk!cd;CuY&8Gx` z7Rc|T5qG=9_;WZzxykx)+ch?H!HBr^m%HDepQtSxITsrMcgQ`Xp<%j}vB{0!Lsmd4 zPk$d!--5i()0lm>6CTeG9(gZPI2dxvv2iOr;>WKMB@RYP83Q=eX*>M`uSCbWa)d^9 zzf+qhZRWAKw)3DjDTy4^T+x|yjwsRyXeWQ@PR?I!j{jC%cD#sG?^tkc>Y<@s+)LCB z%xB|aUGT^G8|**<${8mTD^G~=lH>bQkE#O++;#3~ z!-FSO&Q`wg9$dMlx$p*tNTDn1)g@?2HK7Fz_ z<*S|Qx24^y7aXzD!A2HjEoQ3f!5x@Dg-C|@+$&`F}zKI5*qh(=^rG33S&f3P5 zZW2MJQ--yG3!$@xnvO6=STJO%@>SPNJj6R-t)g|?&g;u9|K%ZRao<-*CmvKL@c_>0 zpeaZyR{Pz-_lMarxXZykf=9>ogHHt<7llgrKjnB&Hn}ei=1;Zp&rhRmpmk~>yLB0& z?*+d8B|Gqm75F)gX2M{EH?|8sZ;lN^OaB+J-;5pf(j2DP{^{977)z9h6twdRy zbBr;1W-{$@zRDhyG;o@AfB?EM80I{-l`KG|^4z7{*L&lHJQ$R}h1ZF^F^34mjZX!l z5mr;EG_?Rk-AoE&)h_A-33Uw9LlQ{PKDBrRAOMm+bk_s1q0~3FOro72b;$ffcpj15SzSZ) z15bXlXm?e-pBKu7&{Ss*Z3+U;eav5}#us-5Tvu5BlJEbr?GQG=6EPrRCPTqFdgK|n ziV}RRZ3D0h0{Xs{CB{?R?k5WT&eoTmWU#oen56Y=D1B34V%d#KA}rfJilR z{;IOCM|dDT&8%RQ>q4Q6AFN|xN}%#I&H!+V1@A})0tyWKGycAi#ic5yN5&iCq}M;p znzo^rqaN{0WYyB2HSVZ-J^|*9gQitpb)o7b&k&VeA+N5Ys89Ee?c#rk4id~?TT+Ea zv&SEPfPYgnnTNcm84vrO2Y;-(RmZ&}$KQp#3&`k!U7)0u&b->C3aA5LzYAwKU+=ZX z#f(9MEvS+;*+{vx5@0HP!-_Vx3cu_?Jt4_@v5ow3d7@7sxu~MxIn@s{{{1W%Zw@%^ z%Ee+9I%c*io+RWzpXC2CnVVzZ)6Uy+hh>3h6I!#@Z_p$;l=A3Z)&ZoSIe&JTL1mRpdxmjsGuP{d@NCcAuu1UFM# z+K{8_eMbXjEkv=Mjfxsm0wV|=8z#@lt8ta>L}hHL=G1m%S?yJ>9sEQKNKjwyRzWaD zdA{_+M@tES*9>%{7?+&WC5%`nZJ~Fn=q{|)H-3bZGRIp?uX(aJ)Ifn(>V#km)?dQkQ*3?$bqV8Gzt8=Ylyg}@xSnaJ!Jjc zH@I*_U4*0-;cSbB_gKzTQXTuV167f)`n4#fk#k8l`){%FB7p2L(wds8s?FpDSex4U z-bf(Th4L2&Bhj!0VoH4JhyMVB;n5aLa>mdHb8l|zo#r(`SJ`5`aI&N0CDwGBVNydf*paF3Wsz&{MdmDK*Rbct~&mlFHbo6 z0zGz8mHLPDF5Gj<2NIF49^)yqy{CQ6{bOyQ5$N$dU#o;})*;l+w1Kz!(aXJ$4CUhR z3qwU83OeikM#?OpZD3m+@C$SG<#sSFPDp)T!u$L_m3Ro62=j(JPgUJbESc3DwpU$4 z%1?ziRMgEtHV1SAaN~8;_(oTRNv7Iz2cPL?RtgjdSQ$3!PN}#7p{_r!g94COOKn{% z^=&x99)wngQ15d0q`oZXxt0Fr;0&j@G(oK*%N zC2)N{EnbrC&grnUFpyn)9-B$-$FqnM@lzy}6Md3OXHbD4$^zs5aT?eG{96BPUxaT< zchv-VUyuM^y_3FA)ckh+mA6vG(HV`z)+o_2oLiR%DvaS?U{JF>_u-=I5r)P10*XyJ zTmkUTpRLSzt-|*vP8OgtMKRKqT-fPV%%aZ$?YN(iDLO2LalC+$)`j5vrNAGBv6lO4 z0eh*qk08aGJM4EgwH;@WGpiu>SQ0Wkm4FyipY5o1+ zuX&V$=OpVu3(|9L%N51YV+cDmVmaFfW#wT4xgJ68t(O_|f*h`(6|C7YB{Yny3BHYF zBw{k>1Gl`$UxJHIiM8`UsM)iUQC;4Xi1M-Ot5!6es_pJ=+-`Vd)wS2bVdl3m!F&qf z(=#ttzK`rH;xoA@hHo^5&@6GVkAQNcP0u*hP59@yQIl8Qn;yd0#>#;t%ADmNjm2PD zdmQr5#gt3(j>4n%_jMjtTkhyYR%R4PZ+?H#_x50%x~vxC1f2LGAhlV0BS;%pE0EM2 zbsp@2rS5_C9eVCq{~e(-M3LS|$(4&~;Js}n+pcd^y_$c+`J;Ty`i~sj!b9+l490nP zG?=Ep=ci=+Sm)jdK;9XKcO6U-Mu{6#*n~nW&>wW!9jb13<{br}l_HRJ8|i<`{(s3$ zdqoCV6nMF$Gd3o;*?;{XPPG|NE`$L9Ka?R|mbAC;0)1dBa#qFW`2+Omh<~svf`j9> z_h08M-NM)N&zH@YKp<*XhH?JUl6E-tylX7oYv)0L83&B%n>=W_E&KJ7^WQb%***4W zt%8E~a`52vA9#yYjoRA1vx`8$6%2l#d~oh%=k{-*(1 z^vW}>P{#2?zRQ5NqU}MzlfI#P7{-ZlAE91WD!r{@82Pa0%?T)WUT_|)3Ty(ctz7HY|LpC(nyR@U~1FsTccFaVMg(R(=JTD zb_HVsfvl1b)NDa~wrTxmFli+C?>ZtKC$jqES`0>Q@db~u@wbzk!dQDA{DqdAUILeZ zudR&STsQRb<#v{pl14v)l8%+iXAlib7-6p7{o7lKwxz+W1QRvoku}l1=%Uf%H zMDGfD6mRdXiDvJ*@8R=;B|e{WdcaHqIX<62g{ydHyr0T{i6(=}vPMt~r03}c@P!v< zi0RU$fA${Z0=;ei%bdCZIX6W;{ks&F`Wl6Nw-&lyv0O7}7|}QnpqB^)6BN%KjkscZ z37?DD)5uKmL(Pq*j_$jdiijm$uukCyjvzS6XwocgDY`$)f3UB%4mT z4lZ%jWz>wlP{8XW$}Kc>oplK(41ObT`{haCD^D=_BdQ(>lvr2Tz{59QY%#C@3nyqS zQTi?W2ExSuBT(>gAep1ynW!0{OU+y>M1VW#J6m`D*q>lLBe(8%jQebro^3kFK74nH zB!BM2H!_4c=e3M<|JR@;?#A8<@F_r?4~BGJ2ZH3OSDTvo&Cxsvve4`|F=&EAN4! zzr-L0PaL&R1Dl^|6+l83(E5F#fg7}Ft&yz8Py?p>D@I=nfTpU!9bvtdGzq{~M&u6D zIb0ce#Tl`SY73f=bero1C2e}$by3*XG^$G0}8R@41(rF+ubsZ)VVg7Vr`bnBRJ3UQNm*ck-`w!I1&j9 zv*9}-q(=JamkDJ$ycv9;IW%>RVu?*;1I#DV%fOHtpOPJc z7i2Q;44|JoZ-=5le7Qa&T z^Qh+W-5haSrYLFZTmyX9A2lFAe{q_lVfP7kNc@aEQMlPZjhXkzA6Vx&0uwvQ$T`?- zY!HhAgRfe~HH!W`aSJcRqXY12{VkFtl}RI!Y>`i;1M{_2I;-?kK0#13iC)uH1sGpu zv&(;4A9y~^Nc+Tcg&{HgU8g3wZl}Z@;ZXl$+da9)-%n@Jw*1evQGtK=9~+`-2|sGZ zc2n>+McffFcNLCb6^VcG*?agpPf5PXrG=wkcz~!2R{+3?N4)&?{ju#(jr3Dd zzQUBFovPoa!tKIAX2c$n-uRd9Eh+o`LLC7FCQChe=%RI zqKQTgF7%_jiw3pg&~g%!#M!`~uQNVE+qQzx3uqq0O&S*A|LnY&HtMFah!CvJO9kS> zB)ZJOh_HW?3e55DO@6pr67N>5)OYW#^B3Jje`D%Zr>@^3G02^u2s)1k(pVlIj`vtbUMZ^Wge`oP&SO`;{SbecQ<< z)v2ojbw3MLO7grliW@^Icp|cu>9#v^w0?!4JaXK!FO`A;DL5Kl65A%bkyh zma|@3t-UiXptiQ#E=?*^w;s|uH?RI*+`R`>R9n|AxQif31r?r z?j8)uA2jdy9Gf4fAf#0z0IN#a8I!p7kC!erSDm-2}5T)9@Hg-G=~ojIX1^T#sZeT_GIEA{jZj@a z)g6H7Qs(h*#Y}-dt^K$2_YPfWPRq*iz3 zZXE8eo+wRz+{-a2*=z|K#i#dFGmDI4D#dAT+;s*o%qi6i!)xv5c&+Z|ijT#OzpVE; z)czx(vo2uk)SaC%b9O99j7r;_mrwiU}C zNeCr=z9S``=Io<(B0)5BgF;@x#o{TxXxdtnX6#3Vl zp~Ai7!U$(yTMh-k)oSMpWT*aV1(y>IoJujOm+=9M`xZ!MPI-hFq3ZTQB}8U$RNZTY z&9vRRt?Avp<$Rvn%w=A(ATV6gdE-$_NQC_nB`G|8#lxoIys? zsiCDAa}LWSQ^he!Nv!zRZgv0g8ex(W@$7rgpkX4X^O=h$$k3~LH@QYy>tE`v&Yf+K z*DEmDMd1M#HI2gsZbW^Vl@=8r)e>%nDdn3)p(hT=IF4f89^qL)^;(t-%jHITO=3kBnG#Vo?znb_zpyZpF0IOUteWFQy0(@~`Q?OWEoIm_^@QOM{_w%qF@Y zg`L-tGZW^5p#8VzOOVYd4!xUjWOZS^XHj`V&*3=iJEt#EdZ3LF)QAjUV$~qpoXuvv z)|T|GG=-|rYIBJF>%+&VnP5yQ1#^HwBtw61=P<_NJQM7xL`Q3M(J}oMEZTppjvM-B zowN%h9waaS?wbN{UUu2Me8+`#Kz>Nmp1@^_n%Hbk)zy}`@7V&HM4WFnP*ozcZ?wFf zLQJxaVr~|DxcgXiL=<1(zc#p)x_v)40)2e977S=@Qj)=~OVgtLsmXE?wE%d^)f$@Iusmi#( z^wiZ>+)>OPjo#$BWu&iB4JDlNJQD2cLN6VoyLJ%4_&-YX>aW)R+p_&|mrq{CNfPV* zsl2(?F-}f0hXKgsg^s0${gtv)qb1d7ow8TLE$PLl{!O%Q_AjrYFc-U>iq@;TL67A7lWq_htd=Fe z=n}2Jl@UV$ZBTFS9up8AhuGCHZgCoa%7`{~b<>dnv}^BMkk-i3$l6BiThqAj-~xq-n#QqcR2Vg(g#8=X5HZn1&}B zhn9LLcAVsAuuF}C6HT#zZT+4m=U)NlD^Qp2@w z^6$$87lp-m(}I^v`Zh&Hvud=#7dBZ!LL-0RkEHp#t<_^x-D@!p-$u8pHzt?K=od1x zf$O4WX`e-&xc7I(b0g}W2UKg0XujXi@b1p{OTEDbp4MfH0nbK$s1gDRUc(6$>ry;~ z>M_py!ehMkXd7TcE-4l!y;3aNyaG)M_o~=Y|!!%)BMx; zxyHwVB$6#sXNas`(K5;Cv*h^+&#cyJWieymdgCzp=D)>54n%yjuR;bDSyb0Y-l6kO zM@*P3D-&{FV}2`@j90ZwZPLbhTI8M!g6)`bn@Pe>`4&8{HpI*B+Xa9j;^#f$>*w+- z1@ha*iFYUbl4*(9rZ!b=BIcLPsk_UV=EP4kTteLilg32pQE{W}p8_6J4Jga!=9Ieo z&}GH!;E=on*HsPnF@O*0;J$_Fl4fcg_0*csz1Bt+EE_7|^;&?KDJsY^Wu-JJ$7NdG;!@tEDe3|}%ZnJzCOW8U8CM*i#Xb>UOiv5E$ z(a1k_Fniv3r17uAOo6Wi&1%+@h_7@pQ(MeQGbRmJm`r>1n%K2R�l8u%HQM-{;?a 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±l3EO$WT!4cU5Fslj=S)G@Xas@+l{B)jj9AJ0N>%y zXOdm+KgVrNy-KtHO!xk(p9=kiiSkidOPZJLB}RXGC=iopjJKL1Odx z)YMe^yL5*nT-vI&ugv>PZ(^MBn6<6>e%jkct*f`>^m*5W&7|%bdH1?H0r7@3d(@A& zamHOdQ2Xa^N4NC*bN_ax;|hcPUBmu=<8K8~XBcCWTJn8v-bNC_vf@u-Ar(o4VIevX_;0 zaxBs_JpAg8sXdM$M}guOQ(ar@>OBqQY|3=La`AK0kw^*l#&plZiMJaoPgC)h-tQKF zyP7bFqWwoIIXP3etRYVwaSoSmAb(>dUNHdR3LXl2lJUi1&u!OM*UTE=YDvg$vIqfuO8nf~)r!TisU++bp& zINffmaL;%0qK+@MZ!k*V1tP>Ry$-7O|_3y ze$zbiD}_dXE|as$x~r{vmOiWg+m-!qLl%|T+G(DaWIGc#Zh^uuj0OOH&!>;S?FQfu zVrS*{(|A;%T<3DlDq8?qrdZK|gQ9Q#aXJ?PGRX5j=YD+nk$&tx%;KVQ*o&Qq?X&V- zotP0#WxkZ!9{$MhWbzTz;yzyVQF_edh@8=Vtn!UyTt+_8UCQ7?SMI~J&Lr4#Xl7}IqZpvi56h|T<<7fYv_#}juPOjemtWs=AozUZ z;xTtQ7OSKzi;o$=KlX$9?KTltfD_F^tN2dF;5w`tIgvD(llx?~PRGaMdB^!iMwm~$ zx7*~(ZUr>tVy49&QH;rxfV-pnq-rwEe)o5`PX|>^3~Nhd_IlEK%6u>t z5mBfjxlRpuO8hc$R9b{o>A2rGlg007WedQk1Gb^s`)1&3`?}rEhGS$PokLNq4BqFk z$4=o&tnM$W1l;0jU=dX+0D6s8Ut(YYXc`Z*%V$|85Y@UsNo3)CowfnH?H3`W90AN{ zAJcouc+hV_PYS}(vtr)QQ+~LfT<~HK4hsBB^bKQvo|*B;;p6XlJ^*u0EQjw&#pf19 zivQV`gYh@U6?n{!@E$k8+V=6-DIzNL3pYMN+;%W8di&&=1pv_qXDXFkM1s?4`$#u` zju0R-u~tu%!|DKb#i13`2Zb5A@#%gs96N6m4`YAEy_giml z_e_qboae3D#P@k3bj=Ez3wxGAC^#&`jYD4JlG5;vqWBycJ`ky>0<|UOB3kPNp(u-5inP{ zMf%@qBy>^O)2RxTe2vlylH ziiXSnpZvAh3z(b^i^q|(%Q+q49xS87!?)xZ;*b8F_qbB^qIkXpyf0RyBdTrVcuoVh z^6P`se6dvUo;W&T{JZ7VSBM@9y4`*ErTx}TftjdZ`b)aPJ`=}lZht^u?;v+l*`>F& zan;(dH;CVehWss&23?Y0FE=m{Vrp;Fj4q{VA{}tLNiYqUb4wFIJ8fxfquzMkWEi;U zN@QvF7FrIHj+V6FbCsJu87OJ~GjSZRsp@c80h&=2LnGN$8N-8HTSoa0IWtW@Rd~(P ze}~F>g@VJVqw*xYS+?UbYI}{AvDFZRSox_vghuOYcs>_B|E67$dcbUrL(bEUElo69 zESc$uGz5B~TR-m)w3dinj;ya}#Y5h=s7_2c3pD=Q>o)znskYnSo=p$u+5he4b)D`1 zkl&F~DKnQz($#?kd{0m!8|z;{B5Jd@$H>VAx6+%Z)X)EvMaJrC=w=JyIS0Jn^&jq| zHuahErzj)$_8Zr{fSFE-!r58^>zA05^2Y+Og1iQH6nWKC34(dxI5p^{ zV9N_kqWCM_Sig4iG#*!+gYWc(6s8C2bNi|St=(t5T(Ou)xwk&6MB!hL&pzkhiIc>0 zYv;D_!kL(~bDwcnhIRkO%0kDBv{qqVEZELDZ!~=neT74UrH=aC@mKXz+eqX*3bMzi zkhC-hpECcU{%MLIyKp8U!=1^A#P4MwUlF0kYH+_}`%2GX+5(>QY?6A4aRxmMs&@i* z@^`miG;k3{kNRDide08Wt0#+)wwd@~XQKa0tEmZ1UhYUh57WmtiSO`-1qXMK%F%mo zmn+k->u4cbm|T0E_ABr4qMp8K^*f(C>rMP_J{^bZ=E;cOYk}>xm+yanmY}y0O4hwU zUdup6hdW3dMz*erxx4!fiko8a(sTW6d4H` zn#PObV|mj;9(}3_2fy5f{wm$rmg6}oc#8YnBnsm$^}gx2JmRvo1C8PKCXO)Oo3w#L z)cFT8q>Shn4SBsnJe;vDs|!nMZTD%jq4Pc0@yDRGdjB7%-8RyAEd|P)(PO*Bc}FwG zz#;Kdb)L=$yv`)MPIQZQjkM1U=)v9t)=|=0_hytOp{=sZ2Uyd5I+v9+xLnBGQ$+x) zC$*jp3*}dp`C`9&$~?XU3io-L>39QLK>yjn4fw6>(c@}6n1?fCV62I1?3z^m3&;4e zNXtR@ZmY|$CA8KXDWg{M^yeF0DH1WQK6zlZWVt~q@^b88@MI-y#%F^l=rAuTb)1~s z&T>psB>$j!v%6No%@=Achc7`sU0{5mq?&^kn{h>?YqpVq86tesZ8ty%X^^0q?Wy0< zq%|DGUZr3=Ve}G0{0(cFfe1|z?!)GJCVf($qxG@2hC{_bd({lIaIP869UR;WM z{aXY0&+#hrt*2wq#gQ~wSyt9S5|oF}&QBt-rig-k>-om>GpD01CeHFju2{(ej@T9> zj9$d;(^ShzXh|9jF>ZqEVi=@l&S`OPCKe6M(>czpgS0V3!7X;PJ2w|KZ?TjUM~6)fYQKLrO1&6;z;!F*HSQXu-qEfok>8ia0h z$ot{n7LK+N|J#ko|9?j8v({7}r|D9SUMrL4rv2SDr%zd!l=-~FIv&wN=sc&XU~QG3 zhU|W??O38nN~HUnw;{y*Efaq^s(yd+TO6eYZp`X``Wuf&&M#3$p{26x_dj+1_Le=& z{$h~edSeaHBViwxFKE%*`mwil>VN(_?LPCSe^}zR2$Pa~lialGFn4}9bhJsKdd?9R z{Ni|2n=mDlfj0#)y~3(#up(0vx682)UY?)cMYe`x;?3cTNPKOfUaW~cJF48?w zrTlEg(^UDZdP;yp1&|ee2CU^e%L~e!;p5x#Wjrc5Xm0WQ*KcOCKOB(EL@bU~}6cY!8wJpI6FhH84rIOR=wJLa9UFdi_2_K(;=d)dKQy##n zcziW-A_~Bu{v{Nde@X7U*5ASDY~&^CoTjS$9i4VQVJO}*8*7>nnj!MQA&>lnS<;ku z;I{v(>;7MFo;t1xwX-YM_}h>iLYLq3KdA2W|A|z`tl4TP$hUb~uTtQ%1@ToYuCA`$ zq6HCR>(qo|!%7ZT){5h#n} zNWad%gvcbb)RS}Qy{(3L-2-|)@9##XxJP_Zrg!+O+kE9nHL+-9>f-{A;OOtX9Em#m7%jFGRYeyukq4); zFD^`~T|7OGyl{?%fA0+AKCE@}WB>;QfEx1D5~q#qjT|tR7S8QWFtY-5aD0wbl{MNp z`@f32+HkhAElf)dGgHD<^{x82T2*dGTi4a1K1-(J`Yf76tKpeYYBW?r(wQ2jdWrgI z=!evlB0)-rAS91Un-)b&5J6hyo;E>-gdigKgt^cCd7t?;zxF!ooU_+Hd+qh^cdhed zbI8Y;+bDs5G4dt!z7j=(^`V;%q>Wb+MlN|>NNX=WkU!KS#s)s_cOhKZ2l3A(ws(&H zBDu^dg76(gT&(^ns0u8s`$Y=Xc;VLm*x`izXsu=7C${9^Yu?%$K~vO2%Ed zuRfkINQ_;(zb{lMj{%#@EEd2)k?v%h8Y!yg^@W~lJTC)2KX$o1{_fVzn0I-GD)=de zRw$SK7Kz;iI?SFhyVV>}Th%Ehw_lGtg_T4ExGN8H{>MZVT2kVQ$k|(|j#uZNh076f zU$|hUpX`-~s5y3aR56U+HU4S}6MY9W?V=cd*d5C>kyca>?rUXD?!G<7Hufv9Q=G!h zdJZ4n#RyKY0^U6!r z%8-wl3v8 z#{3V|w8^GWtC^L+fWgf&c)B&l}4ZdHS$=1QKl?7UC%WrHkawBmn{LBF8)HFZ}Q*!e-(4sb4v%(OX=jURHKbM_I zjSF2lt*0f$!LN+=m#bXnmlSOrhI3@|EwB46+~J3ByEh#pXq$Ph^D60lOWe8np?Awg zoUq1iqy;1Cpo3x_Y9U4h*l>#i1SBwP_{zKkfLerfmTWLDDssm0xeJ%Ll&>LgohwSD zdqBnlENHO_j`&m4*jH{4dTF@t-0^1dx{xzn%b;3GO#AAixT`=+^0}S}RqicXgIe*H zGM|c$jiiRnj>{h0XD^W|dpQ=6r4L@Zm`%2&Um*3eB?UWaDi=GF(j~)KeUG#dyNk#j zOcY~{5=}tDN!Ut_zYA_?pbJA_6+sN?GPSutuiw1f)TWe)#D1=B**yIPp+hP^mN05B zNxtDL>!QQ4=5HYb4^w7q69sUjpn*2%#rgRq5gaqCR zkj@a&R?0cDd#=c2dtEDMyqJAdF|o~_>M`AsXT_fECfb zga{ul(Xy82X6Vrh0$LB=${~YafM5ITlS(IEoRI`}&fh6=y-9r8mB2u*=J+S)iKdm` z35>oD_5H+{*$_RWC%YfwV-|7(-gHnnUDXclkDoXiUgN-6M$emOswaU^kuafmUgEs> z`me1YM#Vnf?3`$pk>Ky|-$q5YlvD~igyBiXvzEc+`##?FS$KXu8pz}lSyV8GiC7&M z)xk6iogFs(dsZ<^q(<~ObNrk92d~f3Cnuj~HKBU%y?*54$6eeSpwA>H$wM+1LxuzD zD$&`@s+l5JH1GMA($=(t--;py`=hj2AvF&ope|;jBf$$2wm)#WhKx;|{H@)om_nub zR}SWim6@A;Kgr@$LZnRzrI$lszuJw45VX4`oUnhcd=v|NkI?KH*>08Ku+&idj{~(6 z6uCtngC%KbhSp_&<0UZn%cm%QYo@(0O&1)c{A%(PoE4jj3^W|%T!8`I;*rAucdsIs zaMr<0ln^{GQC)AXC_c`tr5r_(-qlnO0SA38f*3RXvhb6cbsI~`&$v!4w+x5{S6ZUdFnKw}j) zSO-t%3Ckxw*I0nn@GL}L*4CJ@GK&9{1E39T9~OW6w+W-tDM00dD=J%?!6OvVuDFwf z;2G$;hw?LsU1sjmAhM1XAswRxE9pPr?w(9UCYJjS%$9RuCsbl{e6xfuN6}l}IJV?a zAREE0IQ)9d=B8X}5N}qrj!1m;mz#n)s91J-NUd%)Ls%jys;ANpKOVf9K4se%f#0^= z%~%Yn(|aQ1=(a}!bfE)71qPgC$0!u$}x^8PR-rlXUY1~+Eqn4BE>6l?*$FA>3 z*KN6}sg4QaE?MK7e<-W;u2uJZt9p67WCu!vE*|v#*CB?6N~bfDAlUiOc$WMJ$!fn+ Y>*X0*Pf}L(RaH;^K7rnio-sfE2S*lAga7~l literal 98807 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

CW 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 Tte`l6^=D*Yst?z__x!&A`Ukk6 From 8075c027b748ff4a3041263627bc88947f5995a9 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 18:23:37 +0000 Subject: [PATCH 243/445] Automatic changelog for PR #97015 [ci skip] --- html/changelogs/AutoChangeLog-pr-97015.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97015.yml diff --git a/html/changelogs/AutoChangeLog-pr-97015.yml b/html/changelogs/AutoChangeLog-pr-97015.yml new file mode 100644 index 000000000000..3c1a2133754a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97015.yml @@ -0,0 +1,9 @@ +author: "YesterdaysPromise" +delete-after: True +changes: + - bugfix: "Scythes, shovels, flasks, shears and secutors will now display their worn sprites when in belt/suit storage." + - bugfix: "Tiles, circuit components, wands, types of leather and door remotes no longer display missing/incorrect sprites when worn on belt/suit storage." + - image: "Added missing metalhydrogen inhand sprite, and fixed the colours of the adamantine one." + - image: "Added missing belt/suit storage sprites for all basic material and glass types, flash, shards, light tubes, light bulbs, drapes, and surgery tools which can be put in those slots." + - image: "Hatchets, beakers, secutors, shears etc. had their sprites corrected, as despite showing on left side of front-facing sprite, they also showed up on left facing profile (with right side of the body to the front)" + - image: "Added unique belt/suit storage sprites for carp, ratvar, and narsie plushies." \ No newline at end of file From 4dab89e969b59843b80c796817a4786abb760335 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 26 Jul 2026 18:30:26 +0000 Subject: [PATCH 244/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97015.yml | 9 --------- html/changelogs/AutoChangeLog-pr-97173.yml | 5 ----- html/changelogs/archive/2026-07.yml | 18 ++++++++++++++++++ 3 files changed, 18 insertions(+), 14 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97015.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97173.yml diff --git a/html/changelogs/AutoChangeLog-pr-97015.yml b/html/changelogs/AutoChangeLog-pr-97015.yml deleted file mode 100644 index 3c1a2133754a..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97015.yml +++ /dev/null @@ -1,9 +0,0 @@ -author: "YesterdaysPromise" -delete-after: True -changes: - - bugfix: "Scythes, shovels, flasks, shears and secutors will now display their worn sprites when in belt/suit storage." - - bugfix: "Tiles, circuit components, wands, types of leather and door remotes no longer display missing/incorrect sprites when worn on belt/suit storage." - - image: "Added missing metalhydrogen inhand sprite, and fixed the colours of the adamantine one." - - image: "Added missing belt/suit storage sprites for all basic material and glass types, flash, shards, light tubes, light bulbs, drapes, and surgery tools which can be put in those slots." - - image: "Hatchets, beakers, secutors, shears etc. had their sprites corrected, as despite showing on left side of front-facing sprite, they also showed up on left facing profile (with right side of the body to the front)" - - image: "Added unique belt/suit storage sprites for carp, ratvar, and narsie plushies." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97173.yml b/html/changelogs/AutoChangeLog-pr-97173.yml deleted file mode 100644 index d34812d58a82..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97173.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Iajret" -delete-after: True -changes: - - bugfix: "Defeating Hiero now gives style point multiplier" - - bugfix: "Successfully tapping a vent will grant a style point multiplication too, as it was supposed to" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index 13924eb51264..1fbd30dd5b30 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -786,6 +786,10 @@ - spellcheck: Adjusted some descriptions on some rifles (the Sakhno bolt-action, the SKS, the anti-materiel sniper) and nudged extended lore bits to examining twice. + Iajret: + - bugfix: Defeating Hiero now gives style point multiplier + - bugfix: Successfully tapping a vent will grant a style point multiplication too, + as it was supposed to Melbert: - rscadd: The AI and Cyborg upload computers have been replaced with module racks, a physical manifestation of AI laws. @@ -808,6 +812,20 @@ - bugfix: Departmental photo albums now properly save their photos between rounds SmArtKar: - bugfix: Fixed hair gradients, again + YesterdaysPromise: + - bugfix: Scythes, shovels, flasks, shears and secutors will now display their worn + sprites when in belt/suit storage. + - bugfix: Tiles, circuit components, wands, types of leather and door remotes no + longer display missing/incorrect sprites when worn on belt/suit storage. + - image: Added missing metalhydrogen inhand sprite, and fixed the colours of the + adamantine one. + - image: Added missing belt/suit storage sprites for all basic material and glass + types, flash, shards, light tubes, light bulbs, drapes, and surgery tools which + can be put in those slots. + - image: Hatchets, beakers, secutors, shears etc. had their sprites corrected, as + despite showing on left side of front-facing sprite, they also showed up on + left facing profile (with right side of the body to the front) + - image: Added unique belt/suit storage sprites for carp, ratvar, and narsie plushies. hry-gh: - qol: the escape menu now runs clientside, and should be considerably more responsive lelandkemble: From be08c1e51ed6237fea179710ebbeac7d67fb1300 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sun, 26 Jul 2026 16:40:44 -0500 Subject: [PATCH 245/445] Makes significant changes to Heretic spells and rituals (#95796) ## About The Pull Request - Focus is dead. You no longer need a focus to cast spells. - Heretic spells are now limited according to a number of charges. Once you are out of charges, you need to replenish them according to your path or the spell. Some spells have special conditions to fulfill, but for the most part your path has a primary condition to fulfill (like rusting tiles for rust). - Holy water will purge charges from heretic spells. - The heretic focus item has been repurposed; it is now a tier one knowledge that restores a charge of your spells every few minutes. The upgraded cult versions restore spells even faster. - Integrates #95551 . Codex is a T1 knowledge, Warren is a T1 knowledge with a new effect. - Integrates #95728 , to a degree. Codex numbers have been reduced a bit. - Some spells have had their numbers tweaked. - Wolves Among Sheep healing has been reduced. - Spell empowerment has been removed from ash heretics. - Heretic rituals now use a radial. image - Tooltips for heretic nodes are now formatted. image - Adds t2 knowledge `Unwrap Minds` which allows the heretic to hypnotize someone with the contents of a paper. If you try to hypnotize someone who is mindshielded, it breaks the mindshield, but hypnotizes the with something random. - Void prison is now an item - Ritual: 1x handcuff, 1x closet (non-crate), 1x baton - Now captures *all* non-heretic, non-marked mobs within 3 tiles - Using it as a non-heretic captures yourself (which funnily enough might be useful for emergencies) - Void conduit is now a "passive" - Clicking on space/snow/vacuum turf with a void blade will, after a short do-after, break the blade and create a conduit. - There is no cooldown to this, you are solely limited by blade count (as it requires breaking blades) - Adds a t1 knowledge "Miraculous Mirror" - Alongside being able to change your hair, allows you to change your gender, skin/scale color, and eye color. Also allows you to change your species to any other roundstart race, but doing this will break the mirror. If a non-heretic tries to use the mirror, gives them a random result (ie, a random hairstyle or random species). - Ash heretic's Mask of Madness is now a T3 side knowledge. Also now requires lit candles. - Taking place of Mask of Madness on the Ash Heretic's main path is the Nightwatcher's Lantern, a special light source that causes eye damage, confusion, and stamina damage to people who are enveloped in the light. Wearing eye protection lessens the effect (and being light sensitive worsens the effect). The light itself is weaker if used in an already bright area. - Adds a T3 ritual "Keys to the Backdoor", which effectively makes a pocket dimension that you enter via a closet. The dimension serves as a safe-ish place for heretics to prepare gimmicks or transmute stuff. Living mobs must be willing to enter it (ie, not handcuffed and not in crit). Dead mobs have no such qualms, buuuut you can't sacrifice people in here. - Shadow Cloak is no longer a starting knowledge, but is guaranteed to show up in all heretic's first tier draft for free. ## Why It's Good For The Game The intent behind these changes are to shift heretic to focusing (no pun intended) around rituals more. The core of their power should come from the various rituals they can invoke, rather than the buttons they press in the UI. To tackle this, I'm starting with spells - With these changes, Heretics cannot keep their momentum up forever. You can never fully shift to mini-wizard gameplay, as you will have to recharge spells at some point by completing more rituals. It also means heretics are fully containable. You can drain them of all their spell charges, including from their grasp, preventing a rust or lock heretic from waltzing out. ## Changelog :cl: Melbert balance: Heretics no longer need a focus to cast spells. balance: (Almost) all heretic spells are now limited to a number of charges. Once you are out of charges, you need to replenish them. For some spells, there are special conditions to fulfill to recharge them, but for most of them you must simply complete a condition according to your path (like rusting tiles as rust). balance: Holywater will purge spell charges from heretics. balance: "Amber Focus" is now a tier one side knowledge that slooowwwly replenishes spell charges over time. balance: Codex Cicatrix is now a T1 knowledge. Its drain speed has been increased (1.5x slower) as has its draw speed (1.875x slower). Codex Morbus has also seen an increase to drain speed (2x slower) and draw speed (2x slower). balance: Warren King's Welcome is now a T1 knowedge and has additional effects - restricting nearby airlocks to "heretic" access and granting your ID "heretic" access. balance: Ash heretics can no longer empower their spells while on fire. balance: Wolves Among Sheep heretic healing has been reduced by 75% (60 -> 15) qol: Heretic rituals now use a radial for deciding which to invoke qol: Tooltips for heretics are now formatted, easier to read add: Adds a tier two knowledge to heretic, "Unwrap Minds", which allows them to hypnotize someone. add: Adds a tier one knowledge to heretic, "The Miraculous Mirror", which allow them to change their appearance (including species). add: Adds a tier three knowledge to heretic, "Keys to the Backdoor", which effectively gives them a pocket dimension accessible via closet to do... things in. add: Mask of Madness is now a tier three side knowledge, allowing any heretic access to it. Also now requires lit candles. add: Ash Heretics now have a new main path node, "Nightwatcher's Lantern", which causes negative effects to mobs who are in the light's radius. balance: Shadow Cloak is no longer an innate heretic knowledge, but is guaranteed to show up for free in the first draft pool (in addition to the three other options). /:cl: --- _maps/templates/mansus_backdoor.dmm | 3545 +++++++++++++++++ code/__DEFINES/dcs/signals/signals_action.dm | 3 + code/__DEFINES/dcs/signals/signals_heretic.dm | 22 + .../signals/signals_mob/signals_mob_living.dm | 3 + code/__DEFINES/dcs/signals/signals_object.dm | 7 + code/__DEFINES/dcs/signals/signals_spell.dm | 3 + code/__DEFINES/is_helpers.dm | 8 + code/__DEFINES/span.dm | 5 +- code/__DEFINES/traits/declarations.dm | 2 - code/_globalvars/phobias.dm | 2 +- code/_globalvars/traits/_traits.dm | 1 - code/_globalvars/traits/admin_tooling.dm | 1 - code/datums/actions/action.dm | 1 + code/datums/eigenstate.dm | 8 +- .../proximity_monitor/fields/heretic_arena.dm | 14 +- code/game/atom/atom_act.dm | 5 +- code/game/machinery/_machinery.dm | 1 + code/game/machinery/doors/windowdoor.dm | 4 + code/game/objects/items.dm | 7 +- code/game/objects/items/stacks/bscrystal.dm | 1 + code/game/objects/structures.dm | 1 + .../structures/crates_lockers/closets.dm | 6 +- code/game/objects/structures/mirror.dm | 72 +- code/game/objects/structures/secure_safe.dm | 1 + code/game/turfs/open/floor/fancy_floor.dm | 5 + code/game/turfs/turf.dm | 4 +- code/modules/antagonists/cult/cult_items.dm | 8 +- .../antagonists/heretic/heretic_antag.dm | 81 +- .../antagonists/heretic/heretic_curses.dm | 8 +- .../antagonists/heretic/heretic_focus.dm | 61 - .../antagonists/heretic/heretic_knowledge.dm | 198 +- .../heretic/heretic_living_heart.dm | 2 +- .../modules/antagonists/heretic/influences.dm | 1 + .../heretic/items/forbidden_book.dm | 31 +- .../heretic/items/heretic_armor.dm | 18 +- .../heretic/items/heretic_blades.dm | 2 + .../heretic/items/heretic_grenade.dm | 4 +- .../heretic/items/heretic_lantern.dm | 185 + .../heretic/items/heretic_necks.dm | 70 +- .../void_prison.dm => items/voidballd.dm} | 82 +- .../heretic/knowledge/_heretic_paths.dm | 22 +- .../antagonists/heretic/knowledge/ash_lore.dm | 87 +- .../heretic/knowledge/blade_lore.dm | 89 +- .../heretic/knowledge/cosmic_lore.dm | 43 +- .../heretic/knowledge/flesh_lore.dm | 40 +- .../knowledge/heretic_armor_knowledge.dm | 3 +- .../heretic/knowledge/lock_lore.dm | 54 +- .../heretic/knowledge/moon_lore.dm | 72 +- .../heretic/knowledge/rust_lore.dm | 52 +- .../sacrifice_knowledge/reroll_targets.dm | 4 +- .../sacrifice_knowledge/sacrifice_curse.dm | 4 +- .../sacrifice_knowledge.dm | 36 +- .../sacrifice_knowledge/sacrifice_map.dm | 2 +- .../knowledge/side_knowledge/tier_four.dm | 75 +- .../knowledge/side_knowledge/tier_one.dm | 113 +- .../knowledge/side_knowledge/tier_three.dm | 192 +- .../knowledge/side_knowledge/tier_two.dm | 116 +- .../heretic/knowledge/starting_lore.dm | 69 +- .../heretic/knowledge/void_lore.dm | 166 +- .../antagonists/heretic/magic/ash_jaunt.dm | 53 - .../heretic/magic/furious_steel.dm | 21 +- .../heretic/magic/lunatic_track.dm | 2 +- .../antagonists/heretic/magic/rust_charge.dm | 3 +- .../antagonists/heretic/magic/shadow_cloak.dm | 22 +- .../antagonists/heretic/magic/space_crawl.dm | 5 - .../heretic/magic/wolves_among_sheep.dm | 1 - .../heretic/status_effects/buffs.dm | 8 +- .../heretic/status_effects/heretic_passive.dm | 187 +- .../heretic/structures/mawed_crucible.dm | 2 +- .../{magic => structures}/void_conduit.dm | 36 +- .../antagonists/heretic/transmutation_rune.dm | 64 +- .../modules/fishing/sources/subtypes/rifts.dm | 2 +- code/modules/mob/living/basic/bots/_bots.dm | 1 + code/modules/mob/living/silicon/silicon.dm | 1 + .../chemistry/reagents/other_reagents.dm | 25 +- .../spells/spell_types/touch/_touch.dm | 1 + code/modules/unit_tests/heretic_rituals.dm | 3 +- code/modules/vehicles/mecha/_mecha.dm | 3 +- interface/stylesheet.dm | 2 +- .../antagonist_flavor/heretic_influences.json | 8 + tgstation.dme | 6 +- .../tgui-panel/styles/tgchat/chat-dark.scss | 5 + .../tgui-panel/styles/tgchat/chat-light.scss | 5 + .../tgui/interfaces/AntagInfoHeretic.tsx | 341 +- 84 files changed, 5692 insertions(+), 837 deletions(-) create mode 100644 _maps/templates/mansus_backdoor.dmm delete mode 100644 code/modules/antagonists/heretic/heretic_focus.dm create mode 100644 code/modules/antagonists/heretic/items/heretic_lantern.dm rename code/modules/antagonists/heretic/{magic/void_prison.dm => items/voidballd.dm} (51%) rename code/modules/antagonists/heretic/{magic => structures}/void_conduit.dm (83%) diff --git a/_maps/templates/mansus_backdoor.dmm b/_maps/templates/mansus_backdoor.dmm new file mode 100644 index 000000000000..ab7864012a35 --- /dev/null +++ b/_maps/templates/mansus_backdoor.dmm @@ -0,0 +1,3545 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"as" = ( +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"ay" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 4 + }, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"aD" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -30 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"ei" = ( +/obj/structure/dresser, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"eo" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -3; + teleport_x_offset = 2 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"fq" = ( +/turf/open/mirage{ + dir = 4; + target_turf_x = -31; + range = 12 + }, +/area/centcom/heretic_backdoor) +"fW" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = -3; + teleport_y_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"gm" = ( +/obj/structure/table/wood/shuttle_bar, +/obj/effect/turf_decal/siding/wood, +/obj/item/paper/crumpled{ + default_raw_text = "We know you're here." + }, +/obj/item/pen, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"iG" = ( +/obj/structure/fireplace{ + desc = "It looks like it hasn't been lit in a long time." + }, +/obj/effect/decal/cleanable/ash{ + pixel_y = 0; + pixel_x = 4 + }, +/turf/open/floor/stone, +/area/centcom/heretic_backdoor) +"iL" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -3; + teleport_x_offset = 1 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"jq" = ( +/obj/effect/turf_decal/siding/wood/corner{ + dir = 4 + }, +/obj/structure/chair/sofa/corner/brown{ + dir = 1 + }, +/obj/effect/turf_decal/siding/wood/corner{ + dir = 1 + }, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"lL" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -1; + teleport_x_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"nc" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = -2; + teleport_y_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"nf" = ( +/turf/open/mirage{ + dir = 8; + target_turf_x = 31; + range = 12 + }, +/area/centcom/heretic_backdoor) +"oy" = ( +/obj/effect/spawner/structure/window, +/obj/structure/curtain/bounty, +/turf/open/indestructible/plating, +/area/centcom/heretic_backdoor) +"oz" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -3; + teleport_x_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"oH" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = -3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"pE" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = 1; + teleport_y_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"pS" = ( +/obj/item/paper/crumpled{ + invisibility = 101; + default_raw_text = "How do mirage work: TL;DR projects a number of turfs in front of the target turf (according to direction and offset) based on range." + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"ql" = ( +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"qF" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -2; + teleport_x_offset = -3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"rv" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = -30 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"tY" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -2; + teleport_x_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"va" = ( +/obj/effect/spawner/structure/window, +/obj/structure/curtain/bounty/start_closed, +/turf/open/indestructible/plating, +/area/centcom/heretic_backdoor) +"vY" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = 30 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"xb" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -3; + teleport_x_offset = -1 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"xr" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = 30 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"xA" = ( +/obj/structure/bed/double, +/obj/item/bedsheet/cosmos/double, +/obj/effect/turf_decal/siding/wood{ + dir = 4 + }, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"xD" = ( +/obj/structure/chair/wood{ + dir = 8 + }, +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"yL" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = 2; + teleport_y_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"Ah" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = -1; + teleport_y_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"Ak" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"Fx" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -1; + teleport_x_offset = -3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"FH" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 4 + }, +/obj/structure/closet/cabinet, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"Gp" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"KE" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 1 + }, +/obj/structure/chair/sofa/right/brown{ + dir = 1 + }, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"Lu" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -3; + teleport_x_offset = -2 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"Ng" = ( +/turf/open/mirage{ + target_turf_y = 31; + range = 7 + }, +/area/centcom/heretic_backdoor) +"Nx" = ( +/turf/closed/indestructible/wood, +/area/centcom/heretic_backdoor) +"Ny" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = 3; + teleport_y_offset = 1 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"OC" = ( +/obj/structure/chair/wood{ + dir = 4 + }, +/obj/effect/turf_decal/siding/wood, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"PM" = ( +/obj/structure/table/wood/shuttle_bar, +/obj/effect/turf_decal/siding/wood/corner, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) +"PY" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"Qh" = ( +/turf/open/mirage{ + dir = 1; + target_turf_y = -31; + range = 7 + }, +/area/centcom/heretic_backdoor) +"Qm" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = -3; + teleport_x_offset = -3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"QU" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = -3; + teleport_y_offset = 2 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"Tn" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_y_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"Uo" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = 3; + teleport_y_offset = 2 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"UX" = ( +/obj/structure/mineral_door/wood, +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/obj/effect/turf_decal/siding/wood{ + dir = 4 + }, +/turf/open/floor/stone, +/area/centcom/heretic_backdoor) +"WH" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = 3; + teleport_y_offset = 3 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"XR" = ( +/turf/open/floor/stone, +/area/centcom/heretic_backdoor) +"Yi" = ( +/obj/effect/step_trigger/teleporter/offset{ + teleport_x_offset = -3; + teleport_y_offset = 1 + }, +/turf/open/floor/grass/norandomicon{ + desc = "Focusing intently on individual blades of grass, it almost looks like they're moving." + }, +/area/centcom/heretic_backdoor) +"YR" = ( +/obj/effect/turf_decal/siding/wood{ + dir = 4 + }, +/obj/structure/chair/sofa/left/brown{ + dir = 4 + }, +/obj/effect/turf_decal/siding/wood{ + dir = 8 + }, +/turf/open/floor/wood/large, +/area/centcom/heretic_backdoor) + +(1,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(2,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +pS +as +"} +(3,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(4,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(5,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(6,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(7,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(8,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(9,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(10,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(11,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(12,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(13,1,1) = {" +as +as +as +as +as +as +as +as +oz +tY +lL +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +PY +Ny +Uo +WH +as +as +as +as +as +as +as +as +"} +(14,1,1) = {" +as +as +as +as +as +as +as +as +eo +as +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +nf +as +yL +as +as +as +as +as +as +as +as +"} +(15,1,1) = {" +as +as +as +as +as +as +as +as +iL +Qh +as +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +vY +as +Ng +pE +as +as +as +as +as +as +as +as +"} +(16,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(17,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(18,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(19,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(20,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(21,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(22,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(23,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(24,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(25,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(26,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(27,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +Nx +va +Nx +UX +Nx +Nx +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(28,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +Nx +YR +jq +ql +ql +Nx +Nx +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(29,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +Nx +XR +KE +ql +ql +OC +oy +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(30,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +Nx +iG +Ak +ql +ql +gm +oy +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(31,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +Nx +XR +Ak +ql +ql +xD +oy +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(32,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +Nx +Nx +Nx +UX +Nx +FH +Nx +Nx +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(33,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +Nx +ei +ql +ql +Nx +va +Nx +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(34,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +Nx +PM +xA +ay +Nx +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(35,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +Nx +Nx +va +Nx +Nx +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(36,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(37,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(38,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(39,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(40,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(41,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(42,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(43,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(44,1,1) = {" +as +as +as +as +as +as +as +as +Gp +Qh +aD +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +xr +Ng +Tn +as +as +as +as +as +as +as +as +"} +(45,1,1) = {" +as +as +as +as +as +as +as +as +xb +Qh +as +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +rv +as +Ng +Ah +as +as +as +as +as +as +as +as +"} +(46,1,1) = {" +as +as +as +as +as +as +as +as +Lu +as +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +fq +as +nc +as +as +as +as +as +as +as +as +"} +(47,1,1) = {" +as +as +as +as +as +as +as +as +Qm +qF +Fx +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +oH +Yi +QU +fW +as +as +as +as +as +as +as +as +"} +(48,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(49,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(50,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(51,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(52,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(53,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(54,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(55,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(56,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(57,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(58,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} +(59,1,1) = {" +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +as +"} diff --git a/code/__DEFINES/dcs/signals/signals_action.dm b/code/__DEFINES/dcs/signals/signals_action.dm index 66a4ffc894b6..649662d2add1 100644 --- a/code/__DEFINES/dcs/signals/signals_action.dm +++ b/code/__DEFINES/dcs/signals/signals_action.dm @@ -58,6 +58,9 @@ /// From /datum/action/cooldown/spell/touch/lay_on_hands/proc/determine_if_this_hurts_instead(), sent to the /mob/living/carbon/hurtguy: (/mob/living/carbon/mendicant) #define COMSIG_ON_LAY_ON_HANDS "mob_ability_lay_on_hands" +/// From /datum/action/update_button_status (atom/movable/screen/movable/action_button/current_button, force) +#define COMSIG_ACTION_STATUS_UPDATE "action_status_update" + /// From /datum/action/innate/dash/proc/teleport when teleport is successful #define COMSIG_DASH_ACTION_DASHED "dash_action_dashed" /// From /datum/action/innate/dash/proc/charge when charges are replenished diff --git a/code/__DEFINES/dcs/signals/signals_heretic.dm b/code/__DEFINES/dcs/signals/signals_heretic.dm index 14fe7a51e535..dc19b2f0b057 100644 --- a/code/__DEFINES/dcs/signals/signals_heretic.dm +++ b/code/__DEFINES/dcs/signals/signals_heretic.dm @@ -9,6 +9,8 @@ /// Default behavior is to continue attack chain and do nothing else, so return this to use up the hand after use. #define COMPONENT_USE_HAND (1<<0) +/// From /obj/item/melee/sickly_blade/preattack : (mob/living/source, mob/living/target) +#define COMSIG_HERETIC_BLADE_PREATTACK "blade_preattack" /// From /obj/item/melee/sickly_blade/afterattack : (mob/living/source, mob/living/target) #define COMSIG_HERETIC_BLADE_ATTACK "blade_attack" /// From /obj/item/melee/sickly_blade/ranged_interact_with_atom (without proximity) : (mob/living/source, mob/living/target) @@ -16,6 +18,8 @@ /// For [/datum/status_effect/protective_blades] to signal when it is triggered #define COMSIG_BLADE_BARRIER_TRIGGERED "blade_barrier_triggered" +/// For [/datum/status_effect/protective_blades] to the mob after triggering a blade +#define COMSIG_MOB_BLADE_BARRIER_TRIGGERED "mob_blade_barrier_triggered" /// at the end of determine_drafted_knowledge #define COMSIG_HERETIC_SHOP_SETUP "heretic_shop_finished" @@ -24,3 +28,21 @@ #define COMSIG_HERETIC_PASSIVE_UPGRADE_FIRST "heretic_passive_upgrade_first" /// called on the antagonist datum, upgrades the passive to level 3 #define COMSIG_HERETIC_PASSIVE_UPGRADE_FINAL "heretic_passive_upgrade_final" + +/// Sent to a heretic datum when an influence is drained +#define COMSIG_HERETIC_INFLUENCE_DRAINED "heretic_influence_drained" + +/// Send to a mob after they apply the moonlight amulet to another mob +#define COMSIG_MOB_APPLIED_MOONLIGHT_AMULET "mob_applied_moonlight_amulet" + +/// Sent from a mob as they invoke rust_heretic_act +#define COMSIG_MOB_RUST_HERETIC_ACT "living_rust_heretic_act" + +/// Sent to a heretic datum on a successful sacrifice: (mob/living/sacrificed, high_value) +#define COMSIG_HERETIC_SACRIFICE "heretic_sacrifice" + +/// Sent to a mob when they summon a heretic monster +#define COMSIG_HERETIC_SUMMONED_MOB "heretic_summoned_mob" + +/// Sent to a heretic when they successfully research a piece of heretic knowledge +#define COMSIG_HERETIC_RESEARCHED_KNOWLEDGE "heretic_researched_knowledge" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index f245d49bbf8a..1daa58ee6548 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -388,6 +388,9 @@ /// From /mob/living/on_looking_z_level_change() : (turf/old_turf, turf/new_turf) #define COMSIG_LIVING_LOOK_Z_CHANGE "living_look_z_change" +/// From /obj/item/stack/ore/bluespace_crystal/attack_self() : (obj/item/crushed_crystal) +#define COMSIG_MOB_CRUSHED_BLUESPACE_CRYSTAL "living_crushed_bluespace_crystal" + /// From /mob/living/proc/refresh_gravity() : (new_gravity, old_gravity) #define COMSIG_LIVING_GRAVITY_CHANGED "living_gravity_changed" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 04075907d90c..91948c77bb7c 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -258,6 +258,13 @@ ///used to interrupt insertion #define COMPONENT_CLOSET_INSERT_INTERRUPT (1<<0) +/// From closet_teleporter_controller when something is teleporting through a closet +#define COMSIG_CLOSET_TELEPORTER_PRE_SENDING "closet_teleporter_pre_sending" + /// Blocks the item from teleporting + #define CLOSET_TELEPORT_BLOCKED (1<<0) + /// Ignores the can_teleport check + #define CLOSET_TELEPORT_FORCED (1<<1) + ///From open: (forced) #define COMSIG_CLOSET_PRE_OPEN "closet_pre_open" #define BLOCK_OPEN (1<<0) diff --git a/code/__DEFINES/dcs/signals/signals_spell.dm b/code/__DEFINES/dcs/signals/signals_spell.dm index 1154eba338f1..50f1ee20fae3 100644 --- a/code/__DEFINES/dcs/signals/signals_spell.dm +++ b/code/__DEFINES/dcs/signals/signals_spell.dm @@ -74,6 +74,9 @@ #define COMSIG_TOUCH_HANDLESS_CAST "spell_touch_handless_cast" /// Return this to prevent the hand spawning/unspawning #define COMPONENT_CAST_HANDLESS (1<<0) +/// "Casting" a touch spell brings it into your hand, so this is for actually hitting someone with it +/// Sent from /datum/action/cooldown/spell/touch/do_hand_hit: (atom/hit, mob/living/carbon/caster, obj/item/melee/touch_attack/hand) +#define COMSIG_SPELL_TOUCH_SPELL_ACTUALLY_CAST "spell_touch_spell_actually_cast" // Jaunt Spells /// Sent from datum/action/cooldown/spell/jaunt/before_cast, before the mob enters jaunting as a pre-check: (datum/action/cooldown/spell/spell) diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 4f8cf553f2e9..9e397e697ac2 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -58,6 +58,14 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list( #define isopenspaceturf(A) (is_type_in_typecache(A, GLOB.turfs_openspace)) +GLOBAL_LIST_INIT(turfs_snow, typecacheof(list( + /turf/open/floor/fake_snow, + /turf/open/misc/asteroid/snow, + /turf/open/misc/snow +))) + +#define issnowturf(A) (is_type_in_typecache(A, GLOB.turfs_snow)) + #define isopenturf(A) (istype(A, /turf/open)) #define isindestructiblefloor(A) (istype(A, /turf/open/indestructible)) diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm index 99a54aa4ba47..eb512792517c 100644 --- a/code/__DEFINES/span.dm +++ b/code/__DEFINES/span.dm @@ -86,15 +86,15 @@ #define span_looc(str) ("" + str + "") #define span_major_announcement_text(str) ("" + str + "") #define span_major_announcement_title(str) ("" + str + "") +#define span_mansus(str) ("" + str + "") #define span_medal(str) ("" + str + "") #define span_medradio(str) ("" + str + "") #define span_memo(str) ("" + str + "") #define span_memoedit(str) ("" + str + "") -#define span_policy(str) ("" + str + "") #define span_message(str) ("" + str + "") #define span_mind_control(str) ("" + str + "") -#define span_minorannounce(str) ("" + str + "") #define span_minoralert(str) ("" + str + "") +#define span_minorannounce(str) ("" + str + "") #define span_monkey(str) ("" + str + "") #define span_name(str) ("" + str + "") #define span_narsie(str) ("" + str + "") @@ -106,6 +106,7 @@ #define span_ooc_announcement_text(str) ("" + str + "") #define span_papyrus(str) ("" + str + "") #define span_phobia(str) ("" + str + "") +#define span_policy(str) ("" + str + "") #define span_prefix(str) ("" + str + "") #define span_priorityalert(str) ("" + str + "") #define span_priorityannounce(str) ("" + str + "") diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index c498ae7f4099..e79646192b15 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1001,8 +1001,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_HERETIC_AURA_HIDDEN "heretic_aura_hidden" /// This rod was infused by a heretic, making it awesome and improving influence gain #define TRAIT_ROD_MANSUS_INFUSED "rod_infused" -/// Allows heretics to cast their spells. -#define TRAIT_ALLOW_HERETIC_CASTING "allow_heretic_casting" /// Designates a heart as a living heart for a heretic. #define TRAIT_LIVING_HEART "living_heart" /// Trait given to all participants in a heretic arena diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index a040aeab24e7..1318496ac263 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -341,7 +341,7 @@ GLOBAL_LIST_INIT(phobia_objs, list( /obj/machinery/door/airlock/centcom, /obj/item/clothing/mask/madness_mask, /obj/item/wallframe/painting/eldritch, - /obj/item/clothing/neck/heretic_focus/moon_amulet, + /obj/item/clothing/neck/moon_amulet, /obj/item/melee/sickly_blade/moon, )), "doctors" = typecacheof(list( diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 4a59cc9fd831..ff9913e11df5 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -178,7 +178,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_AI_ACCESS" = TRAIT_AI_ACCESS, "TRAIT_AI_BAGATTACK" = TRAIT_AI_BAGATTACK, "TRAIT_ALCOHOL_TOLERANCE" = TRAIT_ALCOHOL_TOLERANCE, - "TRAIT_ALLOW_HERETIC_CASTING" = TRAIT_ALLOW_HERETIC_CASTING, "TRAIT_ALWAYS_WANTED" = TRAIT_ALWAYS_WANTED, "TRAIT_ANALGESIA" = TRAIT_ANALGESIA, "TRAIT_ANGELIC" = TRAIT_ANGELIC, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index cdbd203f996a..ba5d03e2197e 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -45,7 +45,6 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_AGENDER" = TRAIT_AGENDER, "TRAIT_AGEUSIA" = TRAIT_AGEUSIA, "TRAIT_ALCOHOL_TOLERANCE" = TRAIT_ALCOHOL_TOLERANCE, - "TRAIT_ALLOW_HERETIC_CASTING" = TRAIT_ALLOW_HERETIC_CASTING, "TRAIT_ALWAYS_WANTED" = TRAIT_ALWAYS_WANTED, "TRAIT_ANOSMIA" = TRAIT_ANOSMIA, "TRAIT_ANTENNAE" = TRAIT_ANTENNAE, diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm index 22ab9c51a92e..b70c3f4d821f 100644 --- a/code/datums/actions/action.dm +++ b/code/datums/actions/action.dm @@ -341,6 +341,7 @@ current_button.color = rgb(255,255,255,255) else current_button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0) + SEND_SIGNAL(src, COMSIG_ACTION_STATUS_UPDATE, current_button, force) /// Gives our action to the passed viewer. /// Puts our action in their actions list and shows them the button. diff --git a/code/datums/eigenstate.dm b/code/datums/eigenstate.dm index 390d79bde950..90b76662a050 100644 --- a/code/datums/eigenstate.dm +++ b/code/datums/eigenstate.dm @@ -53,7 +53,8 @@ GLOBAL_DATUM_INIT(closet_teleport_controller, /datum/closet_teleport_controller, target.alpha = 200 do_sparks(3, FALSE, target) - visible_atom.visible_message("The items shimmer and fizzle, turning a shade of violet blue.") + if(!subtle) + visible_atom.visible_message("The items shimmer and fizzle, turning a shade of violet blue.") id_counter++ return TRUE @@ -90,6 +91,9 @@ GLOBAL_DATUM_INIT(closet_teleport_controller, /datum/closet_teleport_controller, /datum/closet_teleport_controller/proc/use_eigenlinked_atom(atom/object_sent_from, atom/movable/thing_to_send) SIGNAL_HANDLER + var/tp_flags = SEND_SIGNAL(object_sent_from, COMSIG_CLOSET_TELEPORTER_PRE_SENDING, thing_to_send) + if(tp_flags & CLOSET_TELEPORT_BLOCKED) + return FALSE var/id = eigen_id[object_sent_from] if(!id) stack_trace("[object_sent_from] attempted to eigenlink to something that didn't have a valid id!") @@ -106,7 +110,7 @@ GLOBAL_DATUM_INIT(closet_teleport_controller, /datum/closet_teleport_controller, if(!eigen_target) stack_trace("No eigen target set for the eigenstate component!") return FALSE - if(check_teleport_valid(thing_to_send, eigen_target, TELEPORT_CHANNEL_QUANTUM)) + if((tp_flags & CLOSET_TELEPORT_FORCED) || check_teleport_valid(thing_to_send, eigen_target, TELEPORT_CHANNEL_QUANTUM)) thing_to_send.forceMove(eigen_target) else if(!subtle) diff --git a/code/datums/proximity_monitor/fields/heretic_arena.dm b/code/datums/proximity_monitor/fields/heretic_arena.dm index b90e81201aa3..1b4bdbde476d 100644 --- a/code/datums/proximity_monitor/fields/heretic_arena.dm +++ b/code/datums/proximity_monitor/fields/heretic_arena.dm @@ -163,16 +163,22 @@ GLOBAL_LIST_EMPTY(heretic_arenas) human_head?.worn_head_offset?.apply_offset(crown_overlay) owner.add_overlay(crown_overlay) owner.remove_traits(list(TRAIT_ELDRITCH_ARENA_PARTICIPANT, TRAIT_NO_TELEPORT), TRAIT_STATUS_EFFECT(id)) + crit_count++ // The mansus celebrates your efforts if(IS_HERETIC(owner)) - owner.heal_overall_damage(60, 60, 60) - owner.adjust_tox_loss(-60, forced = TRUE) // Slime heretics everywhere... - owner.adjust_oxy_loss(-60) + // enemies are given 17 force 0 ap blades, heretics have 50 armor: meaning 30 healing reverses about 4 hits of damage + owner.heal_overall_damage(round(30 / crit_count, DAMAGE_PRECISION), round(30 / crit_count, DAMAGE_PRECISION)) + owner.adjust_tox_loss(round(-20 / crit_count, DAMAGE_PRECISION), forced = TRUE) // Slime heretics everywhere... + owner.adjust_oxy_loss(round(-20 / crit_count, DAMAGE_PRECISION), forced = TRUE) if(iscarbon(owner)) var/mob/living/carbon/carbon_owner = owner for(var/datum/wound/wound as anything in carbon_owner.all_wounds) wound.remove_wound() + if(crit_count == 3) + var/datum/antagonist/heretic/our_heretic = GET_HERETIC(owner) + var/datum/heretic_knowledge/spell/wolves_among_sheep/our_knowledge = our_heretic.get_knowledge(__IMPLIED_TYPE__) + our_knowledge?.add_charges(1) if(arena_victor) // No need to spam if we've already killed at least 1 person return @@ -194,6 +200,8 @@ GLOBAL_LIST_EMPTY(heretic_arenas) tick_interval = STATUS_EFFECT_NO_TICK status_type = STATUS_EFFECT_UNIQUE alert_type = null + /// Tracks number of people crit + var/crit_count = 0 /// Tracks the last person who dealt damage to this mob var/datum/weakref/last_attacker /// If our mob is free to leave, set to true diff --git a/code/game/atom/atom_act.dm b/code/game/atom/atom_act.dm index 233060015dc6..69a4ec6c7840 100644 --- a/code/game/atom/atom_act.dm +++ b/code/game/atom/atom_act.dm @@ -233,12 +233,13 @@ * rust_strength (optional) - if you want to vary the effect based on the users' strength */ /atom/proc/rust_heretic_act(rust_strength) - return + return FALSE ///wrapper proc that passes our mob's rust_strength to the target we are rusting /mob/living/proc/do_rust_heretic_act(atom/target) var/datum/antagonist/heretic/heretic_data = GET_HERETIC(src) - target.rust_heretic_act(heretic_data?.rust_strength) + var/result = target.rust_heretic_act(heretic_data?.rust_strength) + SEND_SIGNAL(src, COMSIG_MOB_RUST_HERETIC_ACT, target, result) ///Called when something resists while this atom is its loc /atom/proc/container_resist_act(mob/living/user) diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index b1be826611c6..6e78ec9dbc9b 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -1293,6 +1293,7 @@ /obj/machinery/rust_heretic_act(rust_strength) var/damage = 500 + rust_strength * 200 take_damage(damage, BRUTE, BOMB, 1) + return TRUE /obj/machinery/vv_edit_var(vname, vval) if(vname == NAMEOF(src, occupant)) diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index ec912feeef54..519b221c735f 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -353,11 +353,15 @@ add_atom_colour(NARSIE_WINDOW_COLOUR, FIXED_COLOUR_PRIORITY) /obj/machinery/door/window/rust_heretic_act() + if(HAS_TRAIT(src, TRAIT_RUSTY)) + return FALSE + add_atom_colour(COLOR_RUSTED_GLASS, FIXED_COLOUR_PRIORITY) AddElement(/datum/element/rust) set_armor(/datum/armor/none) take_damage(get_integrity() * 0.5) modify_max_integrity(initial(max_integrity) * 0.2) + return TRUE /obj/machinery/door/window/should_atmos_process(datum/gas_mixture/air, exposed_temperature) return (exposed_temperature > T0C + (reinf ? 1600 : 800)) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index a429dc021f3e..6b5b4f2746a1 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -955,14 +955,11 @@ GAME_VERB_SRC(/obj/item, verb_pickup, oview(1), "Pick up", null) . = SFX_DESECRATION /// Creates an ignition hotspot if item is lit and located on turf, in mask, or in hand -/obj/item/proc/open_flame(flame_heat=700) +/obj/item/proc/open_flame(flame_heat = 700, mob_slots = ITEM_SLOT_MASK|ITEM_SLOT_HANDS) var/turf/location = loc if(ismob(location)) var/mob/pyromanic = location - var/success = FALSE - if(src == pyromanic.get_item_by_slot(ITEM_SLOT_MASK) || (src in pyromanic.held_items)) - success = TRUE - if(success) + if((pyromanic.get_slot_by_item(src) & mob_slots)) location = get_turf(pyromanic) if(isturf(location)) location.hotspot_expose(flame_heat, 5) diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm index e504b5789ea7..d50904bee027 100644 --- a/code/game/objects/items/stacks/bscrystal.dm +++ b/code/game/objects/items/stacks/bscrystal.dm @@ -38,6 +38,7 @@ return 1 /obj/item/stack/ore/bluespace_crystal/attack_self(mob/user) + SEND_SIGNAL(user, COMSIG_MOB_CRUSHED_BLUESPACE_CRYSTAL, src) user.visible_message(span_warning("[user] crushes [src]!"), span_danger("You crush [src]!")) new /obj/effect/particle_effect/sparks(loc) playsound(loc, SFX_PORTAL_ENTER, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 84ca7cc046cd..4a2090302acf 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -60,6 +60,7 @@ /obj/structure/rust_heretic_act() take_damage(500, BRUTE, "melee", 1) + return TRUE /obj/structure/zap_act(power, zap_flags) if(zap_flags & ZAP_OBJ_DAMAGE) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 25315401caec..73373fd8331f 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -43,7 +43,6 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) var/opened = FALSE var/welded = FALSE var/locked = FALSE - var/large = TRUE var/wall_mounted = 0 //never solid (You can always pass over it) var/breakout_time = 1200 var/message_cooldown @@ -926,7 +925,12 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) if(user in contents) return ITEM_INTERACT_BLOCKING + if(opened && istype(tool, cutting_tool)) // not all of them take welders + if(resistance_flags & INDESTRUCTIBLE) + to_chat(user, span_warning("You can't cut [src] apart!")) + return ITEM_INTERACT_BLOCKING + if(!tool.tool_start_check(user, amount=1)) return ITEM_INTERACT_BLOCKING diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 43a6e7108f93..165232371345 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -138,7 +138,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) /// Checks if the mob can continue to use the mirror /obj/structure/mirror/proc/can_use_mirror(mob/living/carbon/human/user) - return !QDELETED(src) && !QDELETED(user) && user.can_perform_action(src, FORBID_TELEKINESIS_REACH) + return !QDELETED(src) && !QDELETED(user) && !broken && user.can_perform_action(src, FORBID_TELEKINESIS_REACH) /obj/structure/mirror/proc/change_beard(mob/living/carbon/human/beard_dresser) if(beard_dresser.physique == FEMALE) @@ -359,6 +359,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) result_path = /obj/structure/mirror pixel_shift = 28 +/obj/item/wallframe/mirror/heretic + name = /obj/structure/mirror/magic/lesser/heretic::name + desc = /obj/structure/mirror/magic/lesser/heretic::desc + icon = 'icons/obj/watercloset.dmi' + icon_state = "magic_mirror" + result_path = /obj/structure/mirror/magic/lesser/heretic + /obj/item/wallframe/mirror/broken name = "broken mirror" desc = "An unmounted and broken mirror. Attach it to a wall for decor." @@ -427,6 +434,69 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) selectable_races = get_selectable_species().Copy() return ..() +/obj/structure/mirror/magic/lesser/heretic + name = "miraculous mirror" + desc = "Gazing at your reflection, you feel like you could be better." + mirror_options = PRIDE_MIRROR_OPTIONS + +/obj/structure/mirror/magic/lesser/heretic/on_species_change(mob/living/carbon/human/race_changer, datum/species/newrace) + . = ..() + if(race_changer.dna?.species?.type == newrace.type) + return + atom_break(null) + +/obj/structure/mirror/magic/lesser/heretic/atom_deconstruct(disassembled) + new /obj/item/shard(loc) // never gives the frame back + +/obj/structure/mirror/magic/lesser/heretic/welder_act(mob/living/user, obj/item/I) + to_chat(user, span_alert("Trying to find where to start fixing [src] proves nigh impossible.")) + return ITEM_INTERACT_BLOCKING + +/obj/structure/mirror/magic/lesser/heretic/pre_change(mob/living/carbon/human/user, picked) + if(IS_HERETIC(user)) + return TRUE + + to_chat(user, span_hypnophrase("You find yourself gazing at [src], unable to look away...")) + user.Immobilize(5 SECONDS) + addtimer(CALLBACK(src, PROC_REF(change_something), user, picked), 5 SECONDS) + return FALSE + +/obj/structure/mirror/magic/lesser/heretic/proc/change_something(mob/living/carbon/human/user, picked) + if(QDELETED(src) || QDELETED(user)) + return + + if(!user.Adjacent(src)) + to_chat(user, span_warning("Pulled away from [src], you snap out of your trance.")) + return + if(broken) + to_chat(user, span_warning("With the shattering of [src], you snap out of your trance.")) + return + + // randomizes your stuff, doesn't affect DNA though so you could easily get genetics to fix you + switch(picked) + if(CHANGE_HAIR, CHANGE_BEARD) + to_chat(user, span_hypnophrase("The hair - yes, the hair is wrong - [pick("it can be improved", "it needs to be changed", "it must be different")]...")) + user.set_hairstyle(random_hairstyle(user.gender)) + user.set_facial_hairstyle(random_facial_hairstyle(user.gender)) + if(CHANGE_RACE) + to_chat(user, span_hypnophrase("The body - no, the body is all wrong - [pick("I must become anew", "I need to be different", "I want to be something else")]...")) + var/list/options = selectable_races.Copy() - user.dna.species.type + var/datum/species/newrace = GLOB.species_prototypes[pick(options)] + on_species_change(user, newrace) + user.set_species(newrace.type, icon_update = FALSE) + if(CHANGE_SEX) + to_chat(user, span_hypnophrase("My form - yes, the form is all wrong - I need to change it...")) // the wording used here needs to be very deliberate... + var/list/options = list(MALE, FEMALE, PLURAL, NEUTER) - user.gender + user.gender = pick(options) + user.physique = user.gender + if(CHANGE_EYES) + to_chat(user, span_hypnophrase("My eyes - I cannot see clearly - [pick("They're simply not right", "they must be replaced", "they are both wrong")]...")) + user.set_eye_color(random_eye_color()) + if(CHANGE_NAME) // not enabled by default but for badmin support + to_chat(user, span_hypnophrase("My name - it doesn't fit me - [pick("Something else would be better", "it must be changed", "Something more appropriate would be good")]...")) + user.real_name = user.generate_random_mob_name() + user.update_visible_name() + /obj/structure/mirror/magic/badmin race_flags = MIRROR_BADMIN diff --git a/code/game/objects/structures/secure_safe.dm b/code/game/objects/structures/secure_safe.dm index 71c3d0bb481a..239ab4c162ea 100644 --- a/code/game/objects/structures/secure_safe.dm +++ b/code/game/objects/structures/secure_safe.dm @@ -198,3 +198,4 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/secure_safe, 32) /obj/structure/secure_safe/caps_spare/rust_heretic_act() take_damage(damage_amount = 100, damage_type = BRUTE, damage_flag = MELEE, armour_penetration = 100) + return TRUE diff --git a/code/game/turfs/open/floor/fancy_floor.dm b/code/game/turfs/open/floor/fancy_floor.dm index de8572517724..270cfef5db95 100644 --- a/code/game/turfs/open/floor/fancy_floor.dm +++ b/code/game/turfs/open/floor/fancy_floor.dm @@ -166,6 +166,11 @@ /turf/open/floor/grass/proc/spawniconchange() icon_state = "grass[rand(0,3)]" +/turf/open/floor/grass/norandomicon + +/turf/open/floor/grass/norandomicon/spawniconchange() + return + /turf/open/floor/grass/lavaland name = "dead grass patch" desc = "It turns out grass doesn't grow very well in hell." diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 4836e2370213..bb6b51dd90ca 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -634,10 +634,12 @@ GLOBAL_LIST_EMPTY(station_turfs) /// Check if the heretic is strong enough to rust this turf, and if so, rusts the turf with an added visual effect. /turf/rust_heretic_act(rust_strength = RUST_RESISTANCE_BASIC) if((rust_strength < rust_resistance)) - return + return FALSE if (rust_turf(magic = TRUE)) new /obj/effect/glowing_rune(src) + return TRUE + return FALSE /// Override this to change behaviour when being rusted /turf/proc/rust_turf(magic = FALSE) diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index 0be336ff584d..c22ca17e0b33 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -194,7 +194,7 @@ Striking a noncultist, however, will tear their flesh."} // Void PATH_VOID = list( WIELDER_SPELLS = list(/datum/action/cooldown/spell/pointed/void_phase), - SWORD_SPELLS = list(/datum/action/cooldown/spell/pointed/void_prison), + SWORD_SPELLS = list(/datum/action/cooldown/spell/aoe/void_pull), SWORD_PREFIX = "tenebrous", ), // Blade @@ -377,8 +377,6 @@ Striking a noncultist, however, will tear their flesh."} /obj/item/melee/cultblade/haunted/Initialize(mapload, mob/soul_to_bind, mob/awakener, do_bind = TRUE) . = ..() - - AddElement(/datum/element/heretic_focus) add_traits(list(TRAIT_CASTABLE_LOC, TRAIT_SPELLS_TRANSFER_TO_LOC), INNATE_TRAIT) if(do_bind && !mapload) bind_soul(soul_to_bind, awakener) @@ -425,10 +423,6 @@ Striking a noncultist, however, will tear their flesh."} bozo.objectives |= copied_objectives trapped_entity.mind.add_antag_datum(bozo) - // Assigning the spells to give to the wielder and spirit. - // Let them cast the given spell. - ADD_TRAIT(trapped_entity, TRAIT_ALLOW_HERETIC_CASTING, INNATE_TRAIT) - var/list/path_spells = heretic_paths_to_haunted_sword_abilities[heretic_path] var/list/wielder_spells = path_spells[WIELDER_SPELLS] diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm index 9f302e0ca435..4e9ef0b4f81e 100644 --- a/code/modules/antagonists/heretic/heretic_antag.dm +++ b/code/modules/antagonists/heretic/heretic_antag.dm @@ -159,12 +159,22 @@ var/list/knowledge_info = researched_knowledge[knowledge] if(islist(knowledge_info)) var/datum/heretic_knowledge/knowledge_instance = knowledge_info[HKT_INSTANCE] - knowledge_data["desc"] = knowledge_instance.desc + knowledge_data["info"] = knowledge_instance.transmute_text + knowledge_data["notice"] = knowledge_instance.notice + else knowledge_data["desc"] = initial(knowledge.desc) - return knowledge_data + knowledge_data["info"] = initial(knowledge.transmute_text) + knowledge_data["notice"] = initial(knowledge.notice) + + if(ispath(knowledge, /datum/heretic_knowledge/ultimate)) + var/ascension_check = can_ascend() + if(ascension_check != HERETIC_CAN_ASCEND) + knowledge_data["disabled"] = TRUE + knowledge_data["notice"] += "
[ascension_check]" + return knowledge_data /datum/antagonist/heretic/ui_interact(mob/user, datum/tgui/ui) . = ..() @@ -215,15 +225,6 @@ if(!(knowledge_info[HKT_ID] in researchable_knowledges)) continue var/list/knowledge_data = get_knowledge_data(knowledge_path, heretic_tree, FALSE) - - // Final knowledge can't be learned until all objectives are complete. - if(ispath(knowledge_path, /datum/heretic_knowledge/ultimate)) - var/ascension_check = can_ascend() - if(ascension_check != HERETIC_CAN_ASCEND) - knowledge_data["disabled"] = TRUE - knowledge_data["tooltip"] = ascension_check - - var/depth = knowledge_data[HKT_DEPTH] while(depth > length(tree_data)) @@ -385,7 +386,6 @@ ADD_TRAIT(our_mob, TRAIT_MANSUS_TOUCHED, REF(src)) RegisterSignal(our_mob, COMSIG_LIVING_CULT_SACRIFICED, PROC_REF(on_cult_sacrificed)) - RegisterSignals(our_mob, list(COMSIG_MOB_BEFORE_SPELL_CAST, COMSIG_MOB_SPELL_ACTIVATED), PROC_REF(on_spell_cast)) RegisterSignal(our_mob, COMSIG_USER_ITEM_INTERACTION, PROC_REF(on_item_use)) RegisterSignal(our_mob, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(after_fully_healed)) RegisterSignal(our_mob, COMSIG_ATOM_EXAMINE, PROC_REF(on_heretic_examine)) @@ -411,8 +411,6 @@ UnregisterSignal( our_mob, list( - COMSIG_MOB_BEFORE_SPELL_CAST, - COMSIG_MOB_SPELL_ACTIVATED, COMSIG_USER_ITEM_INTERACTION, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_LIVING_CULT_SACRIFICED, @@ -480,31 +478,6 @@ knowledge.on_lose(old_body, src) knowledge.on_gain(new_body, src) -/* - * Signal proc for [COMSIG_MOB_BEFORE_SPELL_CAST] and [COMSIG_MOB_SPELL_ACTIVATED]. - * - * Checks if our heretic has [TRAIT_ALLOW_HERETIC_CASTING] or is ascended. - * If so, allow them to cast like normal. - * If not, cancel the cast, and returns [SPELL_CANCEL_CAST]. - */ -/datum/antagonist/heretic/proc/on_spell_cast(mob/living/source, datum/action/cooldown/spell/spell) - SIGNAL_HANDLER - - // Heretic spells are of the forbidden school, otherwise we don't care - if(spell.school != SCHOOL_FORBIDDEN) - return - - // If we've got the trait, we don't care - if(HAS_TRAIT(source, TRAIT_ALLOW_HERETIC_CASTING)) - return - // All powerful, don't care - if(ascended) - return - - // We shouldn't be able to cast this! Cancel it. - source.balloon_alert(source, "you need a focus!") - return SPELL_CANCEL_CAST - /* * Signal proc for [COMSIG_USER_ITEM_INTERACTION]. * @@ -785,7 +758,7 @@ /datum/antagonist/heretic/proc/passive_influence_gain() adjust_knowledge_points(1) if(!IS_UNCONSCIOUS(owner?.current)) - to_chat(owner.current, "[span_hear("You hear a whisper...")] [span_hypnophrase(pick_list(HERETIC_INFLUENCE_FILE, "drain_message"))]") + to_chat(owner.current, "[span_hear("You hear a whisper...")] [span_mansus(pick_list(HERETIC_INFLUENCE_FILE, "drain_message"))]") addtimer(CALLBACK(src, PROC_REF(passive_influence_gain)), passive_gain_timer) /datum/antagonist/heretic/proc/adjust_knowledge_points(amount, update = TRUE) @@ -932,7 +905,7 @@ var/mob/living/pawn = owner.current pawn.equip_to_slot_if_possible(new /obj/item/clothing/neck/heretic_focus(get_turf(pawn)), ITEM_SLOT_NECK, TRUE, TRUE) - to_chat(pawn, span_hypnophrase("The Mansus has manifested you a focus.")) + to_chat(pawn, span_mansus("The Mansus has manifested you a focus.")) /datum/antagonist/heretic/antag_panel_data() var/list/string_of_knowledge = list() @@ -1028,6 +1001,26 @@ researchable_knowledge -= banned_knowledge return researchable_knowledge +/** + * Get a list of all knowledge datums that we've researched. + */ +/datum/antagonist/heretic/proc/get_researched_knowledge() + var/list/knowledge_list = list() + for(var/knowledge_type in researched_knowledge) + knowledge_list += researched_knowledge[knowledge_type][HKT_INSTANCE] + return knowledge_list + +/** + * Get a list of all knowledge datums that we've researched in a specific category. + */ +/datum/antagonist/heretic/proc/get_researched_knowledge_by_category(category_type) + var/list/knowledge_list = list() + for(var/knowledge_type in researched_knowledge) + if(researched_knowledge[knowledge_type][HKT_CATEGORY] == category_type) + knowledge_list += researched_knowledge[knowledge_type][HKT_INSTANCE] + return knowledge_list + + /** * Check if the wanted type-path is in the list of research knowledge. */ @@ -1058,9 +1051,9 @@ var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_path][HKT_INSTANCE] if(!knowledge.can_be_invoked(src)) continue - rituals[knowledge.name] = knowledge + rituals += knowledge - return sortTim(rituals, GLOBAL_PROC_REF(cmp_heretic_knowledge), associative = TRUE) + return sortTim(rituals, GLOBAL_PROC_REF(cmp_heretic_knowledge)) /** * Checks to see if our heretic can ccurrently ascend. @@ -1071,7 +1064,7 @@ if(feast_of_owls) return "The owls have taken your right of ascension (denied ascension)." // We sold our ambition for immediate power :/ if(!can_assign_self_objectives) - return "The mansus has spurned you (denied ascension)." + return "The Mansus has spurned you (denied ascension)." for(var/datum/objective/must_be_done as anything in objectives) if(!must_be_done.check_completion()) return "Must complete all objectives before ascending." diff --git a/code/modules/antagonists/heretic/heretic_curses.dm b/code/modules/antagonists/heretic/heretic_curses.dm index 1893bc725587..3d05ff097f46 100644 --- a/code/modules/antagonists/heretic/heretic_curses.dm +++ b/code/modules/antagonists/heretic/heretic_curses.dm @@ -80,7 +80,7 @@ log_combat(user, to_curse, "cursed via heretic ritual", addition = "([name])") var/obj/item/codex_cicatrix/morbus/cursed_book = locate() in selected_atoms curse(to_curse, cursed_book) - to_chat(user, span_hierophant("You cast a [name] upon [to_curse.real_name].")) + to_chat(user, span_mansus("You cast a [name] upon [to_curse.real_name].")) fingerprints = null blood_samples = null @@ -136,9 +136,10 @@ /datum/heretic_knowledge/curse/paralysis abstract_type = /datum/heretic_knowledge/curse/paralysis name = "Curse of Paralysis" - desc = "Allows you to transmute a hatchet and both a left and right leg to cast a curse of immobility on a crew member. \ + desc = "Casts a curse of immobility on a crew member.
\ While cursed, the victim will be unable to walk. You can additionally supply an item that a victim has touched \ or is covered in the victim's blood to make the curse last longer." + transmute_text = "Transmute a hatchet, and both a left and right leg." gain_text = "The flesh of humanity is weak. Make them bleed. Show them their fragility." duration = 5 MINUTES @@ -171,9 +172,10 @@ /datum/heretic_knowledge/curse/corrosion abstract_type = /datum/heretic_knowledge/curse/corrosion name = "Curse of Corrosion" - desc = "Allows you to transmute wirecutters, a pool of vomit, and a heart to cast a curse of sickness on a crew member. \ + desc = "Cast a curse of sickness on a crew member.
\ While cursed, the victim will repeatedly vomit while their organs will take constant damage. You can additionally supply an item \ that a victim has touched or is covered in the victim's blood to make the curse last longer." + transmute_text = "Transmute a pair of wirecutters, a pool of vomit, and a heart." gain_text = "The body of humanity is temporary. Their weaknesses cannot be stopped, like iron falling to rust. Show them all." duration = 3 MINUTES diff --git a/code/modules/antagonists/heretic/heretic_focus.dm b/code/modules/antagonists/heretic/heretic_focus.dm deleted file mode 100644 index 45bbf743b8cd..000000000000 --- a/code/modules/antagonists/heretic/heretic_focus.dm +++ /dev/null @@ -1,61 +0,0 @@ -/// Heretic focus element, simple element for making an item a heretic focus, -/// allowing heretics to cast advanced spells (examine message included). -/datum/element/heretic_focus - -/datum/element/heretic_focus/Attach(datum/target) - . = ..() - if(!isitem(target)) - return ELEMENT_INCOMPATIBLE - - RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) - RegisterSignal(target, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip)) - RegisterSignal(target, COMSIG_ITEM_DROPPED, PROC_REF(on_drop)) - - var/obj/item/item_target = target - // If our loc is a mob, it's possible we already have it equippied - if(ismob(item_target.loc)) - var/mob/wearer = item_target.loc - if(!item_target.slot_flags || wearer.get_item_by_slot(item_target.slot_flags) == item_target) - ADD_TRAIT(wearer, TRAIT_ALLOW_HERETIC_CASTING, ELEMENT_TRAIT(target)) - -/datum/element/heretic_focus/Detach(obj/item/source) - . = ..() - UnregisterSignal(source, list(COMSIG_ATOM_EXAMINE, COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED)) - if(isliving(source.loc)) - REMOVE_TRAIT(source.loc, TRAIT_ALLOW_HERETIC_CASTING, ELEMENT_TRAIT(source)) - -/** - * Signal proc for [COMSIG_ATOM_EXAMINE]. - * Let's the examiner see that this item is a heretic focus - */ -/datum/element/heretic_focus/proc/on_examine(obj/item/source, mob/user, list/examine_list) - SIGNAL_HANDLER - - if(!IS_HERETIC(user)) - return - - examine_list += span_notice("Allows you to cast advanced heretic spells when worn.") - -/** - * Signal proc for [COMSIG_ITEM_EQUIPPED]. - * When equipped in a right slot, give user our trait - */ -/datum/element/heretic_focus/proc/on_equip(obj/item/source, mob/user, slot) - SIGNAL_HANDLER - - if(!IS_HERETIC(user)) - return - - if(source.slot_flags && !(source.slot_flags & slot)) - return - - ADD_TRAIT(user, TRAIT_ALLOW_HERETIC_CASTING, ELEMENT_TRAIT(source)) - -/** - * Signal proc for [COMSIG_ITEM_DROPPED]. - * Remove our trait when we drop (unequip) our item - */ -/datum/element/heretic_focus/proc/on_drop(obj/item/source, mob/user) - SIGNAL_HANDLER - - REMOVE_TRAIT(user, TRAIT_ALLOW_HERETIC_CASTING, ELEMENT_TRAIT(source)) diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 4fa24861d476..8c0f55b6b568 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -15,6 +15,10 @@ var/name = "Basic knowledge" /// Description of the knowledge, shown to the heretic. Describes what it unlocks / does. var/desc = "Basic knowledge of forbidden arts." + /// Text describing how you create the thing + var/transmute_text = "" + /// Big red notices about the knowledge + var/notice = "" /// What's shown to the heretic when the knowledge is acquired var/gain_text /// Assoc list of [typepaths we need] to [amount needed]. @@ -75,6 +79,7 @@ on_gain(user, our_heretic) if(is_final_knowledge && !our_heretic.unlimited_blades) our_heretic.disable_blade_breaking() + SEND_SIGNAL(our_heretic, COMSIG_HERETIC_RESEARCHED_KNOWLEDGE, src) /** * Called when the knowledge is applied to a mob. @@ -128,6 +133,12 @@ /datum/heretic_knowledge/proc/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) return TRUE +/// Used in unit testing to prepare the components for the test +/datum/heretic_knowledge/proc/prepare_atom_for_ritual_test(atom/what) + if(isitem(what)) + var/obj/item/item = what + item.item_flags &= ~ABSTRACT + /** * Parses specific items into a more readble form. * Can be overriden by knoweldge subtypes. @@ -135,10 +146,11 @@ /datum/heretic_knowledge/proc/parse_required_item(atom/item_path, number_of_things) // If we need a human, there is a high likelihood we actually need a (dead) body if(ispath(item_path, /mob/living/carbon/human)) - return "bod[number_of_things > 1 ? "ies" : "y"]" + return "[number_of_things] bod[number_of_things > 1 ? "ies" : "y"]" if(ispath(item_path, /mob/living)) - return "carcass[number_of_things > 1 ? "es" : ""] of any kind" - return "[initial(item_path.name)]\s" + return "[number_of_things] carcass[number_of_things > 1 ? "es" : ""] of any kind" + return "[number_of_things] [initial(item_path.name)]\s" + /** * Called whenever the knowledge's associated ritual is completed successfully. * @@ -193,9 +205,31 @@ /datum/heretic_knowledge/spell abstract_type = /datum/heretic_knowledge/spell /// Spell path we add to the heretic. Type-path. - var/datum/action/action_to_add + var/datum/action/cooldown/spell/action_to_add /// The spell we actually created. - var/datum/weakref/created_action_ref + VAR_FINAL/datum/action/cooldown/spell/created_action_ref + /// Used to display charges on the spell's action button, if applicable. + VAR_FINAL/mutable_appearance/charge_maptext + /// Charge count + VAR_FINAL/charges = 1 + /// Max amount of charges before a heretic needs to recharge by doing the ritual again + var/max_charges = 1 + /// Percent of max charges restored on a successful ritual + var/recharge_amount = 1.0 + /// Percent of max charges restored on completing the path passive + var/path_recharge_amount = 0.5 + /// What percent of the max charges the spell regains periodically while wearing a focus + var/focus_recharge_amount = 0.0 + /// What percent of the max charges the spell loses periodically from consuming holy water + var/holywater_drain_amount = 0.0 + /// When recharged via path passive, if TRUE, we bypass the max_charges cap. + var/path_recharge_can_surpass_cap = FALSE + +/datum/heretic_knowledge/spell/New() + . = ..() + charges = max_charges + if(max_charges != INFINITY) + desc += "
Has [max_charges] charge\s[transmute_text ? ", after which you must recharge the spell" : ""]." /datum/heretic_knowledge/spell/Destroy() QDEL_NULL(created_action_ref) @@ -205,14 +239,144 @@ // Added spells are tracked on the body, and not the mind, // because we handle heretic mind transfers // via the antag datum (on_gain and on_lose). - var/datum/action/created_action = created_action_ref?.resolve() || new action_to_add(user) - created_action.Grant(user) - created_action_ref = WEAKREF(created_action) + created_action_ref ||= new action_to_add(src) + created_action_ref.Grant(user) + + RegisterSignal(created_action_ref, COMSIG_ACTION_STATUS_UPDATE, PROC_REF(action_update)) + RegisterSignal(created_action_ref, COMSIG_QDELETING, PROC_REF(action_delete)) + RegisterSignal(created_action_ref, COMSIG_SPELL_CAN_CAST_CHECK, PROC_REF(spell_check)) + + RegisterSignals(user, list(COMSIG_MOB_BEFORE_SPELL_CAST, COMSIG_MOB_SPELL_ACTIVATED), PROC_REF(check_charges)) + if(istype(created_action_ref, /datum/action/cooldown/spell/pointed/projectile)) + RegisterSignal(user, COMSIG_MOB_SPELL_PROJECTILE, PROC_REF(deduct_charge)) + else if(istype(created_action_ref, /datum/action/cooldown/spell/touch)) + RegisterSignal(user, COMSIG_SPELL_TOUCH_SPELL_ACTUALLY_CAST, PROC_REF(deduct_charge)) + else + RegisterSignal(user, COMSIG_MOB_AFTER_SPELL_CAST, PROC_REF(deduct_charge)) + + update_charge_counter() /datum/heretic_knowledge/spell/on_lose(mob/user, datum/antagonist/heretic/our_heretic) - var/datum/action/cooldown/spell/created_action = created_action_ref?.resolve() - if(created_action?.owner == user) - created_action.Remove(user) + if(created_action_ref?.owner == user) + created_action_ref.Remove(user) + + UnregisterSignal(user, list( + COMSIG_SPELL_CAN_CAST_CHECK, + COMSIG_MOB_SPELL_ACTIVATED, + COMSIG_MOB_AFTER_SPELL_CAST, + COMSIG_MOB_BEFORE_SPELL_CAST, + COMSIG_MOB_SPELL_PROJECTILE, + COMSIG_SPELL_TOUCH_SPELL_ACTUALLY_CAST, + )) + +/datum/heretic_knowledge/spell/can_be_invoked(datum/antagonist/heretic/invoker) + if(!LAZYLEN(required_atoms)) + return FALSE + if(created_action_ref?.owner != invoker.owner?.current) + return FALSE + if(charges >= max_charges) + return FALSE + return TRUE + +/datum/heretic_knowledge/spell/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) + add_charges(ceil(max_charges * recharge_amount)) + return TRUE + +/datum/heretic_knowledge/spell/proc/action_update(datum/action/source, atom/movable/screen/movable/action_button/button, ...) + SIGNAL_HANDLER + + if(charge_maptext) + button.cut_overlay(charge_maptext) + + if(charges >= 100) + return + if(source.owner) + var/datum/antagonist/heretic/our_heretic = GET_HERETIC(source.owner) + if(our_heretic?.ascended) + return + + charge_maptext ||= new() + charge_maptext.maptext_x = 4 + charge_maptext.maptext_y = 20 + charge_maptext.maptext = MAPTEXT("[charges]") + button.add_overlay(charge_maptext) + +/datum/heretic_knowledge/spell/proc/action_delete(datum/action/source) + SIGNAL_HANDLER + created_action_ref = null // shouldn't happen... + +/datum/heretic_knowledge/spell/proc/spell_check(datum/action/the_spell, feedback) + SIGNAL_HANDLER + + if(the_spell != created_action_ref || isnull(the_spell.owner)) + return NONE + if(charges > 0) + return NONE + var/datum/antagonist/heretic/our_heretic = GET_HERETIC(the_spell.owner) + if(our_heretic?.ascended) + return NONE + + if(feedback) + to_chat(the_spell.owner, span_mansus("You don't have enough charges to cast this spell!")) + return SPELL_CANCEL_CAST + +/datum/heretic_knowledge/spell/proc/check_charges(mob/living/source, datum/action/cooldown/the_spell) + SIGNAL_HANDLER + + if(the_spell != created_action_ref) + return NONE + if(charges > 0) + return NONE + var/datum/antagonist/heretic/our_heretic = GET_HERETIC(source) + if(our_heretic?.ascended) + return NONE + + to_chat(source, span_mansus("You don't have enough charges to cast this spell! [transmute_text]")) + return SPELL_CANCEL_CAST + +/datum/heretic_knowledge/spell/proc/deduct_charge(mob/living/source, datum/action/cooldown/the_spell) + SIGNAL_HANDLER + + if(the_spell != created_action_ref) + return + var/datum/antagonist/heretic/our_heretic = GET_HERETIC(source) + if(our_heretic?.ascended) + return + + remove_charges(1) + +/// Add a number of charges, optionally bypassing the cap +/datum/heretic_knowledge/spell/proc/add_charges(num, uncapped = FALSE) + if(num <= 0) + return FALSE + + var/pre_charge_value = charges + if(uncapped) + charges += num + else + charges = min(charges + num, max_charges) + update_charge_counter() + return charges != pre_charge_value + +/// Remove a number of charges (down to 0) +/datum/heretic_knowledge/spell/proc/remove_charges(num) + if(num <= 0) + return FALSE + + var/pre_charge_value = charges + charges = max(charges - num, 0) + update_charge_counter() + if(charges <= 0 && created_action_ref.owner?.click_intercept == created_action_ref) + created_action_ref.unset_click_ability(created_action_ref.owner, refund_cooldown = FALSE) + return charges != pre_charge_value + +/datum/heretic_knowledge/spell/proc/update_charge_counter() + created_action_ref?.build_all_button_icons(UPDATE_BUTTON_STATUS) + +/datum/heretic_knowledge/spell/vv_edit_var(var_name, var_value) + . = ..() + if(var_name == NAMEOF(src, charges) || var_name == NAMEOF(src, max_charges)) + update_charge_counter() /** * A knowledge subtype for knowledge that can only @@ -406,7 +570,7 @@ * Overridable proc that invokes special effects * whenever the heretic clicks on someone at range with their heretic blade. */ -/datum/heretic_knowledge/blade_upgrade/proc/do_ranged_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade) +/datum/heretic_knowledge/blade_upgrade/proc/do_ranged_effects(mob/living/source, atom/target, obj/item/melee/sickly_blade/blade) return /** @@ -461,6 +625,8 @@ user.log_message("created a [summoned.name], controlled by [key_name(chosen_one)].", LOG_GAME) message_admins("[ADMIN_LOOKUPFLW(user)] created a [summoned.name], [ADMIN_LOOKUPFLW(summoned)].") + SEND_SIGNAL(user, COMSIG_HERETIC_SUMMONED_MOB, summoned) + var/datum/antagonist/heretic_monster/heretic_monster = summoned.mind.add_antag_datum(/datum/antagonist/heretic_monster) heretic_monster.set_owner(user.mind) @@ -478,6 +644,7 @@ /datum/heretic_knowledge/knowledge_ritual name = "Ritual of Knowledge" desc = "A randomly generated transmutation ritual that rewards knowledge points and can only be completed once." + notice = "This can only be completed once." gain_text = "Everything can be a key to unlocking the secrets behind the Gates. I must be wary and wise." abstract_type = /datum/heretic_knowledge/knowledge_ritual cost = 1 @@ -532,15 +699,16 @@ var/list/requirements_string = list() - to_chat(user, span_hierophant("The [name] requires the following:")) + to_chat(user, span_mansus("The [name] requires the following:")) for(var/obj/item/path as anything in required_atoms) var/amount_needed = required_atoms[path] to_chat(user, span_hypnophrase("[amount_needed] [initial(path.name)]\s...")) requirements_string += "[amount_needed == 1 ? "":"[amount_needed] "][initial(path.name)]\s" - to_chat(user, span_hierophant("Completing it will reward you [KNOWLEDGE_RITUAL_POINTS] knowledge points. You can check the knowledge in your Researched Knowledge to be reminded.")) + to_chat(user, span_mansus("Completing it will reward you [KNOWLEDGE_RITUAL_POINTS] knowledge points. You can check the knowledge in your Researched Knowledge to be reminded.")) - desc = "Allows you to transmute [english_list(requirements_string)] for [KNOWLEDGE_RITUAL_POINTS] bonus knowledge points. This can only be completed once." + transmute_text = "Transmute [english_list(requirements_string)]." + desc = "Rewards you with [KNOWLEDGE_RITUAL_POINTS] bonus knowledge points." /datum/heretic_knowledge/knowledge_ritual/can_be_invoked(datum/antagonist/heretic/invoker) return !was_completed diff --git a/code/modules/antagonists/heretic/heretic_living_heart.dm b/code/modules/antagonists/heretic/heretic_living_heart.dm index 78256b12a635..3a8caa822b9c 100644 --- a/code/modules/antagonists/heretic/heretic_living_heart.dm +++ b/code/modules/antagonists/heretic/heretic_living_heart.dm @@ -174,7 +174,7 @@ if(ismob(tracked_thing)) var/mob/tracked_mob = tracked_thing if(tracked_mob.stat == DEAD) - to_chat(owner, span_hierophant("[tracked_mob] is dead. Bring them to a transmutation rune \ + to_chat(owner, span_mansus("[tracked_mob] is dead. Bring them to a transmutation rune \ and invoke \"[sac_knowledge.name]\" to sacrifice them!")) StartCooldown() diff --git a/code/modules/antagonists/heretic/influences.dm b/code/modules/antagonists/heretic/influences.dm index 882d3c593e34..1292f8fc3eeb 100644 --- a/code/modules/antagonists/heretic/influences.dm +++ b/code/modules/antagonists/heretic/influences.dm @@ -249,6 +249,7 @@ var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user) heretic_datum.adjust_knowledge_points(knowledge_to_gain) + SEND_SIGNAL(heretic_datum, COMSIG_HERETIC_INFLUENCE_DRAINED) // Aaand now we delete it after_drain(user) diff --git a/code/modules/antagonists/heretic/items/forbidden_book.dm b/code/modules/antagonists/heretic/items/forbidden_book.dm index 35039d3e88f4..e31789312c92 100644 --- a/code/modules/antagonists/heretic/items/forbidden_book.dm +++ b/code/modules/antagonists/heretic/items/forbidden_book.dm @@ -2,7 +2,7 @@ /obj/item/codex_cicatrix name = "Codex Cicatrix" desc = "This heavy tome is full of cryptic scribbles and impossible diagrams. \ - According to legend, it can be deciphered to reveal the secrets of the veil between worlds." + According to legend, it can be deciphered to reveal the secrets of the veil between worlds." icon = 'icons/obj/antags/eldritch.dmi' base_icon_state = "book" icon_state = "book" @@ -11,9 +11,10 @@ /// Helps determine the icon state of this item when it's used on self. var/book_open = FALSE /// How fast we can drain influences - var/drain_speed = 5 SECONDS + var/drain_speed = 7.5 SECONDS /// How fast we can draw runes - var/draw_speed = 8 SECONDS + var/draw_speed = 15 SECONDS + /obj/item/codex_cicatrix/Initialize(mapload) . = ..() @@ -34,21 +35,23 @@ . += span_notice("Can be used to tap influences for additional knowledge points.") . += span_notice("Can also be used to draw or remove transmutation runes with ease.") - . += span_notice("Additionally, it can work as a focus for your spells when held.") /obj/item/codex_cicatrix/attack_self(mob/user, modifiers) . = ..() if(.) return - if(book_open) - close_animation() - RemoveElement(/datum/element/heretic_focus) - update_weight_class(WEIGHT_CLASS_SMALL) - else - open_animation() - AddElement(/datum/element/heretic_focus) - update_weight_class(WEIGHT_CLASS_NORMAL) + open_animation() + update_weight_class(WEIGHT_CLASS_NORMAL) + addtimer(CALLBACK(src, PROC_REF(close_book)), 30 SECONDS) + + var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user) + var/datum/heretic_knowledge/spell/basic/grasp_knowledge = heretic_datum.get_knowledge(__IMPLIED_TYPE__) + grasp_knowledge?.add_charges(ceil(grasp_knowledge.max_charges * 0.5)) + +/obj/item/codex_cicatrix/proc/close_book() + close_animation() + update_weight_class(WEIGHT_CLASS_SMALL) /obj/item/codex_cicatrix/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user) @@ -79,8 +82,8 @@ desc = "A hideous, ragged book covered in separately-blinking eyes, all of them staring at you. You have no idea how to hold this thing, and to be honest you're not sure if you want to." base_icon_state = "book_morbus" icon_state = "book_morbus" - drain_speed = 2.5 SECONDS - draw_speed = 5 SECONDS + drain_speed = 5 SECONDS + draw_speed = 10 SECONDS /// List of mobs we've cursed with transmutation. When the codex is destroyed all those curses become undone var/list/transmuted_victims = list() diff --git a/code/modules/antagonists/heretic/items/heretic_armor.dm b/code/modules/antagonists/heretic/items/heretic_armor.dm index c94d7bf94069..a91199fdae78 100644 --- a/code/modules/antagonists/heretic/items/heretic_armor.dm +++ b/code/modules/antagonists/heretic/items/heretic_armor.dm @@ -15,7 +15,7 @@ transparent_protection = HIDEGLOVES | HIDESUITSTORAGE | HIDEJUMPSUIT | HIDESHOES | HIDENECK cold_protection = FULL_BODY min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - allowed = list(/obj/item/melee/sickly_blade, /obj/item/gun/ballistic/rifle/lionhunter) + allowed = list(/obj/item/melee/sickly_blade, /obj/item/gun/ballistic/rifle/lionhunter, /obj/item/flashlight/lantern/heretic) hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch armor_type = /datum/armor/eldritch_armor clothing_traits = list(TRAIT_HERETIC_AURA_HIDDEN) @@ -57,16 +57,6 @@ /obj/item/clothing/suit/hooded/cultrobes/eldritch/on_hood_down(obj/item/clothing/head/hooded/hood) hood_up = FALSE -/obj/item/clothing/suit/hooded/cultrobes/eldritch/examine(mob/user) - . = ..() - if(!IS_HERETIC(user)) - return - if(hood_up) - return - - // Our hood gains the heretic_focus element. - . += span_notice("Allows you to cast heretic spells while the hood is up.") - /obj/item/clothing/head/hooded/cult_hoodie/eldritch name = "ominous hood" icon = 'icons/obj/clothing/head/helmet.dmi' @@ -80,10 +70,6 @@ clothing_flags = THICKMATERIAL | PLASMAMAN_PREVENT_IGNITION | SNUG_FIT armor_type = /datum/armor/eldritch_armor -/obj/item/clothing/head/hooded/cult_hoodie/eldritch/Initialize(mapload) - . = ..() - AddElement(/datum/element/heretic_focus) - /datum/armor/eldritch_armor melee = 50 bullet = 50 @@ -1220,7 +1206,6 @@ /// Makes our cloak "invisible". Not the wearer, the cloak itself. /obj/item/clothing/suit/hooded/cultrobes/void/proc/make_invisible() add_traits(list(TRAIT_NO_STRIP, TRAIT_EXAMINE_SKIP), REF(src)) - RemoveElement(/datum/element/heretic_focus) flags_cover |= ALLOW_SURGERY_THROUGH if(isliving(loc)) @@ -1232,7 +1217,6 @@ /// Makes our cloak "visible" again. /obj/item/clothing/suit/hooded/cultrobes/void/proc/make_visible() remove_traits(list(TRAIT_NO_STRIP, TRAIT_EXAMINE_SKIP), REF(src)) - AddElement(/datum/element/heretic_focus) flags_cover &= ~ALLOW_SURGERY_THROUGH if(isliving(loc)) diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm index 30433933ec84..614a91ed0228 100644 --- a/code/modules/antagonists/heretic/items/heretic_blades.dm +++ b/code/modules/antagonists/heretic/items/heretic_blades.dm @@ -50,6 +50,8 @@ var/mob/living/carbon/human/human_user = user human_user.AdjustParalyzed(5 SECONDS) return TRUE + if(SEND_SIGNAL(user, COMSIG_HERETIC_BLADE_PREATTACK, target, src) & COMPONENT_CANCEL_ATTACK_CHAIN) + return TRUE return . diff --git a/code/modules/antagonists/heretic/items/heretic_grenade.dm b/code/modules/antagonists/heretic/items/heretic_grenade.dm index 4cae667299da..aa62156aa3c6 100644 --- a/code/modules/antagonists/heretic/items/heretic_grenade.dm +++ b/code/modules/antagonists/heretic/items/heretic_grenade.dm @@ -4,8 +4,8 @@ */ /obj/item/grenade/chem_grenade/rust_sower - name = "\improper Rust sower" - desc = "A nifty little thing that explodes into rust. Causes borgs and mechs to get utterly obliterated" + name = "rust sower" + desc = "A nifty little thing that explodes into rust. Causes borgs and mechs to get utterly obliterated." possible_fuse_time = list("5") stage = GRENADE_READY base_icon_state = "rustgrenade" diff --git a/code/modules/antagonists/heretic/items/heretic_lantern.dm b/code/modules/antagonists/heretic/items/heretic_lantern.dm new file mode 100644 index 000000000000..780bc7757266 --- /dev/null +++ b/code/modules/antagonists/heretic/items/heretic_lantern.dm @@ -0,0 +1,185 @@ +/obj/item/flashlight/lantern/heretic + name = "burning lantern" + desc = "A strange lantern that hurts your eyes to look at, even when it's quelled." + light_power = 3 + light_color = "#e8ff66" + heat = 2400 + w_class = WEIGHT_CLASS_NORMAL + ignore_base_color = TRUE + color = list( + 0, 1, 0, 0, + 0, 1, 1, 0, + 0, 1.5, 0, 0, + 0, 0, 0, 0.5, + 0, -0.25, -0.25, 0, + ) + /// Lazylist of world.time values for when the next pulse can affect a given mob. Indexed by REF(mob). + var/list/effect_cds + /// Lazylist of how many active pulses are currently affecting a given mob. Indexed by REF(mob). + var/list/tick_counts + /// Lighting middleman, lets us do a flicker effect + var/datum/light_middleman/middleman + +/obj/item/flashlight/lantern/heretic/Initialize(mapload) + . = ..() + if(IS_OVERLAY_LIGHT_SYSTEM(light_system)) + middleman = new(src, "lantern") + RegisterSignal(middleman, COMSIG_LIGHT_MIDDLEMAN_UPDATED, PROC_REF(light_updated)) + middleman.being_overriding_light() + +/obj/item/flashlight/lantern/heretic/init_slapcrafting() + return + +/obj/item/flashlight/lantern/heretic/set_light_on(new_value) + . = ..() + if(isnull(.)) + return + if(new_value) + START_PROCESSING(SSobj, src) + add_filter("lantern_pulse", 1, outline_filter(color = "#0b8000", size = 1)) + apply_wibbly_filters(src) + update_weight_class(WEIGHT_CLASS_BULKY) + var/atom/message_loc = isturf(loc) ? src : loc + message_loc.visible_message( + span_notice("[isturf(loc) ? src : "[loc]'s [src]"] flickers to life, casting eerie shadows around it.") + ) + else + STOP_PROCESSING(SSobj, src) + remove_filter("lantern_pulse", 1) + remove_wibbly_filters(src) + update_weight_class(WEIGHT_CLASS_NORMAL) + var/atom/message_loc = isturf(loc) ? src : loc + message_loc.visible_message( + span_notice("[isturf(loc) ? src : "[loc]'s [src]"] flickers out, the shadows retreating.") + ) + +/obj/item/flashlight/lantern/heretic/equipped(mob/user, slot, initial) + . = ..() + if(!light_on) + return + remove_wibbly_filters(src) + apply_wibbly_filters(src) + +/obj/item/flashlight/lantern/heretic/dropped(mob/user, silent) + . = ..() + if(!light_on) + return + remove_wibbly_filters(src) + apply_wibbly_filters(src) + +/obj/item/flashlight/lantern/heretic/Destroy() + STOP_PROCESSING(SSobj, src) + QDEL_NULL(middleman) + return ..() + +/obj/item/flashlight/lantern/heretic/proc/light_updated(datum/source) + SIGNAL_HANDLER + fire_flicker_middleman(middleman) + +/obj/item/flashlight/lantern/heretic/process(seconds_per_tick) + if(!isturf(loc) && !ismob(loc)) + return + + open_flame(heat, slot_flags|ITEM_SLOT_HANDS|ITEM_SLOT_SUITSTORE) + effect_pulse(isturf(loc) ? src : loc, seconds_per_tick) + +/obj/item/flashlight/lantern/heretic/proc/effect_pulse(atom/center = src, seconds_per_tick) + var/list/affected_refs = list() + for(var/mob/living/seer in viewers(light_range - 1, center)) + try_effect_pulse(seer, center) + affected_refs += REF(seer) + // if you are unaffected, tick count goes down by 1 + for(var/tick_ref in SANITIZE_LIST(tick_counts) - affected_refs) + LAZYSET(tick_counts, tick_ref, LAZYACCESS(tick_counts, tick_ref) - 1) + if(LAZYACCESS(tick_counts, tick_ref) <= 0) + LAZYREMOVE(tick_counts, tick_ref) + +/obj/item/flashlight/lantern/heretic/proc/try_effect_pulse(mob/living/seer, atom/center) + if(prob(33)) + return // tiny bit of variance + if(LAZYACCESS(effect_cds, REF(seer)) >= world.time) + return + + var/tick_count = LAZYACCESS(tick_counts, REF(seer)) + var/seer_eye_prot = seer.get_eye_protection() + if(seer_eye_prot >= FLASH_PROTECTION_WELDER_HYPER_SENSITIVE || IS_HERETIC_OR_MONSTER(seer)) + if(tick_count - 1 <= 0) + LAZYREMOVE(tick_counts, REF(seer)) + else + LAZYSET(tick_counts, REF(seer), tick_count - 1) + return + + + var/turf/seer_turf = get_turf(seer) + if(!isnull(seer_turf)) + var/list/datum/light_source/other_lights = list() + for(var/datum/lighting_corner/corner in list(seer_turf.lighting_corner_NE, seer_turf.lighting_corner_SE, seer_turf.lighting_corner_SW, seer_turf.lighting_corner_NW)) + other_lights |= SANITIZE_LIST(corner.affecting) + var/total_power = 0 + for(var/datum/light_source/light as anything in other_lights) + // mob lights are excluded, otherwise ash heretics are punished for lighting people on fire + if(istype(light.source_atom, /obj/effect/dummy/lighting_obj/moblight)) + continue + total_power += light.light_power + // if the power of nearby lights is high, the effect is diminished a bit (giving free levels of eye protection) + seer_eye_prot += max(0, round(total_power / 3)) + + LAZYSET(effect_cds, REF(seer), world.time + 5 SECONDS) + LAZYSET(tick_counts, REF(seer), tick_count + 1) + var/seer_stam = seer.get_stamina_loss() + switch(seer_eye_prot) + if(FLASH_PROTECTION_WELDER_SENSITIVE to INFINITY) + to_chat(seer, span_notice("[(src == center) ? src : "[center]'s [src]"] emits a bright flash of light.")) + return + + if(FLASH_PROTECTION_WELDER) + to_chat(seer, span_danger("[(src == center) ? src : "[center]'s [src]"] emits a bright flash of light, causing you to flinch.")) + seer.adjust_organ_loss(ORGAN_SLOT_EYES, 3, maximum = 30) + + if(FLASH_PROTECTION_FLASH) + to_chat(seer, span_danger("[(src == center) ? src : "[center]'s [src]"] emits a bright flash of light, causing you to flinch.")) + seer.adjust_organ_loss(ORGAN_SLOT_EYES, 4, maximum = 40) + seer.adjust_eye_blur(2 SECONDS) + if(tick_count > 2 && (seer_stam < 60 || (tick_count > 8 && seer_stam < 100))) + seer.adjust_stamina_loss(10) + if(tick_count > 3) + seer.adjust_confusion(2 SECONDS) + + if(FLASH_PROTECTION_NONE) + to_chat(seer, span_danger("A bright flash of light from [(src == center) ? src : "[center]'s [src]"] blinds you for a moment!")) + seer.adjust_organ_loss(ORGAN_SLOT_EYES, 5) + seer.adjust_temp_blindness(1 SECONDS) + seer.adjust_eye_blur(4 SECONDS) + if(tick_count > 2 && (seer_stam < 60 || (tick_count > 8 && seer_stam < 100))) + seer.adjust_stamina_loss(10) + if(tick_count > 3) + seer.adjust_confusion(4 SECONDS) + + if(FLASH_PROTECTION_SENSITIVE) + to_chat(seer, span_danger("A bright flash of light from [(src == center) ? src : "[center]'s [src]"] blinds you!")) + seer.adjust_organ_loss(ORGAN_SLOT_EYES, 8) + seer.adjust_temp_blindness(2 SECONDS) + seer.adjust_eye_blur(8 SECONDS) + if(tick_count > 1 && (seer_stam < 80 || (tick_count > 6 && seer_stam < 120))) + seer.adjust_stamina_loss(10) + if(tick_count > 2) + seer.adjust_confusion(tick_count > 3 ? 8 SECONDS : 4 SECONDS) + + if(-INFINITY to FLASH_PROTECTION_HYPER_SENSITIVE) + to_chat(seer, span_danger("A bright flash of light from [(src == center) ? src : "[center]'s [src]"] blinds you!")) + seer.adjust_organ_loss(ORGAN_SLOT_EYES, 15) + seer.adjust_temp_blindness(4 SECONDS) + seer.adjust_eye_blur(12 SECONDS) + if(seer_stam < 80 || (tick_count > 4 && seer_stam < 120)) + seer.adjust_stamina_loss(30) + if(tick_count > 1) + seer.adjust_confusion(tick_count > 2 ? 12 SECONDS : 6 SECONDS) + + if(!iscarbon(seer)) + return + + var/mob/living/carbon/carbon_seer = seer + // persistent exposure, to the point of causing full blindness, may put us in a trance. + // more sensitive mobs, funnily enough, will be blinded before the trance can set it (this is intentional). + if(tick_count > 6 && prob(66) && seer_eye_prot <= FLASH_PROTECTION_NONE && seer.is_blind_from(EYE_DAMAGE) && !carbon_seer.has_trauma_type(/datum/brain_trauma/hypnosis)) + carbon_seer.gain_trauma(new /datum/brain_trauma/hypnosis(pick_list(HERETIC_INFLUENCE_FILE, "hypnosis")), TRAUMA_RESILIENCE_BASIC) diff --git a/code/modules/antagonists/heretic/items/heretic_necks.dm b/code/modules/antagonists/heretic/items/heretic_necks.dm index c3c8eb20d448..2e6116c92ddf 100644 --- a/code/modules/antagonists/heretic/items/heretic_necks.dm +++ b/code/modules/antagonists/heretic/items/heretic_necks.dm @@ -1,18 +1,45 @@ /obj/item/clothing/neck/heretic_focus name = "amber focus" - desc = "An amber focusing glass that provides a link to the world beyond. The necklace seems to twitch, but only when you look at it from the corner of your eye." + desc = "An amber focusing glass that provides a link to the world beyond. \ + The necklace seems to twitch, but only when you look at it from the corner of your eye." icon_state = "eldritch_necklace" w_class = WEIGHT_CLASS_SMALL resistance_flags = FIRE_PROOF + /// Cooldown between attempts to recharge spells + var/cooldown_period = 3 MINUTES + COOLDOWN_DECLARE(spell_recharge_cd) -/obj/item/clothing/neck/heretic_focus/Initialize(mapload) +/obj/item/clothing/neck/heretic_focus/equipped(mob/living/user, slot) . = ..() - AddElement(/datum/element/heretic_focus) + if(!(slot & ITEM_SLOT_NECK) || !IS_HERETIC(user)) + return + START_PROCESSING(SSobj, src) + COOLDOWN_START(src, spell_recharge_cd, cooldown_period) + +/obj/item/clothing/neck/heretic_focus/dropped(mob/living/user) + . = ..() + STOP_PROCESSING(SSobj, src) + +/obj/item/clothing/neck/heretic_focus/process(seconds_per_tick) + if(!COOLDOWN_FINISHED(src, spell_recharge_cd)) + return + + var/mob/living/wearer = loc + if(!istype(wearer) || !IS_HERETIC(wearer)) + return PROCESS_KILL + + var/datum/antagonist/heretic/our_heretic = GET_HERETIC(wearer) + for(var/datum/heretic_knowledge/spell/spell in our_heretic?.get_researched_knowledge()) + spell.add_charges(ceil(spell.max_charges * spell.focus_recharge_amount)) + + COOLDOWN_START(src, spell_recharge_cd, cooldown_period) /obj/item/clothing/neck/heretic_focus/crimson_medallion name = "crimson medallion" - desc = "A blood-red focusing glass that provides a link to the world beyond, and worse. Its eye is constantly twitching and gazing in all directions. It almost seems to be silently screaming..." + desc = "A blood-red focusing glass that provides a link to the world beyond, and worse. \ + Its eye is constantly twitching and gazing in all directions. It almost seems to be silently screaming..." icon_state = "crimson_medallion" + cooldown_period = 1 MINUTES /// The aura healing component. Used to delete it when taken off. var/datum/component/component /// If active or not, used to add and remove its cult and heretic buffs. @@ -98,7 +125,6 @@ . += span_cult_bold("This focus will allow you to store one extra spell and halve the empowering time, alongside providing a small regenerative effect.") magic_dude = TRUE if(IS_HERETIC_OR_MONSTER(user)) - . += span_notice("This focus will halve your spell cooldowns, alongside granting a small regenerative effect to any nearby heretics or monsters, including you.") magic_dude = TRUE if(magic_dude) @@ -106,7 +132,8 @@ /obj/item/clothing/neck/eldritch_amulet name = "warm eldritch medallion" - desc = "A strange medallion. Peering through the crystalline surface, the world around you melts away. You see your own beating heart, and the pulsing of a thousand others." + desc = "A strange medallion. Peering through the crystalline surface, the world around you melts away. \ + You see your own beating heart, and the pulsing of a thousand others." icon = 'icons/obj/antags/eldritch.dmi' icon_state = "eye_medalion" w_class = WEIGHT_CLASS_SMALL @@ -114,10 +141,6 @@ /// A secondary clothing trait only applied to heretics. var/heretic_only_trait = TRAIT_THERMAL_VISION -/obj/item/clothing/neck/eldritch_amulet/Initialize(mapload) - . = ..() - AddElement(/datum/element/heretic_focus) - /obj/item/clothing/neck/eldritch_amulet/equipped(mob/user, slot) . = ..() if(!(slot & ITEM_SLOT_NECK)) @@ -147,7 +170,7 @@ w_class = WEIGHT_CLASS_SMALL // The amulet conversion tool used by moon heretics -/obj/item/clothing/neck/heretic_focus/moon_amulet +/obj/item/clothing/neck/moon_amulet name = "moonlight amulet" desc = "A piece of the mind, the soul and the moon. Gazing into it makes your head spin and hear whispers of laughter and joy." icon = 'icons/obj/antags/eldritch.dmi' @@ -163,12 +186,12 @@ var/valid_weapon_type = /obj/item/melee/sickly_blade var/sanity_threshold = SANITY_LEVEL_INSANE -/obj/item/clothing/neck/heretic_focus/moon_amulet/examine(mob/user) +/obj/item/clothing/neck/moon_amulet/examine(mob/user) . = ..() if(IS_HERETIC(user)) - . += span_notice("Wearing this amulet increases your healing speed by 50%") + . += span_notice("Wearing this amulet increases your healing speed by 50%.") -/obj/item/clothing/neck/heretic_focus/moon_amulet/equipped(mob/living/user, slot) +/obj/item/clothing/neck/moon_amulet/equipped(mob/living/user, slot) . = ..() if(!IS_HERETIC(user) && (slot_flags & slot)) channel_amulet(user) @@ -179,7 +202,7 @@ on_amulet_activate(user) /// Modifies any blades you hold/pickup/drop when the amulet is enabled -/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/on_amulet_activate(mob/living/user) +/obj/item/clothing/neck/moon_amulet/proc/on_amulet_activate(mob/living/user) RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, PROC_REF(blade_channel)) RegisterSignal(user, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(on_equip_item)) RegisterSignal(user, COMSIG_MOB_DROPPED_ITEM, PROC_REF(on_dropped_item)) @@ -192,7 +215,7 @@ moon_passive?.amulet_equipped = TRUE /// Modifies any blades you hold/pickup/drop when the amulet is disabled -/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/on_amulet_deactivate(mob/living/user) +/obj/item/clothing/neck/moon_amulet/proc/on_amulet_deactivate(mob/living/user) // Make sure to restore the values of any blades we might be holding when our amulet is deactivated on_dropped_item(user, user.get_active_held_item()) on_dropped_item(user, user.get_inactive_held_item()) @@ -201,21 +224,21 @@ var/datum/status_effect/heretic_passive/moon/moon_passive = user.has_status_effect(/datum/status_effect/heretic_passive/moon) moon_passive?.amulet_equipped = FALSE -/obj/item/clothing/neck/heretic_focus/moon_amulet/dropped(mob/living/user) +/obj/item/clothing/neck/moon_amulet/dropped(mob/living/user) on_amulet_deactivate(user) return ..() -/obj/item/clothing/neck/heretic_focus/moon_amulet/attack(mob/living/target, mob/living/user, list/modifiers, list/attack_modifiers) +/obj/item/clothing/neck/moon_amulet/attack(mob/living/target, mob/living/user, list/modifiers, list/attack_modifiers) if(channel_amulet(user, target)) return return ..() -/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/blade_channel(mob/living/attacker, mob/living/victim) +/obj/item/clothing/neck/moon_amulet/proc/blade_channel(mob/living/attacker, mob/living/victim) SIGNAL_HANDLER channel_amulet(attacker, victim) /// Makes whoever the target is a bit more insane. If they are insane enough, they will be zombified into a moon zombie -/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/channel_amulet(mob/user, atom/target) +/obj/item/clothing/neck/moon_amulet/proc/channel_amulet(mob/user, atom/target) if(!isliving(user)) return FALSE @@ -253,6 +276,7 @@ human_target.balloon_alert(living_user, "their mind almost bends but something protects it!") human_target.apply_status_effect(/datum/status_effect/moon_slept) return TRUE + SEND_SIGNAL(user, COMSIG_MOB_APPLIED_MOONLIGHT_AMULET, target) human_target.balloon_alert(living_user, "their mind bends to see the truth!") human_target.apply_status_effect(/datum/status_effect/moon_converted) living_user.log_message("made [human_target] insane.", LOG_GAME) @@ -260,7 +284,7 @@ return TRUE /// Modifies any blades that we equip while wearing the amulet -/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/on_equip_item(mob/user, obj/item/blade, slot) +/obj/item/clothing/neck/moon_amulet/proc/on_equip_item(mob/user, obj/item/blade, slot) SIGNAL_HANDLER if(!istype(blade, valid_weapon_type)) return // We only care about modifying blades @@ -277,7 +301,7 @@ blade.armour_penetration = initial(blade.armour_penetration) UnregisterSignal(blade, COMSIG_SEND_ITEM_ATTACK_MESSAGE_OBJECT) -/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/modify_attack_message(obj/item/weapon, mob/living/victim, mob/living/attacker) +/obj/item/clothing/neck/moon_amulet/proc/modify_attack_message(obj/item/weapon, mob/living/victim, mob/living/attacker) SIGNAL_HANDLER var/list/attack_list = list( @@ -297,7 +321,7 @@ return SIGNAL_MESSAGE_MODIFIED /// Modifies any blades that we drop while wearing the amulet -/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/on_dropped_item(mob/user, obj/item/dropped_item) +/obj/item/clothing/neck/moon_amulet/proc/on_dropped_item(mob/user, obj/item/dropped_item) SIGNAL_HANDLER if(!istype(dropped_item, valid_weapon_type)) return // We only care about modifying blades diff --git a/code/modules/antagonists/heretic/magic/void_prison.dm b/code/modules/antagonists/heretic/items/voidballd.dm similarity index 51% rename from code/modules/antagonists/heretic/magic/void_prison.dm rename to code/modules/antagonists/heretic/items/voidballd.dm index 0d4904937e80..f88b3e6288e1 100644 --- a/code/modules/antagonists/heretic/magic/void_prison.dm +++ b/code/modules/antagonists/heretic/items/voidballd.dm @@ -1,40 +1,58 @@ -/datum/action/cooldown/spell/pointed/void_prison - name = "Void Prison" - desc = "Sends a heathen into the void for 10 seconds. \ - They will be unable to perform any actions for the duration. \ - Afterwards, they will be chilled and returned to the mortal plane." - background_icon_state = "bg_heretic" - overlay_icon_state = "bg_heretic_border" - button_icon = 'icons/mob/actions/actions_ecult.dmi' - button_icon_state = "voidball" - ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' - sound = 'sound/effects/magic/voidblink.ogg' - - cooldown_time = 1 MINUTES - cast_range = 3 +/obj/item/void_prison + name = "void prison" + desc = "A small glass orb of swirling darkness. It feels cold to the touch, and consumes all light around it." + icon = 'icons/mob/actions/actions_ecult.dmi' + icon_state = "voidball" + pickup_sound = 'sound/items/handling/materials/glass_pick_up.ogg' + drop_sound = 'sound/items/handling/materials/glass_drop.ogg' - sound = null - school = SCHOOL_FORBIDDEN - invocation = "V'D PR'S'N!" - invocation_type = INVOCATION_SHOUT - spell_requirements = NONE +/obj/item/void_prison/Initialize(mapload) + . = ..() + transform = transform.Scale(0.5) -/datum/action/cooldown/spell/pointed/void_prison/before_cast(atom/cast_on) +/obj/item/void_prison/attack_self(mob/living/user, modifiers) . = ..() - if(. & SPELL_CANCEL_CAST) + if(.) return - if(!ismob(cast_on)) - return SPELL_CANCEL_CAST -/datum/action/cooldown/spell/pointed/void_prison/cast(mob/living/carbon/human/cast_on) - . = ..() - if(cast_on.can_block_magic(antimagic_flags)) - cast_on.visible_message( - span_danger("A swirling, cold void wraps around [cast_on], but they burst free in a wave of heat!"), - span_danger("A yawning void begins to open before you, but a great wave of heat bursts it apart! You are protected!!") + playsound(src, SFX_SHATTER, 50, TRUE) + playsound(src, 'sound/effects/magic/voidblink.ogg', 50, FALSE) + if(IS_HERETIC_OR_MONSTER(user)) + to_chat(user, span_mansus("You smash [src], releasing its power around you!")) + for(var/mob/living/nearby_mob in view(3, user)) + if(IS_HERETIC_OR_MONSTER(nearby_mob)) + continue + if(nearby_mob.has_status_effect(/datum/status_effect/eldritch)) + continue + if(nearby_mob.can_block_magic(MAGIC_RESISTANCE)) + nearby_mob.visible_message( + span_danger("A swirling, cold void wraps around [nearby_mob], but they burst free in a wave of heat!"), + span_userdanger("A yawning void begins to open before you, but a great wave of heat bursts it apart! You are protected!!") + ) + continue + nearby_mob.visible_message( + span_danger("A swirling, cold void wraps around [nearby_mob]!"), + span_userdanger("A yawning void opens before you! You are swallowed by the darkness, and find yourself in complete nothingness..."), + ) + nearby_mob.apply_status_effect(/datum/status_effect/void_prison) + + else if(user.can_block_magic(MAGIC_RESISTANCE)) + to_chat(user, span_hypnophrase("You smash [src], but its power begins to encompass you!")) + user.visible_message( + span_danger("A swirling, cold void wraps around [user], but they burst free in a wave of heat!"), + span_userdanger("A yawning void begins to open before you, but a great wave of heat bursts it apart! You are protected!!") ) - return - cast_on.apply_status_effect(/datum/status_effect/void_prison, "void_stasis") + + else + to_chat(user, span_hypnophrase("You smash [src], but its power begins to encompass you!")) + user.visible_message( + span_danger("A swirling, cold void wraps around [user]!"), + span_userdanger("A yawning void opens before you! You are swallowed by the darkness, and find yourself in complete nothingness..."), + ) + user.apply_status_effect(/datum/status_effect/void_prison) + + qdel(src) + return TRUE /datum/status_effect/void_prison id = "void_prison" @@ -43,7 +61,7 @@ ///The overlay that gets applied to whoever has this status active var/obj/effect/abstract/voidball/stasis_overlay -/datum/status_effect/void_prison/on_creation(mob/living/new_owner, set_duration) +/datum/status_effect/void_prison/on_creation(mob/living/new_owner) . = ..() stasis_overlay = new /obj/effect/abstract/voidball(new_owner) RegisterSignal(stasis_overlay, COMSIG_QDELETING, PROC_REF(clear_overlay)) diff --git a/code/modules/antagonists/heretic/knowledge/_heretic_paths.dm b/code/modules/antagonists/heretic/knowledge/_heretic_paths.dm index ed4c7b97dd35..243e1d579469 100644 --- a/code/modules/antagonists/heretic/knowledge/_heretic_paths.dm +++ b/code/modules/antagonists/heretic/knowledge/_heretic_paths.dm @@ -90,6 +90,7 @@ GLOBAL_LIST_INIT(heretic_path_datums, init_heretic_path_datums()) var/datum/status_effect/heretic_passive/passive = new start.eldritch_passive() data["passive"] = list( "name" = initial(passive.name), + "recharge" = initial(passive.recharge_description), "description" = passive.passive_descriptions.Copy(), ) qdel(passive) @@ -294,6 +295,7 @@ GLOBAL_LIST_INIT(heretic_path_datums, init_heretic_path_datums()) list( "parent_knowledge" = knowledge_tier1, "guaranteed_knowledge" = guaranteed_draft_t1, + "supplementary_knowledge" = list(/datum/heretic_knowledge/spell/cloak_of_shadows), "probabilities" = list("1" = 50, "2" = 50, "3" = 0, "4" = 0, "5" = 0), HKT_DEPTH = HKT_DEPTH_DRAFT_1, ), @@ -316,13 +318,20 @@ GLOBAL_LIST_INIT(heretic_path_datums, init_heretic_path_datums()) ) ) /// generate 3 drafts for each draft tier, while banning you from picking multiple drafts - for(var/draft in drafts) + for(var/list/draft as anything in drafts) var/parent_knowledge_path = draft["parent_knowledge"] var/datum/heretic_knowledge/guaranteed_draft = draft["guaranteed_knowledge"] var/list/probabilities = draft["probabilities"] var/depth = draft[HKT_DEPTH] var/list/draft_blacklist = list() + for(var/datum/heretic_knowledge/supplementary as anything in draft["supplementary_knowledge"]) + final_draft[supplementary] = make_knowledge_entry(supplementary, null, HERETIC_KNOWLEDGE_DRAFT, depth, 0) + final_draft[supplementary][HKT_PURCHASED_DEPTH] = supplementary::drafting_tier + var/supplementary_id = final_draft[supplementary][HKT_ID] + draft_blacklist[supplementary] = supplementary_id + heretic_research_tree[parent_knowledge_path][HKT_NEXT] |= supplementary_id + for(var/cycle in 1 to 3) var/datum/heretic_knowledge/selected_knowledge if(guaranteed_draft && cycle == 1) @@ -343,21 +352,14 @@ GLOBAL_LIST_INIT(heretic_path_datums, init_heretic_path_datums()) stack_trace("Failed to select a knowledge for heretic path [heretic_path] at depth [depth]. This should never happen.") continue - final_draft[selected_knowledge] = make_knowledge_entry( - selected_knowledge, - null, - HERETIC_KNOWLEDGE_DRAFT, - depth, - 0, - ) + final_draft[selected_knowledge] = make_knowledge_entry(selected_knowledge, null, HERETIC_KNOWLEDGE_DRAFT, depth, 0) final_draft[selected_knowledge][HKT_PURCHASED_DEPTH] = selected_knowledge::drafting_tier var/draft_id = final_draft[selected_knowledge][HKT_ID] draft_blacklist[selected_knowledge] = draft_id heretic_research_tree[parent_knowledge_path][HKT_NEXT] |= draft_id var/list/blacklist_ids = assoc_to_values(draft_blacklist) - for(var/blacklist_path in draft_blacklist) - var/id = draft_blacklist[blacklist_path] + for(var/blacklist_path, id in draft_blacklist) final_draft[blacklist_path][HKT_BAN] += (blacklist_ids - id) // all possible drafts are added to the shop, this time with costs diff --git a/code/modules/antagonists/heretic/knowledge/ash_lore.dm b/code/modules/antagonists/heretic/knowledge/ash_lore.dm index 6579c857b8db..ff7ff37093e4 100644 --- a/code/modules/antagonists/heretic/knowledge/ash_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/ash_lore.dm @@ -27,9 +27,7 @@ tips = list( "Your Mansus Grasp applies a short blind and a mark that puts your opponent into stamina crit when triggered by your blade. The mark can spread to nearby opponents.", "Selecting this path makes you immune to high temperature damage. Remember, however, that your clothes can still burn! If you want to protect yourself from your own fire, wear a Scorched Mantle.", - "Your Scorched Mantle will cause you to generate firestacks on your own body (Make sure you toggle the effect!). Upon reaching 5 fire stacks, your ashen spells will be empowered (indicated by your spells being highlighted in green).", - "Your Ashen passage is a short cooldown jaunt capable of removing restraints. If empowered, it gains a longer jaunt time, and also will remove stuns and stamina crit.", - "Volcano blast can make short work of your enemies, should they be foolish enough to stick close to each other. If empowered, it will have no cast time and generate twice the amount of firestacks. Burn the heathens to ashes!", + "Your Scorched Mantle will keep you on fire while protecting you from the ill effects of it. Use this to your advantage by running into groups of enemies, spreading fire far and wide.", "Do not neglect the Mask of Madness. It will slowly sap the stamina of your enemies and make them hallucinate.", "Make sure to set as many enemies on fire as you possibly can! Nightwatcher's Rebirth will heal you and have its cooldown reduced based on how many mobs you siphon.", "Your ascension grants you complete immunity to environmental hazards, including bombs! But you are still vulnerable to more conventional weaponry. Do not become overconfident.", @@ -41,7 +39,7 @@ knowledge_tier2 = /datum/heretic_knowledge/spell/fire_blast guaranteed_side_tier2 = /datum/heretic_knowledge/rifle robes = /datum/heretic_knowledge/armor/ash - knowledge_tier3 = /datum/heretic_knowledge/mad_mask + knowledge_tier3 = /datum/heretic_knowledge/nightwatchers_lantern guaranteed_side_tier3 = /datum/heretic_knowledge/summon/ashy blade = /datum/heretic_knowledge/blade_upgrade/ash knowledge_tier4 = /datum/heretic_knowledge/spell/flame_birth @@ -49,9 +47,10 @@ /datum/heretic_knowledge/limited_amount/starting/base_ash name = "Nightwatcher's Secret" - desc = "Opens up the Path of Ash to you. \ - Allows you to transmute a match and a knife into an Ashen Blade. \ + desc = "Opens up the Path of Ash to you.
\ + Allows you to create a Ashen Blades. \ You can only create two at a time." + transmute_text = "Transmute a match and a knife." gain_text = "The City Guard know their watch. If you ask them at night, they may tell you about the ashy lantern." required_atoms = list( /obj/item/knife = 1, @@ -89,32 +88,35 @@ /datum/heretic_knowledge/spell/ash_passage name = "Ashen Passage" - desc = "Grants you Ashen Passage, a spell that lets you phase out of reality, allowing you to traverse a short distance, passing though any walls. \ - When empowered, it will break you out of any stuns and restraints, and will have a longer range." + desc = "Grants you Ashen Passage, a spell that lets you phase out of reality, \ + allowing you to traverse a short distance, passing though any walls." gain_text = "He knew how to walk between the planes." - action_to_add = /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash cost = 2 drafting_tier = 5 + max_charges = 6 + focus_recharge_amount = 0.15 + holywater_drain_amount = 0.15 /datum/heretic_knowledge/spell/fire_blast name = "Volcano Blast" desc = "Grants you Volcano Blast, a spell that - after a short charge - fires off a beam of energy \ at a nearby enemy, setting them on fire and burning them. If they do not extinguish themselves, \ - the beam will continue to another target. \ - When empowered, has instant cast time and blasts enemies with more flames." + the beam will continue to another target." gain_text = "No fire was hot enough to rekindle them. No fire was bright enough to save them. No fire is eternal." action_to_add = /datum/action/cooldown/spell/charged/beam/fire_blast cost = 2 research_tree_icon_frame = 7 + max_charges = 3 + focus_recharge_amount = 0.33 + holywater_drain_amount = 0.16 /datum/heretic_knowledge/armor/ash - desc = "Allows you to transmute a table (or a suit), a mask and a match to create a scorched mantle. \ - It provides completes protection from fire, and is able to produce more flames passively. \ - When you have enough fire, you may cast empowered versions of your ashen spells. \ - Acts as a focus while hooded." + desc = "Create a Scorched Mantle.
\ + It provides completes protection from fire, and is able to produce more flames passively." + transmute_text = "Transmute a table (or a suit), a mask and a match." gain_text = "The Watch remain as they fell, crumbling away from sight. \ - Yet the winds blowing through the city call them back to service, dust kicked into the air, a drifting silhouette of the fallen." + Yet the winds blowing through the city call them back to service, dust kicked into the air, a drifting silhouette of the fallen." result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash) research_tree_icon_state = "ash_armor" required_atoms = list( @@ -123,22 +125,34 @@ /obj/item/match = 1, ) -/datum/heretic_knowledge/mad_mask - name = "Mask of Madness" - desc = "Allows you to transmute any mask, four candles, a stun baton, and a liver to create a Mask of Madness. \ - The mask instills fear into heathens who witness it, causing stamina damage, hallucinations, and insanity. \ - It can also be forced onto a heathen, to make them unable to take it off..." - gain_text = "The Nightwatcher was lost. That's what the Watch believed. Yet he walked the world, unnoticed by the masses." +/datum/heretic_knowledge/nightwatchers_lantern + name = "Nightwatcher's Lantern" + desc = "Create a burning lantern.
\ + A burning lantern is a bright light that damages the eyes and eventually confuses those who witness it for too long. \ + The effect is reduced for those with protective eyewear, and strengthened if the burning lantern is the only nearby source of light." + transmute_text = "Transmute a lamp, lantern, or seclight, a pair of eyes, a flash, and four lit candles." + gain_text = "The Nightwatcher did not venture out in the dark. That was foolish, even the Watch knew that. \ + Their lantern burned with a light that could burn the sun." + result_atoms = list(/obj/item/flashlight/lantern/heretic) required_atoms = list( - /obj/item/organ/liver = 1, - /obj/item/melee/baton/security = 1, // Technically means a cattleprod is valid - /obj/item/clothing/mask = 1, + list(/obj/item/flashlight/lamp, /obj/item/flashlight/lantern, /obj/item/flashlight/seclite) = 1, + /obj/item/organ/eyes = 1, + /obj/item/assembly/flash = 1, /obj/item/flashlight/flare/candle = 4, ) - result_atoms = list(/obj/item/clothing/mask/madness_mask) - cost = 2 - research_tree_icon_path = 'icons/obj/clothing/masks.dmi' - research_tree_icon_state = "mad_mask" + research_tree_icon_path = 'icons/obj/lighting.dmi' + research_tree_icon_state = "lantern" + +/datum/heretic_knowledge/nightwatchers_lantern/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) + . = ..() + for(var/obj/item/flashlight/flare/candle/candle in atoms) + if(!candle.light_on) + atoms -= candle + +/datum/heretic_knowledge/nightwatchers_lantern/prepare_atom_for_ritual_test(atom/what) + . = ..() + if(istype(what, /obj/item/flashlight/flare/candle)) + what.set_light_on(TRUE) /datum/heretic_knowledge/blade_upgrade/ash name = "Fiery Blade" @@ -146,7 +160,6 @@ gain_text = "He returned, blade in hand, he swung and swung as the ash fell from the skies. \ His city, the people he swore to watch... and watch he did, as they all burnt to cinders." - research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi' research_tree_icon_state = "blade_upgrade_ash" @@ -160,7 +173,7 @@ /datum/heretic_knowledge/spell/flame_birth name = "Nightwatcher's Rebirth" desc = "Grants you Nightwatcher's Rebirth, a spell that extinguishes you and \ - burns all nearby heathens who are currently on fire, healing you for every victim afflicted. \ + burns all nearby heathens who are currently on fire, healing you for every victim afflicted.
\ If any victims afflicted are in critical condition, they will also instantly die." gain_text = "The fire was inescapable, and yet, life remained in his charred body. \ The Nightwatcher was a particular man, always watching." @@ -168,16 +181,18 @@ cost = 2 research_tree_icon_frame = 5 is_final_knowledge = TRUE + max_charges = 3 + focus_recharge_amount = 0.33 + holywater_drain_amount = 0.16 /datum/heretic_knowledge/ultimate/ash_final name = "Ashlord's Rite" - desc = "The ascension ritual of the Path of Ash. \ - Bring 3 burning or husked corpses to a transmutation rune to complete the ritual. \ - When completed, you become a harbinger of flames, gaining two abilites. \ + desc = "The ascension ritual of the Path of Ash.
\ + When completed, you become a harbinger of flames, gaining two abilites.
\ Cascade, which causes a massive, growing ring of fire around you, \ - and Oath of Flame, causing you to passively create a ring of flames as you walk. \ - Some ashen spells you already knew will be empowered as well. \ + and Oath of Flame, causing you to passively create a ring of flames as you walk.
\ You will also become immune to flames, space, and similar environmental hazards." + transmute_text = "Transmute 3 burning or husked corpses." gain_text = "The Watch is dead, the Nightwatcher burned with it. Yet his fire burns evermore, \ for the Nightwatcher brought forth the rite to mankind! His gaze continues, as now I am one with the flames, \ WITNESS MY ASCENSION, THE ASHY LANTERN BLAZES ONCE MORE!" diff --git a/code/modules/antagonists/heretic/knowledge/blade_lore.dm b/code/modules/antagonists/heretic/knowledge/blade_lore.dm index 495d994f1372..8d97e5d3d1e0 100644 --- a/code/modules/antagonists/heretic/knowledge/blade_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/blade_lore.dm @@ -52,9 +52,10 @@ /datum/heretic_knowledge/limited_amount/starting/base_blade name = "The Cutting Edge" - desc = "Opens up the Path of Blades to you. \ - Allows you to transmute a knife with one bar of silver or titanium to create a Sundered Blade. \ + desc = "Opens up the Path of Blades to you.
\ + Allows you to create Sundered Blades. \ You can create up to four at a time." + transmute_text = "Transmute a knife with one bar of silver or titanium." gain_text = "Our great ancestors forged swords and practiced sparring on the eve of great battles." required_atoms = list( /obj/item/knife = 1, @@ -95,19 +96,22 @@ /datum/heretic_knowledge/spell/realignment name = "Realignment" - desc = "Grants you Realignment a spell that wil realign your body rapidly for a short period. \ - During this process, you will rapidly regenerate stamina and quickly recover from stuns, however, you will be unable to attack. \ + desc = "Grants you Realignment, a spell that wil realign your body rapidly for a short period.
\ + During this process, you will rapidly regenerate stamina and quickly recover from stuns, however, you will be unable to attack.
\ This spell can be cast in rapid succession, but doing so will increase the cooldown." gain_text = "In the flurry of death, he found peace within himself. Despite insurmountable odds, he forged on." action_to_add = /datum/action/cooldown/spell/realignment cost = 2 + max_charges = 3 + focus_recharge_amount = 0.33 + holywater_drain_amount = 0.16 /// The amount of blood flow reduced per level of severity of gained bleeding wounds for Stance of the Torn Champion. #define BLOOD_FLOW_PER_SEVEIRTY -1 /datum/heretic_knowledge/duel_stance name = "Stance of the Torn Champion" - desc = "Grants resilience to blood loss from wounds and immunity to having your limbs dismembered. \ + desc = "Grants resilience to blood loss from wounds and immunity to having your limbs dismembered.
\ Additionally, when damaged below 50% of your maximum health, \ you gain increased resistance to gaining wounds and resistance to slowdown." gain_text = "In time, it was he who stood alone among the bodies of his former comrades, awash in blood, none of it his own. \ @@ -173,11 +177,11 @@ #undef BLOOD_FLOW_PER_SEVEIRTY /datum/heretic_knowledge/armor/blade - desc = "Allows you to transmute a table (or a suit), a mask and a sheet of titanium or silver to create a Shattered Panoply. \ - Provides baton resistance and shock insulation while worn. \ - Acts as a focus while hooded." + desc = "Create a Shattered Panoply.
\ + Provides baton resistance and shock insulation while worn." + transmute_text = "Transmute a table (or a suit), a mask and a sheet of titanium or silver." gain_text = "The echoing, directionless cacophony of violence reverberates about me. \ - Even as the Champion's steel panoply was torn from their form, each piece craves purpose still, seeking to intercept unseen or imagined attackers." + Even as the Champion's steel panoply was torn from their form, each piece craves purpose still, seeking to intercept unseen or imagined attackers." result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade) research_tree_icon_state = "blade_armor" required_atoms = list( @@ -188,10 +192,10 @@ /datum/heretic_knowledge/spell/wolves_among_sheep name = "Wolves Among Sheep" - desc = "Alters the fabric of reality, conjuring a magical arena unpassable to outsiders, \ - all participants are trapped and immune to any form of crowd control or enviromental hazards; \ + desc = "Alters the fabric of reality, conjuring a magical arena impassable to outsiders, \ + all participants are trapped and immune to any form of crowd control or environmental hazards; \ trapped participants are granted a Blade and are unable to leave or jaunt until they score a critical hit. \ - Critical hits partially restore the Heretic's health." + Knocking combatant into critical condition partially restore the Heretic's health." gain_text = "Shadows crawl across the room, casting every chair, table \ and console into the looming shape of another traitorous hand. \ I have made an enemy of all, and peace will never be known to me \ @@ -200,12 +204,34 @@ cost = 2 action_to_add = /datum/action/cooldown/spell/wolves_among_sheep is_final_knowledge = TRUE + max_charges = 4 + path_recharge_amount = 0.0 + holywater_drain_amount = 0.25 + transmute_text = "You are rewarded with one charge for every sacrifice you complete. \ + You will also be rewarded with a charge if you knock at least three combatants into critical condition while the spell is active. \ + You may only have up to four charges at once." + +/datum/heretic_knowledge/spell/wolves_among_sheep/New() + . = ..() + charges = 1 // start with one, can go up to four + +/datum/heretic_knowledge/spell/wolves_among_sheep/on_gain(mob/user, datum/antagonist/heretic/our_heretic) + . = ..() + RegisterSignal(our_heretic, COMSIG_HERETIC_SACRIFICE, PROC_REF(on_sacrifice)) + +/datum/heretic_knowledge/spell/wolves_among_sheep/on_lose(mob/user, datum/antagonist/heretic/our_heretic) + . = ..() + UnregisterSignal(our_heretic, COMSIG_HERETIC_SACRIFICE) + +/datum/heretic_knowledge/spell/wolves_among_sheep/proc/on_sacrifice(datum/source, mob/living/sacrifice, high_value) + SIGNAL_HANDLER + add_charges(1) /datum/heretic_knowledge/blade_upgrade/blade name = "Empowered Blades" desc = "Attacking someone with a Sundered Blade in both hands \ - will now deliver a blow with both at once, dealing two attacks in rapid succession. \ - The second blow will be slightly weaker. \ + will now deliver a blow with both at once, dealing two attacks in rapid succession.
\ + The second blow will be slightly weaker.
\ You are able to infuse your mansus grasp directly into your blades, and your blades are more effective against structures." gain_text = "I found him cleaved in twain, halves locked in a duel without end; \ a flurry of blades, neither hitting their mark, for the Champion was indomitable." @@ -299,23 +325,44 @@ /datum/heretic_knowledge/spell/furious_steel name = "Furious Steel" desc = "Grants you Furious Steel, a targeted spell. Using it will summon three \ - orbiting blades around you. These blades will protect you from all attacks, \ - but are consumed on use. Additionally, you can click to fire the blades \ + orbiting blades around you.
These blades will protect you from all attacks, \ + but are consumed on use.
You can click to fire the blades \ at a target, dealing damage and causing bleeding." gain_text = "Without thinking, I took the knife of a fallen soldier and threw with all my might. My aim was true! \ The Torn Champion smiled at their first taste of agony, and with a nod, their blades became my own." + required_atoms = list(/obj/item/knife = 1) action_to_add = /datum/action/cooldown/spell/pointed/projectile/furious_steel cost = 2 + max_charges = 6 + recharge_amount = 0.5 + focus_recharge_amount = 0.33 + holywater_drain_amount = 0.16 + transmute_text = "Can be manually recharged by completing a ritual with a knife - this will return half of the spell's maximum charges." + +/datum/heretic_knowledge/spell/furious_steel/on_gain(mob/user, datum/antagonist/heretic/our_heretic) + . = ..() + RegisterSignal(user, COMSIG_MOB_BLADE_BARRIER_TRIGGERED, PROC_REF(blade_barrier_triggered)) + +/datum/heretic_knowledge/spell/furious_steel/on_lose(mob/user, datum/antagonist/heretic/our_heretic) + . = ..() + UnregisterSignal(user, COMSIG_MOB_BLADE_BARRIER_TRIGGERED) + +/datum/heretic_knowledge/spell/furious_steel/proc/blade_barrier_triggered(mob/living/target, datum/status_effect/barrier) + SIGNAL_HANDLER + + var/datum/action/cooldown/spell/pointed/projectile/furious_steel/spell = created_action_ref + if(spell?.blade_effect == barrier) + remove_charges(1) /datum/heretic_knowledge/ultimate/blade_final name = "Maelstrom of Silver" - desc = "The ascension ritual of the Path of Blades. \ - Bring 3 corpses with either no head or a split skull to a transmutation rune to complete the ritual. \ + desc = "The ascension ritual of the Path of Blades.
\ When completed, you will be surrounded in a constant, regenerating orbit of blades. \ - These blades will protect you from all attacks, but are consumed on use. \ - Your Furious Steel spell will also have a shorter cooldown. \ - Additionally, you become a master of combat, gaining full wound immunity and the ability to shrug off short stuns. \ + These blades will protect you from all attacks, but are consumed on use.
\ + Your Furious Steel spell will also have a shorter cooldown.
\ + Additionally, you become a master of combat, gaining full wound immunity and the ability to shrug off short stuns.
\ Your Sundered Blades deal bonus damage and heal you on attack for a portion of the damage dealt." + transmute_text = "Transmute three corpses with either no head or a split skull." gain_text = "The Torn Champion is freed! I will become the blade reunited, and with my greater ambition, \ I AM UNMATCHED! A STORM OF STEEL AND SILVER IS UPON US! WITNESS MY ASCENSION!" diff --git a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm index 7acf182d30ef..d62814767c30 100644 --- a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm @@ -49,9 +49,10 @@ /datum/heretic_knowledge/limited_amount/starting/base_cosmic name = "Eternal Gate" - desc = "Opens up the Path of Cosmos to you. \ - Allows you to transmute a sheet of plasma and a knife into an Cosmic Blade. \ + desc = "Opens up the Path of Cosmos to you.
\ + Allows you to create Cosmic Blades. \ You can only create two at a time." + transmute_text = "Transmute a sheet of plasma and a knife." gain_text = "A nebula appeared in the sky, its infernal birth shone upon me. This was the start of a great transcendence." required_atoms = list( /obj/item/knife = 1, @@ -81,6 +82,7 @@ action_to_add = /datum/action/cooldown/spell/cosmic_rune cost = 2 drafting_tier = 5 + max_charges = INFINITY /datum/heretic_knowledge/spell/star_blast name = "Star Blast" @@ -89,13 +91,17 @@ gain_text = "The Beast was behind me now at all times, with each sacrifice words of affirmation coursed through me." action_to_add = /datum/action/cooldown/spell/pointed/projectile/star_blast cost = 2 + max_charges = 4 + path_recharge_amount = 0.5 + focus_recharge_amount = 0.25 + holywater_drain_amount = 0.25 /datum/heretic_knowledge/armor/cosmic - - desc = "Allows you to transmute a table (or a suit), a mask and a sheet of plasma to create a Starwoven Cloak, grants protection from the hazards of space while granting to the user the ability to levitate at will. \ - Acts as a focus while hooded." + desc = "Create a Starwoven Cloak.
Grants protection from the hazards of space \ + while granting to the user the ability to levitate at will." + transmute_text = "Transmute a table (or a suit), a mask and a sheet of plasma." gain_text = "Like radiant cords, the stars shone in union across the silken shape of a billowing cloak, that at once does and does not drape my shoulders. \ - The eyes of the Beast rested upon me, and through me." + The eyes of the Beast rested upon me, and through me." result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/cosmic) research_tree_icon_state = "cosmic_armor" required_atoms = list( @@ -114,6 +120,10 @@ My veins now emitted a strange purple glow, the Beast knows I will surpass its expectations." action_to_add = /datum/action/cooldown/spell/touch/star_touch cost = 2 + max_charges = 4 + path_recharge_amount = 0.5 + focus_recharge_amount = 0.25 + holywater_drain_amount = 0.25 /datum/heretic_knowledge/blade_upgrade/cosmic name = "Cosmic Blade" @@ -214,18 +224,21 @@ action_to_add = /datum/action/cooldown/spell/conjure/cosmic_expansion cost = 2 is_final_knowledge = TRUE + max_charges = 2 + path_recharge_amount = 0.25 + holywater_drain_amount = 0.25 /datum/heretic_knowledge/ultimate/cosmic_final name = "Creators's Gift" - desc = "The ascension ritual of the Path of Cosmos. \ - Bring 3 corpses with a star mark to a transmutation rune to complete the ritual. \ + desc = "The ascension ritual of the Path of Cosmos.
\ When completed, you become the owner of a Star Gazer. \ You will be able to command the Star Gazer with Alt+click. \ - You can also give it commands through speech. \ + You can also give it commands through speech.
\ The Star Gazer is a strong ally who can even break down reinforced walls. \ - The Star Gazer has an aura that will heal you and damage opponents. \ - Star Touch can now teleport you to the Star Gazer when activated in your hand. \ + The Star Gazer has an aura that will heal you and damage opponents.
\ + Star Touch can now teleport you to the Star Gazer when activated in your hand.
\ Your cosmic expansion spell and your blades also become greatly empowered." + transmute_text = "Transmute three corpses with a star mark on them." gain_text = "The Beast held out its hand, I grabbed hold and they pulled me to them. Their body was towering, but it seemed so small and feeble after all their tales compiled in my head. \ I clung on to them, they would protect me, and I would protect it. \ I closed my eyes with my head laid against their form. I was safe. \ @@ -314,14 +327,14 @@ var/mob/living/to_reset = bad_dog.resolve() - to_chat(owner, span_hierophant("You prompt [to_reset] to shift it\'s personality...")) + to_chat(owner, span_mansus("You prompt [to_reset] to shift it\'s personality...")) var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as [span_danger("[owner.real_name]'s")] [span_notice(to_reset.name)]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, alert_pic = to_reset, jump_target = owner, role_name_text = to_reset.name, amount_to_pick = 1) if(isnull(chosen_one)) - to_chat(owner, span_hierophant("Your request to shift [to_reset]'\s personality appears to have been denied... Looks like you're stuck with it for now.")) + to_chat(owner, span_mansus("Your request to shift [to_reset]'\s personality appears to have been denied... Looks like you're stuck with it for now.")) StartCooldown() return FALSE - to_chat(to_reset, span_hierophant("Your summoner reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")) - to_chat(owner, span_hierophant("The mind of [to_reset] has twisted itself to suit you better.")) + to_chat(to_reset, span_mansus("Your summoner reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")) + to_chat(owner, span_mansus("The mind of [to_reset] has twisted itself to suit you better.")) message_admins("[key_name_admin(chosen_one)] has taken control of ([ADMIN_LOOKUPFLW(to_reset)])") to_reset.ghostize(FALSE) to_reset.PossessByPlayer(chosen_one.key) diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm index 6adb49cc85fa..97bde95a079c 100644 --- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm @@ -51,9 +51,10 @@ /datum/heretic_knowledge/limited_amount/starting/base_flesh name = "Principle of Hunger" - desc = "Opens up the Path of Flesh to you. \ - Allows you to transmute a knife and a pool of blood into a Bloody Blade. \ + desc = "Opens up the Path of Flesh to you.
\ + Allows you to create Bloody Blades. \ You can only create three at a time." + transmute_text = "Transmute a knife and a pool of blood." gain_text = "Hundreds of us starved, but not me... I found strength in my greed." required_atoms = list( /obj/item/knife = 1, @@ -72,7 +73,7 @@ summon_objective.owner = our_heretic.owner our_heretic.objectives += summon_objective - to_chat(user, span_hierophant("Undertaking the Path of Flesh, you are given another objective.")) + to_chat(user, span_mansus("Undertaking the Path of Flesh, you are given another objective.")) our_heretic.owner.announce_objectives() /datum/heretic_knowledge/limited_amount/starting/base_flesh/on_mansus_grasp(mob/living/source, mob/living/target) @@ -128,10 +129,11 @@ /datum/heretic_knowledge/limited_amount/flesh_ghoul name = "Imperfect Ritual" - desc = "Allows you to transmute a corpse and a poppy to create a Voiceless Dead. \ - The corpse does not need to have a soul. \ + desc = "Enchant a corpse into a Voiceless Dead. \ + The corpse does not need to have a soul.
\ Voiceless Dead are mute ghouls and only have 50 health, but can use Bloody Blades effectively. \ You can only create two at a time." + transmute_text = "Transmute a corpse and a poppy." gain_text = "I found notes of a dark ritual, unfinished... yet still, I pushed forward." required_atoms = list( /mob/living/carbon/human = 1, @@ -212,21 +214,23 @@ /datum/heretic_knowledge/spell/flesh_surgery name = "Knitting of Flesh" - desc = "Grants you the spell Knit Flesh. This spell allows you to remove organs from victims \ - without requiring a lengthy surgery. This process is much longer if the target is not dead. \ + desc = "Grants you the spell Knit Flesh.
This spell allows you to remove organs from victims \ + without requiring a lengthy surgery.
This process is much longer if the target is not dead.
\ This spell also allows you to heal your minions and summons, or restore failing organs to acceptable status." gain_text = "But they were not out of my reach for long. With every step, the screams grew, until at last \ I learned that they could be silenced." action_to_add = /datum/action/cooldown/spell/touch/flesh_surgery cost = 2 drafting_tier = 5 + max_charges = INFINITY /datum/heretic_knowledge/armor/flesh - desc = "Allows you to transmute a table (or a suit), a mask and a pool of blood to create a writhing embrace. \ - It grants you the ability to detect the health condition of other living (and non-living) and an aura that slowly heals your summons. \ - Acts as a focus while hooded." + desc = "Create a Writhing Embrace.
\ + It grants you the ability to detect the health condition of other living (and non-living) \ + and an aura that slowly heals your summons." + transmute_text = "Transmute a table (or a suit), a mask and a pool of blood." gain_text = "I tugged these wretched, slothing things about me, like one might a warm blanket. \ - With eyes-not-mine, they will witness. With teeth-not-mine, they will clench. With limbs-not-mine, they will break." + With eyes-not-mine, they will witness. With teeth-not-mine, they will clench. With limbs-not-mine, they will break." result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/flesh) research_tree_icon_state = "flesh_armor" required_atoms = list( @@ -237,9 +241,10 @@ /datum/heretic_knowledge/summon/raw_prophet name = "Raw Ritual" - desc = "Allows you to transmute a pair of eyes, a left arm, and a pool of blood to create a Raw Prophet. \ + desc = "Summon a Raw Prophet.
\ Raw Prophets have a greatly increased sight range and x-ray vision, as well as a long range jaunt and \ the ability to link minds to communicate with ease, but are very fragile and weak in combat." + transmute_text = "Transmute a pair of eyes, a left arm, and a pool of blood." gain_text = "I could not continue alone. I was able to summon The Uncanny Man to help me see more. \ The screams... once constant, now silenced by their wretched appearance. Nothing was out of reach." required_atoms = list( @@ -273,8 +278,9 @@ /datum/heretic_knowledge/summon/stalker name = "Lonely Ritual" - desc = "Allows you to transmute a tail of any kind, a stomach, a tongue, a pen and a piece of paper to create a Stalker. \ + desc = "Summon a Stalker.
\ Stalkers can jaunt, release EMPs, shapeshift into animals or automatons, and are strong in combat." + transmute_text = "Transmute a tail of any kind, a stomach, a tongue, a pen and a piece of paper." gain_text = "I was able to combine my greed and desires to summon an eldritch beast I had never seen before. \ An ever shapeshifting mass of flesh, it knew well my goals. The Marshal approved." @@ -293,14 +299,14 @@ /datum/heretic_knowledge/ultimate/flesh_final name = "Priest's Final Hymn" - desc = "The ascension ritual of the Path of Flesh. \ - Bring 4 corpses to a transmutation rune to complete the ritual. \ + desc = "The ascension ritual of the Path of Flesh.
\ When completed, you gain the ability to shed your human form \ and become the Lord of the Night, a supremely powerful creature. \ - Just the act of transforming causes nearby heathens great fear and trauma. \ - While in the Lord of the Night form, you can consume arms to heal and regain segments. \ + Just the act of transforming causes nearby heathens great fear and trauma.
\ + While in the Lord of the Night form, you can consume arms to heal and regain segments.
\ Additionally, you can summon three times as many Ghouls and Voiceless Dead, \ and can create unlimited blades to arm them all." + transmute_text = "Transmute four corpses." gain_text = "With the Marshal's knowledge, my power had peaked. The throne was open to claim. \ Men of this world, hear me, for the time has come! The Marshal guides my army! \ Reality will bend to THE LORD OF THE NIGHT or be unraveled! WITNESS MY ASCENSION!" diff --git a/code/modules/antagonists/heretic/knowledge/heretic_armor_knowledge.dm b/code/modules/antagonists/heretic/knowledge/heretic_armor_knowledge.dm index 243c84b2ed91..3797909b34a6 100644 --- a/code/modules/antagonists/heretic/knowledge/heretic_armor_knowledge.dm +++ b/code/modules/antagonists/heretic/knowledge/heretic_armor_knowledge.dm @@ -1,7 +1,8 @@ /datum/heretic_knowledge/armor name = "Armorer's Ritual" - desc = "Allows you to transmute a table (or a suit) and a mask to create Eldritch Armor. \ + desc = "Create a set of Eldritch Armor. \ Eldritch Armor provides great protection while also acting as a focus when hooded." + transmute_text = "Transmute a table (or a suit) and a mask." gain_text = "The Rusted Hills welcomed the Blacksmith in their generosity. And the Blacksmith \ returned their generosity in kind." diff --git a/code/modules/antagonists/heretic/knowledge/lock_lore.dm b/code/modules/antagonists/heretic/knowledge/lock_lore.dm index f83bfbdb4b2f..25e2af0ac6c6 100644 --- a/code/modules/antagonists/heretic/knowledge/lock_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/lock_lore.dm @@ -47,10 +47,11 @@ /datum/heretic_knowledge/limited_amount/starting/base_lock name = "A Steward's Secret" - desc = "Opens up the Path of Lock to you. \ - Allows you to transmute a knife and a crowbar into a Key Blade. \ + desc = "Opens up the Path of Lock to you.
\ + Allows you to create Key Blades. \ You can only create two at a time and they function as fast crowbars. \ In addition, they can fit into utility belts." + transmute_text = "Transmute a knife and a crowbar." gain_text = "The Locked Labyrinth leads to freedom. But only the trapped Stewards know the correct path." required_atoms = list( /obj/item/knife = 1, @@ -118,13 +119,16 @@ return COMPONENT_USE_HAND /datum/heretic_knowledge/key_ring - name = "Key Keeper’s Burden" - desc = "Allows you to transmute a wallet, an iron rod, and an ID card to create an Eldritch Card. \ - Hit a pair of airlocks with it to create a pair of portals, which will teleport you between them, but teleport non-heretics randomly. \ + name = "Key Keeper's Burden" + desc = "Enchants an ID card into an Eldritch Card.
\ + Hit a pair of airlocks with it to create a pair of portals, \ + which will teleport you between them, but teleport non-heretics randomly. \ You can ctrl-click the card to invert this behavior for created portals. \ - Each card may only sustain a single pair of portals at the same time. \ + Each card may only sustain a single pair of portals at the same time.
\ It also functions and appears the same as a regular ID Card. \ - Attacking it with a normal ID card consumes it and gains its access, and you can use it in-hand to change its appearance to a card you fused." + Attacking it with a normal ID card consumes it and gains its access, \ + and you can use it in-hand to change its appearance to a card you fused." + transmute_text = "Transmute a wallet, an iron rod, and an ID card." gain_text = "The Keeper sneered. \"These plastic rectangles are a mockery of keys, and I curse every door that desires them.\"" required_atoms = list( /obj/item/storage/wallet = 1, @@ -150,8 +154,10 @@ /datum/heretic_knowledge/limited_amount/concierge_rite name = "Concierge's Rite" - desc = "Allows you to transmute a crayon, a wooden plank, and a multitool to create a Labyrinth Handbook. \ - It can materialize a barricade at range that only you and people resistant to magic can pass. Has 5 charges which regenerate over time." + desc = "Author a Labyrinth Handbook.
\ + It can materialize a barricade at range that only you and people resistant to magic can pass.
\ + Has 5 charges which regenerate over time." + transmute_text = "Transmute a crayon, a wooden plank, and a multitool." gain_text = "The Concierge scribbled my name into the Handbook. \"Welcome to your new home, fellow Steward.\"" required_atoms = list( /obj/item/toy/crayon = 1, @@ -165,12 +171,12 @@ drafting_tier = 5 /datum/heretic_knowledge/armor/lock - desc = "Allows you to transmute a table (or a suit), a mask and a crowbar to create a shifting guise. \ - It grants you camoflage from cameras, hides your identity, voice and muffles your footsteps. \ - Acts as a focus while hooded." + desc = "Create a Shifting Guise.
\ + It grants you camouflage from cameras, hides your identity, voice and muffles your footsteps." + transmute_text = "Transmute a table (or a suit), a mask and a crowbar." gain_text = "While stewards are known to the Concierge, \ - they still consort between one another and with outsiders under shaded cloaks and drawn hoods. \ - Familiarity is treachery, even to oneself." + they still consort between one another and with outsiders under shaded cloaks and drawn hoods. \ + Familiarity is treachery, even to oneself." result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/lock) research_tree_icon_state = "lock_armor" required_atoms = list( @@ -184,9 +190,13 @@ desc = "Grants you Burglar's Finesse, a single-target spell \ that puts a random item from the victims backpack into your hand." gain_text = "Consorting with Burglar spirits is frowned upon, but a Steward will always want to learn about new doors." - + required_atoms = list(/obj/item/clothing/gloves = 1) action_to_add = /datum/action/cooldown/spell/pointed/burglar_finesse cost = 2 + max_charges = 12 + focus_recharge_amount = 0.33 + holywater_drain_amount = 0.33 + transmute_text = "Can be manually recharged by completing a ritual with a pair of gloves." /datum/heretic_knowledge/blade_upgrade/flesh/lock name = "Opening Blade" @@ -202,25 +212,29 @@ return ..() /datum/heretic_knowledge/spell/caretaker_refuge - name = "Caretaker’s Last Refuge" - desc = "Gives you a spell that makes you transparent and not dense. Cannot be used near living sentient beings. \ - While in refuge, you cannot use your hands or spells, and you are immune to slowdown. \ - You are invincible but unable to harm anything. Cancelled by being hit with an anti-magic item." + name = "Caretaker's Last Refuge" + desc = "Gives you a spell that makes you transparent and not dense.
\ + While the spell is active, you cannot use your hands or other spells, and you are immune to slowdown. \ + You are fully invincible but unable to harm anything. " gain_text = "Jealously, the Guard and the Hound hunted me. But I unlocked my form, and was but a haze, untouchable." action_to_add = /datum/action/cooldown/spell/caretaker cost = 2 is_final_knowledge = TRUE + max_charges = 3 + path_recharge_amount = 0.66 + holywater_drain_amount = 0.33 + notice = "• Cannot be used near living sentient beings.
• Cancelled if you are hit with an anti-magic item." /datum/heretic_knowledge/ultimate/lock_final name = "Unlock the Labyrinth" desc = "The ascension ritual of the Path of Knock. \ - Bring 3 corpses without organs in their torso to a transmutation rune to complete the ritual. \ When completed, you gain the ability to transform into empowered eldritch creatures \ and your keyblades will become even deadlier. \ In addition, you will create a tear to the Labyrinth's heart; \ a tear in reality located at the site of this ritual. \ Eldritch creatures will endlessly pour from this rift \ who are bound to obey your instructions." + transmute_text = "Transmute three corpses without organs in their torso." gain_text = "The Stewards guided me, and I guided them. \ My foes were the Locks and my blades were the Key! \ The Labyrinth will be Locked no more, and freedom will be ours! WITNESS US!" diff --git a/code/modules/antagonists/heretic/knowledge/moon_lore.dm b/code/modules/antagonists/heretic/knowledge/moon_lore.dm index 533bc1f5c02b..d944431d3ee6 100644 --- a/code/modules/antagonists/heretic/knowledge/moon_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/moon_lore.dm @@ -53,9 +53,10 @@ /datum/heretic_knowledge/limited_amount/starting/base_moon name = "Moonlight Troupe" - desc = "Opens up the Path of Moon to you. \ - Allows you to transmute 2 sheets of glass and a knife into an Lunar Blade. \ + desc = "Opens up the Path of Moon to you.
\ + Allows you to create Lunar Blades. \ You can only create two at a time." + transmute_text = "Transmute 2 sheets of glass and a knife." gain_text = "Under the light of the moon the laughter echoes." required_atoms = list( /obj/item/knife = 1, @@ -90,20 +91,24 @@ /datum/heretic_knowledge/spell/mind_gate name = "Mind Gate" desc = "Grants you Mind Gate, a spell which mutes, deafens, blinds, inflicts hallucinations, \ - confusion, oxygen loss and brain damage to its target over 10 seconds.\ - The caster takes 20 brain damage per use." + confusion, oxygen loss and brain damage to its target over 10 seconds.
\ + Casting the spell causes brain damage." gain_text = "My mind swings open like a gate, and its insight will let me perceive the truth." - action_to_add = /datum/action/cooldown/spell/pointed/mind_gate cost = 2 + max_charges = 6 + path_recharge_amount = 0.33 + focus_recharge_amount = 0.33 + holywater_drain_amount = 0.33 /datum/heretic_knowledge/moon_amulet name = "Moonlight Amulet" - desc = "Allows you to transmute 2 sheets of glass, a heart and a tie to create a Moonlight Amulet. \ - If the item is used on someone with low sanity they go berserk attacking everyone, \ - if their sanity isn't low enough it decreases their mood. \ - Wearing this will grant you the ability to see heathens through walls and make your blades harmless, they will instead directly attack their mind. \ - Provides thermal vision and doubles the brain regen of a moon heretic while worn." + desc = "Sculpt a Moonlight Amulet.
\ + If the item is used on someone with low sanity, they go berserk - attacking anyone nearby. \ + If their sanity isn't low enough, it decreases their mood.
\ + Wearing yourself this will heal your brain damage and grant you the ability to see heathens through walls, \ + but makes your blades harmless - instead damaging their mind directly." + transmute_text = "Transmute 2 sheets of glass, a heart and a tie." gain_text = "At the head of the parade he stood, the moon condensed into one mass, a reflection of the soul." required_atoms = list( @@ -111,7 +116,7 @@ /obj/item/stack/sheet/glass = 2, /obj/item/clothing/neck/tie = 1, ) - result_atoms = list(/obj/item/clothing/neck/heretic_focus/moon_amulet) + result_atoms = list(/obj/item/clothing/neck/moon_amulet) cost = 2 research_tree_icon_path = 'icons/obj/antags/eldritch.dmi' @@ -119,12 +124,13 @@ research_tree_icon_frame = 9 /datum/heretic_knowledge/armor/moon - desc = "Allows you to transmute a table (or a suit), a mask and two sheets of glass to create a Resplendant Regalia. \ - This robe will render the user fully immune to disabling effects and convert all forms of damage into brain damage, while also pacifying the user and rendering them unable to use ranged weapons. \ - A Moonlight Amulet will be necessary to use blades while wearing it." + desc = "Create a Resplendant Regalia.
While worn, renders you fully immune to disabling effects \ + While worn, pacifies you, while also rendering you fully immune to disabling effects and converting all forms of damage into brain damage." gain_text = "Trails of light and mirth flowed from every arm of this magnificent attire. \ - The troupe twirled in irridescent cascades, dazzling onlookers with the truth they sought. \ - I observed, basking in the light, to find my self." + The troupe twirled in irridescent cascades, dazzling onlookers with the truth they sought. \ + I observed, basking in the light, to find my self." + notice = "Despite the robe's pacifying effect, you can still use your Moon Blades, provided you ALSO wear a Moonlight Amulet." + transmute_text = "Transmute a table (or a suit), a mask and two sheets of glass." result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon) research_tree_icon_state = "moon_armor" required_atoms = list( @@ -135,17 +141,23 @@ /datum/heretic_knowledge/spell/moon_parade name = "Lunar Parade" - desc = "Grants you Lunar Parade, a spell that - after a short charge - sends a carnival forward \ - when hitting someone they are forced to join the parade and suffer hallucinations." + desc = "Grants you Lunar Parade, a spell that - after a short charge - fires a projectile.
\ + Anyone hit by it is forced to join the parade, following the projectile while suffering hallucinations." gain_text = "The music like a reflection of the soul compelled them, like moths to a flame they followed" action_to_add = /datum/action/cooldown/spell/pointed/projectile/moon_parade + notice = "There is no cap to the number of charges on the spell from applying Moonlight Amulets." cost = 2 drafting_tier = 5 + max_charges = 4 + path_recharge_amount = 0.25 + focus_recharge_amount = 0.25 + holywater_drain_amount = 0.25 + path_recharge_can_surpass_cap = TRUE /datum/heretic_knowledge/blade_upgrade/moon name = "Moonlight Blade" - desc = "Your blade now deals brain damage, causes random hallucinations and does sanity damage. \ - Deals more brain damage if your victim is insane or unconscious." + desc = "Your blade now deals brain damage, causes random hallucinations and does sanity damage.
\ + Deals more brain damage if your victim is insane or unconscious." gain_text = "His wit was sharp as a blade, cutting through the lie to bring us joy." research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi' @@ -171,25 +183,27 @@ /datum/heretic_knowledge/spell/moon_ringleader name = "Ringleaders Rise" - desc = "Grants you Ringleaders Rise, an AoE spell that deals more brain damage the lower the sanity of everyone in the AoE \ - and causes hallucinations, with those who have less sanity getting more. \ - If their sanity is low enough this turns them insane, the spell then halves their sanity." + desc = "Grants you Ringleaders Rise, an AoE spell that deals brain damage and causes hallucinations, scaling with sanity." gain_text = "I grabbed his hand and we rose, those who saw the truth rose with us. \ The ringleader pointed up and the dim light of truth illuminated us further." - + notice = "There is no cap to the number of charges on the spell from applying Moonlight Amulets." action_to_add = /datum/action/cooldown/spell/aoe/moon_ringleader cost = 2 - research_tree_icon_frame = 5 is_final_knowledge = TRUE + max_charges = 2 + path_recharge_amount = 0.25 + focus_recharge_amount = 0.25 + holywater_drain_amount = 0.25 + path_recharge_can_surpass_cap = TRUE /datum/heretic_knowledge/ultimate/moon_final name = "The Last Act" - desc = "The ascension ritual of the Path of Moon. \ - Bring 3 corpses with more than 50 brain damage to a transmutation rune to complete the ritual. \ + desc = "The ascension ritual of the Path of Moon.
\ When completed, you become a harbinger of madness gaining and aura of passive sanity decrease, \ - crewmembers with low enough sanity will be converted into acolytes. \ + crewmembers with low enough sanity will be converted into acolytes.
\ 1/5th of the crew will turn into acolytes and follow your command, they will all receive moonlight amulets." + transmute_text = "Transmute 3 corpses with more than 50 brain damage." gain_text = "We dived down towards the crowd, his soul splitting off in search of greater venture \ for where the Ringleader had started the parade, I shall continue it unto the suns demise \ WITNESS MY ASCENSION, THE MOON SMILES ONCE MORE AND FOREVER MORE IT SHALL!" @@ -252,7 +266,7 @@ var/datum/antagonist/lunatic/lunatic = convertee.mind.add_antag_datum(/datum/antagonist/lunatic) lunatic.set_master(user.mind, user) - var/obj/item/clothing/neck/heretic_focus/moon_amulet/amulet = new(convertee.drop_location()) + var/obj/item/clothing/neck/moon_amulet/amulet = new(convertee.drop_location()) var/static/list/slots = list( LOCATION_NECK, LOCATION_HANDS, diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm index 565879a7c8c3..cada5883f386 100644 --- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm @@ -52,9 +52,10 @@ /datum/heretic_knowledge/limited_amount/starting/base_rust name = "Blacksmith's Tale" - desc = "Opens up the Path of Rust to you. \ - Allows you to transmute a knife with any trash item into a Rusty Blade. \ + desc = "Opens up the Path of Rust to you.
\ + Allows you to create Rusty Blades. \ You can only create two at a time." + transmute_text = "Transmute a knife with any trash item." gain_text = "\"Let me tell you a story\", said the Blacksmith, as he gazed deep into his rusty blade." required_atoms = list( /obj/item/knife = 1, @@ -104,28 +105,34 @@ /datum/heretic_knowledge/spell/rust_charge name = "Rust Charge" - desc = "A charge that must be started on a rusted tile and will destroy any rusted objects you come into contact with, will deal high damage to others and rust around you during the charge." + desc = "Allows you to muster a mighty charge while standing on rusted tile.
\ + As you charge, you will spread rust, destroy any rusted objects, and deal high damage to anyone you collide with." gain_text = "The hills sparkled now, as I neared them my mind began to wander. I quickly regained my resolve and pushed forward, this last leg would be the most treacherous." action_to_add = /datum/action/cooldown/mob_cooldown/charge/rust cost = 2 is_final_knowledge = TRUE + max_charges = INFINITY /datum/heretic_knowledge/spell/rust_construction name = "Rust Construction" - desc = "Grants you Rust Construction, a spell that allows you to raise a wall out of a rusted floor. \ + desc = "Grants you Rust Construction, a spell that allows you to raise a wall out of a rusted floor.
\ Anyone overtop the wall will be throw aside (or upwards) and sustain damage." gain_text = "Images of foreign and ominous structures began to dance in my mind. Covered head to toe in thick rust, \ they no longer looked man made. Or perhaps they never were in the first place." action_to_add = /datum/action/cooldown/spell/pointed/rust_construction cost = 2 + max_charges = 20 + path_recharge_amount = 0.2 + focus_recharge_amount = 0.5 + holywater_drain_amount = 0.1 /datum/heretic_knowledge/armor/rust - desc = "Allows you to transmute a table (or a suit), a mask and any trash item to create a Salvaged Remains. \ - Has extra armor, tackle resistance and syringe immunity while standing on rust. \ - Acts as a focus while hooded." + desc = "Create a Salvaged Remains.
\ + Has extra armor, tackle resistance and syringe immunity while standing on rust." + transmute_text = "Transmute a table (or a suit), a mask and any trash item." gain_text = "From beneath warped scrap, the Blacksmith pulls forth an ancient fabric. \ - \"Whatever this once stood for is lost. So now, we give it new purpose.\"" + \"Whatever this once stood for is lost. So now, we give it new purpose.\"" result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust) research_tree_icon_state = "rust_armor" required_atoms = list( @@ -136,16 +143,24 @@ /datum/heretic_knowledge/spell/area_conversion name = "Aggressive Spread" - desc = "Grants you Aggressive Spread, a spell that spreads rust to nearby surfaces. \ - Already rusted surfaces are destroyed \ Also improves the rusting abilities of non rust-heretics." + desc = "Grants you Aggressive Spread, a spell that spreads rust to nearby surfaces.
\ + Already rusted surfaces are destroyed.
Improves the rusting abilities of non rust-heretics." gain_text = "All wise men know well not to visit the Rusted Hills... Yet the Blacksmith's tale was inspiring." + required_atoms = list( + /obj/item/storage/toolbox = 1, + ) action_to_add = /datum/action/cooldown/spell/aoe/rust_conversion cost = 2 research_tree_icon_frame = 5 + max_charges = 12 + path_recharge_amount = 0.25 + focus_recharge_amount = 0.33 + holywater_drain_amount = 0.16 + transmute_text = "Can be manually recharged by completing a ritual with a toolbox." /datum/heretic_knowledge/blade_upgrade/rust name = "Toxic Blade" - desc = "Your Rusty Blade now disgusts enemies on attack \ Allows you to rust Titanium and Plastitanium.." + desc = "Your Rusty Blade now disgusts enemies on attack.
Allows you to rust Titanium and Plastitanium." gain_text = "The Blacksmith hands you their blade. \"The Blade will guide you through the flesh, should you let it.\" \ The heavy rust weights it down. You stare deeply into it. The Rusted Hills call for you, now." research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi' @@ -161,23 +176,28 @@ /datum/heretic_knowledge/spell/entropic_plume name = "Entropic Plume" - desc = "Grants you Entropic Plume, a spell that releases a vexing wave of Rust. \ + desc = "Grants you Entropic Plume, a spell that releases a vexing wave of Rust.
\ Blinds, poisons, and inflicts Amok on any heathen it hits, causing them to strike \ - at friend or foe wildly. Also rusts and destroys and surfaces it hits and improves the rusting abilities of non-rust heretics." + at friend or foe wildly.
Also rusts and destroys and surfaces it hits \ + and improves the rusting abilities of non-rust heretics." gain_text = "The corrosion was unstoppable. The rust was unpleasable. \ The Blacksmith was gone, and you hold their blade. Champions of hope, the Rustbringer is nigh!" action_to_add = /datum/action/cooldown/spell/cone/staggered/entropic_plume cost = 2 drafting_tier = 5 + max_charges = 4 + path_recharge_amount = 0.25 + focus_recharge_amount = 0.25 + holywater_drain_amount = 0.25 /datum/heretic_knowledge/ultimate/rust_final name = "Rustbringer's Oath" - desc = "The ascension ritual of the Path of Rust. \ - Bring 3 corpses to a transmutation rune on the bridge of the station to complete the ritual. \ - When completed, the ritual site will endlessly spread rust onto any surface, stopping for nothing. \ + desc = "The ascension ritual of the Path of Rust.
\ + When completed, the ritual site will endlessly spread rust onto any surface, stopping for nothing.
\ Additionally, you will become extremely resilient on rust, healing at triple the rate \ and becoming immune to many effects and dangers \ You will be able to rust almost anything upon ascending." + transmute_text = "Transmute 3 corpses on the bridge of the station." gain_text = "Champion of rust. Corruptor of steel. Fear the dark, for the RUSTBRINGER has come! \ The Blacksmith forges ahead! Rusted Hills, CALL MY NAME! WITNESS MY ASCENSION!" diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/reroll_targets.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/reroll_targets.dm index 521ac1d412ee..f905e04c7439 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/reroll_targets.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/reroll_targets.dm @@ -4,8 +4,8 @@ /datum/heretic_knowledge/reroll_targets name = "The Relentless Heartbeat" - desc = "Allows you transmute a harebell, a book, and a jumpsuit while standing over a rune \ - to reroll your sacrifice targets." + desc = "Allows you to reroll your sacrifice targets." + transmute_text = "Transmute a harebell, a book, and a jumpsuit." gain_text = "The heart is the principle that continues and preserves." required_atoms = list( /obj/item/food/grown/flower/harebell = 1, diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_curse.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_curse.dm index ecc12f72554b..0857a06974e2 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_curse.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_curse.dm @@ -43,7 +43,7 @@ /// If we attack the guy who cursed us, that's no good /datum/status_effect/heretic_curse/proc/on_curser_attacked(datum/source, mob/attacker) SIGNAL_HANDLER - if (attacker != owner || !HAS_TRAIT(source, TRAIT_ALLOW_HERETIC_CASTING)) + if (attacker != owner) return log_combat(owner, the_curser, "attacked", addition = "and lost some organs because they had previously been sacrificed by them.") experience_the_consequences() @@ -51,7 +51,7 @@ /// If we are attacked by the guy who cursed us, that's also no good /datum/status_effect/heretic_curse/proc/on_owner_attacked(datum/source, mob/attacker) SIGNAL_HANDLER - if (attacker != the_curser || !HAS_TRAIT(attacker, TRAIT_ALLOW_HERETIC_CASTING)) + if (attacker != the_curser) return log_combat(the_curser, owner, "attacked", addition = "and as they had previously sacrificed them, removed some of their organs.") experience_the_consequences() diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm index b7b544bf23c7..dcf57d12a3f3 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm @@ -10,8 +10,8 @@ */ /datum/heretic_knowledge/hunt_and_sacrifice name = "Heartbeat of the Mansus" - desc = "Allows you to sacrifice targets to the Mansus by bringing them to a rune in critical (or worse) condition. \ - If you have no targets, stand on a transmutation rune and invoke it to acquire some." + desc = "Allows you to sacrifice targets to the Mansus by bringing them to a rune in critical (or worse) condition." + notice = "If you have no targets, stand on a transmutation rune and invoke it to acquire some." required_atoms = list(/mob/living/carbon/human = 1) cost = 0 priority = MAX_KNOWLEDGE_PRIORITY // Should be at the top @@ -39,6 +39,7 @@ /obj/item/organ/stomach/corrupt, /obj/item/organ/tongue/corrupt, ) + var/backdoor_sacrifice_attempts = 0 /datum/heretic_knowledge/hunt_and_sacrifice/Destroy(force) heretic_mind = null @@ -79,6 +80,30 @@ atoms += user return TRUE + if(istype(get_area(loc), /area/centcom/heretic_backdoor)) + loc.balloon_alert(user, "ritual failed, invalid location!") + switch(backdoor_sacrifice_attempts) + if(0) + to_chat(user, span_mansus("Attempting a sacrifice so close to the gods is risky...")) + if(1) + to_chat(user, span_mansus("You hear a knocking sound[HAS_TRAIT(user, TRAIT_DEAF) ? ", despite your deafness" : ""]...")) + if(2) + to_chat(user, span_mansus("The knocking grows louder...")) + user.soundbang_act(SOUNDBANG_NORMAL, deafen_pwr = 10 SECONDS, stun_pwr = 1 SECONDS, damage_pwr = 10, ignore_deafness = TRUE) + if(3) + to_chat(user, span_mansus("The knocking becomes deafening!")) + user.soundbang_act(SOUNDBANG_OVERWHELMING, deafen_pwr = 20 SECONDS, stun_pwr = 4 SECONDS, damage_pwr = 20, ignore_deafness = TRUE) + if(4) + if(begin_sacrifice(user)) + to_chat(user, span_mansus("Your insolence is punished!")) + else + to_chat(user, span_mansus("The knocking stops - but you can't help but feel like you've dodged a bullet, somehow.")) + if(5 to INFINITY) + to_chat(user, span_mansus("You don't think trying it again would provide any more insight...")) + + backdoor_sacrifice_attempts++ + return FALSE + // If we have targets, we can check to see if we can do a sacrifice // Let's remove any humans in our atoms list that aren't a sac target for(var/mob/living/carbon/human/sacrifice in atoms) @@ -135,7 +160,7 @@ if(!length(valid_targets)) if(!silent) - to_chat(user, span_hierophant_warning("No sacrifice targets could be found!")) + to_chat(user, span_mansus("No sacrifice targets could be found!")) return FALSE // Now, let's try to get four targets. @@ -209,6 +234,7 @@ var/datum/antagonist/cult/cultist_datum = GET_CULTIST(sacrifice) // Heads give 3 points, cultists give 1 point (and a special reward), normal sacrifices give 2 points. heretic_datum.total_sacrifices++ + SEND_SIGNAL(heretic_datum, COMSIG_HERETIC_SACRIFICE, sacrifice, (sac_job_flag & JOB_HEAD_OF_STAFF)) if((sac_job_flag & JOB_HEAD_OF_STAFF)) heretic_datum.adjust_knowledge_points(3) heretic_datum.high_value_sacrifices++ @@ -247,7 +273,7 @@ // Visible and audible encouragement! to_chat(user, span_big(span_hypnophrase("A servant of the Sanguine Apostate!"))) - to_chat(user, span_hierophant("Your patrons are rapturous!")) + to_chat(user, span_mansus("Your patrons are rapturous!")) playsound(sacrifice, 'sound/effects/magic/disintegrate.ogg', 75, TRUE) // Drop all items and splatter them around messily. @@ -537,7 +563,7 @@ composed_return_message += span_green("alive, but with a shattered mind. ") composed_return_message += span_notice("You hear a whisper... ") - composed_return_message += span_hypnophrase(get_area_name(safe_turf, TRUE)) + composed_return_message += span_mansus(get_area_name(safe_turf, TRUE)) to_chat(heretic_mind.current, composed_return_message) /** diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm index 726665f97dfd..6e5006d97020 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm @@ -60,7 +60,7 @@ GLOBAL_LIST_EMPTY(heretic_sacrifice_landmarks) // Rooms for where heretic sacrifices send people. /area/centcom/heretic_sacrifice - name = "Mansus" + name = "Mansus Gate" icon_state = "heretic" default_gravity = STANDARD_GRAVITY ambience_index = AMBIENCE_SPOOKY diff --git a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_four.dm b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_four.dm index 87345d7ed0c4..51a8f8a4ecc3 100644 --- a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_four.dm +++ b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_four.dm @@ -4,7 +4,7 @@ /datum/heretic_knowledge/spell/space_phase name = "Space Phase" - desc = "Grants you Space Phase, a spell that allows you to move freely through space. \ + desc = "Grants you Space Phase, a spell that allows you to move freely through space.
\ You can only phase in and out when you are on a space or misc turf." gain_text = "You feel like your body can move through space as if you where dust." @@ -12,12 +12,37 @@ cost = 2 research_tree_icon_frame = 6 drafting_tier = 4 + max_charges = 2 + path_recharge_amount = 0.0 + holywater_drain_amount = 0.5 + transmute_text = "To recharge, crush a bluespace crystal while standing over a rune." + /// Tracks tim ein EVA + var/seconds_in_eva = 0 + +/datum/heretic_knowledge/spell/space_phase/on_gain(mob/user, datum/antagonist/heretic/our_heretic) + . = ..() + RegisterSignal(user, COMSIG_MOB_CRUSHED_BLUESPACE_CRYSTAL, PROC_REF(on_crystal_crushed)) + +/datum/heretic_knowledge/spell/space_phase/on_lose(mob/user, datum/antagonist/heretic/our_heretic) + . = ..() + UnregisterSignal(user, COMSIG_MOB_CRUSHED_BLUESPACE_CRYSTAL) + +/datum/heretic_knowledge/spell/space_phase/proc/on_crystal_crushed(mob/living/source, obj/item/crystal) + SIGNAL_HANDLER + + var/obj/effect/heretic_rune/rune = locate() in view(1, source) + if(isnull(rune)) + return + + rune.ritual_animation() + add_charges(1) /datum/heretic_knowledge/unfathomable_curio name = "Unfathomable Curio" - desc = "Allows you to transmute 3 rods, lungs, and any belt into an Unfathomable Curio - \ - a belt that can hold blades and items for rituals. Whilst worn it will veil you, \ - blocking one blow of incoming damage, at the cost of the veil. The veil will recharge itself out of combat." + desc = "Fashion an Unfathomable Curio - \ + a belt that can hold blades and items for rituals.
Whilst worn it will veil you, \ + blocking one blow of incoming damage, at the cost of the veil. The veil will recharge itself out of combat." + transmute_text = "Transmute 3 rods, lungs, and any belt." gain_text = "The mansus holds many a curio, some are not meant for the mortal eye." required_atoms = list( @@ -33,7 +58,9 @@ /datum/heretic_knowledge/rust_sower name = "Rust Sower Grenade" - desc = "Allows you to combine a chemical grenade casing and some moldy food to conjure a cursed grenade filled with Eldritch Rust, upon detonating it releases a huge cloud that blinds organics, rusts affected turfs and obliterates Silicons and Mechs." + desc = "Conjure a cursed grenade filled with Eldritch Rust.
Upon detonation, it releases a huge cloud that \ + blinds organics, rusts affected turfs and obliterates silicons and mechs." + transmute_text = "Transmute a chemical grenade casing and some moldy food." gain_text = "The choked vines of the Rusted Hills are burdened with such overripe fruits. It undoes the markers of progress, leaving a clean slate to work into new shapes." required_atoms = list( list( @@ -54,21 +81,39 @@ /datum/heretic_knowledge/spell/crimson_cleave name = "Crimson Cleave" - desc = "Grants you Crimson Cleave, a targeted spell which siphons health in a small AOE. Cleanses all wounds upon casting" + desc = "Grants you Crimson Cleave, a targeted spell which siphons health in a small AoE.\ +
Also cleanses all wounds upon casting." gain_text = "At first I didn't understand these instruments of war, but the Priest \ - told me to use them regardless. Soon, he said, I would know them well." + told me to use them regardless. Soon, he said, I would know them well." + required_atoms = list( + list(/obj/effect/decal/cleanable/blood, /obj/item/rag, /obj/item/stack/medical/wrap/gauze) = 1, + ) action_to_add = /datum/action/cooldown/spell/pointed/crimson_cleave cost = 2 drafting_tier = 4 + max_charges = 3 + path_recharge_amount = 0.0 + focus_recharge_amount = 0.33 + holywater_drain_amount = 0.33 + +/datum/heretic_knowledge/spell/crimson_cleave/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) + . = ..() + for(var/obj/item/rag/rag in atoms) + if(!GET_ATOM_BLOOD_DNA_LENGTH(rag)) + atoms -= rag + + for(var/obj/item/stack/medical/wrap/gauze/medwrap in atoms) + if(!GET_ATOM_BLOOD_DNA_LENGTH(medwrap)) + atoms -= medwrap /datum/heretic_knowledge/rifle name = "Lionhunter's Rifle" - desc = "Allows you to transmute a piece of wood, with hide \ - from any animal, and a camera to create the Lionhunter's rifle. \ + desc = "Unleash the Lionhunter's rifle.
\ The Lionhunter's Rifle is a long ranged ballistic weapon with three shots. \ These shots function as normal, albeit weak high-caliber munitions when fired from \ close range or at inanimate objects. You can aim the rifle at distant foes, \ causing the shot to mark your victim with your grasp and teleport you directly to them." + transmute_text = "Transmute a piece of wood, hide from any animal, and a camera." gain_text = "I met an old man in an antique shop who wielded a very unusual weapon. \ I could not purchase it at the time, but they showed me how they made it ages ago." required_atoms = list( @@ -84,8 +129,8 @@ /datum/heretic_knowledge/rifle_ammo name = "Lionhunter Rifle Ammunition" - desc = "Allows you to transmute 3 ballistic ammo casings (used or unused) of any caliber, \ - including shotgun shells to create an extra clip of ammunition for the Lionhunter Rifle." + desc = "Create an extra clip of ammunition for the Lionhunter Rifle." + transmute_text = "Transmute 3 ballistic ammo casings (used or unused) of any caliber, including shotgun shells." gain_text = "The weapon came with three rough iron balls, intended to be used as ammunition. \ They were very effective, for simple iron, but used up quickly. I soon ran out. \ No replacement munitions worked in their stead. It was peculiar in what it wanted." @@ -107,6 +152,13 @@ CALIBER_HOOK, ) +/datum/heretic_knowledge/rifle_ammo/pre_research(mob/user, datum/antagonist/heretic/our_heretic) + if(!our_heretic.get_knowledge(/datum/heretic_knowledge/rifle)) + tgui_alert(user, "You must research the Lionhunter's Rifle knowledge before you can research its ammunition.") + return FALSE + + return TRUE + /datum/heretic_knowledge/rifle_ammo/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) for(var/obj/item/ammo_casing/casing in atoms) if(!(casing.caliber in caliber_blacklist)) @@ -114,7 +166,6 @@ // Remove any casings in the caliber_blacklist list from atoms atoms -= casing - // We removed any invalid casings from the atoms list, // return to allow the ritual to fill out selected atoms with the new list return TRUE diff --git a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_one.dm b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_one.dm index 787f554c28ea..2dddb2affe20 100644 --- a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_one.dm +++ b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_one.dm @@ -4,10 +4,10 @@ /datum/heretic_knowledge/void_cloak name = "Void Cloak" - desc = "Allows you to transmute a glass shard, a bedsheet, and any outer clothing item (such as armor or a suit jacket) \ - to create a Void Cloak. While the hood is down, the cloak functions as a focus and protects you from space. \ - While the hood is up, the cloak is completely invisible. It also provide decent armor and \ + desc = "Fashion a Void Cloak.
While the hood is down, protects you from space. \ + While the hood is up, the cloak is completely invisible.
It also provide decent armor and \ has pockets which can hold one of your blades, various ritual components (such as organs), and small heretical trinkets." + transmute_text = "Transmute a glass shard, a bedsheet, and any outer clothing item (such as armor or a suit jacket)." gain_text = "The Owl is the keeper of things that are not quite in practice, but in theory are. Many things are." required_atoms = list( /obj/item/shard = 1, @@ -22,8 +22,9 @@ /datum/heretic_knowledge/medallion name = "Ashen Eyes" - desc = "Allows you to transmute a pair of eyes, a candle, and a glass shard into an Eldritch Medallion. \ + desc = "Sculpt an Eldritch Medallion.
\ The Eldritch Medallion grants you thermal vision while worn, and also functions as a focus." + transmute_text = "Transmute a pair of eyes, a candle, and a glass shard." gain_text = "Piercing eyes guided them through the mundane. Neither darkness nor terror could stop them." required_atoms = list( /obj/item/organ/eyes = 1, @@ -38,8 +39,9 @@ /datum/heretic_knowledge/essence // AKA Eldritch Flask name = "Priest's Ritual" - desc = "Allows you to transmute a tank of water and a glass shard into a Flask of Eldritch Essence. \ + desc = "Fill a flask of Eldritch Essence.
\ Eldritch Essence can be consumed for potent healing, or given to heathens for deadly poisoning." + transmute_text = "Transmute a tank of water and a glass shard." gain_text = "This is an old recipe. The Owl whispered it to me. \ Created by the Priest - the Liquid that both was and is not." required_atoms = list( @@ -54,8 +56,8 @@ /datum/heretic_knowledge/phylactery name = "Phylactery of Damnation" - desc = "Allows you to transmute a sheet of glass and a poppy into a Phylactery that can instantly draw blood, even from long distances. \ - Be warned, your target may still feel a prick." + desc = "Create a Phylactery that can instantly draw blood, even from long distances." + transmute_text = "Transmute a sheet of glass and a poppy." gain_text = "A tincture twisted into the shape of a bloodsucker vermin. \ Whether it chose the shape for itself, or this is the humor of the sickened mind that conjured this vile implement into being is something best not pondered." required_atoms = list( @@ -67,11 +69,16 @@ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi' research_tree_icon_state = "phylactery_2" drafting_tier = 1 + notice = "Target of the Phylactery may feel a prick." /datum/heretic_knowledge/crucible name = "Mawed Crucible" - desc = "Allows you to transmute a portable water tank and a table to create a Mawed Crucible. \ - The Mawed Crucible can brew powerful potions for combat and utility, but must be fed bodyparts and organs between uses." + desc = "Create a Mawed Crucible.
\ + The Mawed Crucible can brew powerful but temporary potions for combat and utility, but must be fed bodyparts and organs between uses. \ +
• Brew of the Crucible Soul: Allows you to walk through walls. Returns you to the place you consumed the potion after it expires. \ +
• Brew of Dusk and Dawn: Allows you to see through walls. \ +
• Brew of the Wounded Soldier: Heals you over time. The more severe your wounds (such as fractures or cuts), the faster it heals you." + transmute_text = "Transmute a portable water tank and a table." gain_text = "This is pure agony. I wasn't able to summon the figure of the Aristocrat, \ but with the Priest's attention I stumbled upon a different recipe..." required_atoms = list( @@ -86,10 +93,10 @@ /datum/heretic_knowledge/eldritch_coin name = "Eldritch Coin" - desc = "Allows you to transmute a sheet of plasma and a diamond to create an Eldritch Coin. \ - The coin will open or close nearby doors when landing on heads and toggle their bolts \ - when landing on tails. If you insert the coin into an airlock, it will be consumed \ - to fry its electronics, opening the airlock permanently unless bolted. " + desc = "Create an Eldritch Coin.
\ + Flip the coin. On heads, nearby airlocks will open or close. On tails, nearby airlocks will bolt to their current state.
\ + If you insert the coin into an airlock, it will be consumed to fry its electronics, keeping it open or closed permanently until repaired." + transmute_text = "Transmute a sheet of plasma and a diamond." gain_text = "The Mansus is a place of all sorts of sins. But greed held a special role." required_atoms = list( /obj/item/stack/sheet/mineral/diamond = 1, @@ -109,9 +116,10 @@ */ /datum/heretic_knowledge/codex_cicatrix name = "Codex Cicatrix" - desc = "Allows you to transmute a book, any pen, and your pick from any carcass (animal or human), leather, or hide to create a Codex Cicatrix. \ - The Codex Cicatrix can be used when draining influences to gain additional knowledge, but comes at greater risk of being noticed. \ - It can also be used to draw and remove transmutation runes easier, and as a spell focus in a pinch." + desc = "Author the Codex Cicatrix.
\ + The Codex Cicatrix can be used when draining influences to gain additional knowledge, but comes at greater risk of being noticed.
\ + It can also be used to draw and remove transmutation runes easier, and can be opened to restore charges to your Mansus Grasp." + transmute_text = "Transmute a book, any pen, and your pick from any carcass (animal or human), leather" gain_text = "The occult leaves fragments of knowledge and power anywhere and everywhere. The Codex Cicatrix is one such example. \ Within the leather-bound faces and age old pages, a path into the Mansus is revealed." required_atoms = list( @@ -121,7 +129,6 @@ ) result_atoms = list(/obj/item/codex_cicatrix) cost = 1 - priority = MAX_KNOWLEDGE_PRIORITY - 4 drafting_tier = 1 is_shop_only = TRUE research_tree_icon_path = 'icons/obj/antags/eldritch.dmi' @@ -191,9 +198,10 @@ */ /datum/heretic_knowledge/bookworm name = "Warren King's Welcome" - desc = "Allows you to transmute 10 cable pieces, a piece of paper, and a multitool to brand nearby ID cards and airlocks. \ - Branded ID cards will gain access to maintenance, external airlocks, as well to branded airlocks. \ + desc = "Brand all present ID cards and nearby airlocks.
\ + Branded ID cards will gain access to maintenance, external airlocks, as well to branded airlocks.
\ Branded airlocks will only be accessible by those with a branded ID card." + transmute_text = "Transmute 10 cable pieces, a piece of paper, and a multitool." gain_text = "Gnawed into vicious-stained fingerbones, my grim invitation snaps my nauseous and clouded mind towards the heavy-set door. \ Slowly, the light dances between a crawling darkness, blanketing the fetid promenade with infinite machinations. \ But the King will soon take his pound of flesh. Even here, the taxman takes their cut. For there are a thousands mouths to feed." @@ -203,7 +211,6 @@ /obj/item/multitool = 1, ) cost = 1 - priority = MAX_KNOWLEDGE_PRIORITY - 3 drafting_tier = 1 research_tree_icon_path = 'icons/obj/card.dmi' research_tree_icon_state = "eldritch" @@ -233,3 +240,69 @@ playsound(door, SFX_SPARKS, 33, vary = TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, ignore_walls = FALSE) return TRUE + +/** + * Allows the heretic to craft a spell focus, which passively regenerates some spell charges + */ +/datum/heretic_knowledge/amber_focus + name = "Amber Focus" + desc = "Sculpts an Amber Focus.
\ + Recharges some spell charges every few minutes while worn, barring some exceptional spells." + transmute_text = "Transmute a sheet of glass and a pair of eyes." + gain_text = "I felt lost and directionless. Everything that I tried to grasp, slipped through my fingers. \ + I needed something to hold onto, something to focus on, to keep me from straying too far from the path." + required_atoms = list( + /obj/item/organ/eyes = 1, + /obj/item/stack/sheet/glass = 1, + ) + result_atoms = list(/obj/item/clothing/neck/heretic_focus) + cost = 1 + drafting_tier = 1 + research_tree_icon_path = 'icons/obj/clothing/neck.dmi' + research_tree_icon_state = "eldritch_necklace" + +/datum/heretic_knowledge/miraculous_mirror + name = "Miraculous Mirror" + desc = "Craft a Miraculous Mirror.
\ + The Miraculous Mirror allows you to freely change any aspect of your appearance. \ + You can also use it to change your species, but doing so will cause the mirror to shatter in the process." + transmute_text = "Transmute five bars of silver and a pair of organic eyes." + gain_text = "I was imperfect, weak. How could I achieve such great things in such a sorry state? \ + Every window I passed by, I saw a reflection of myself, and every time I did, I felt a burning desire to change, to be better, to start anew." + required_atoms = list( + /obj/item/organ/eyes = 1, + /obj/item/stack/sheet/mineral/silver = 5, + ) + result_atoms = list(/obj/item/wallframe/mirror/heretic) + cost = 1 + drafting_tier = 1 + research_tree_icon_path = 'icons/obj/watercloset.dmi' + research_tree_icon_state = "magic_mirror" + research_tree_icon_frame = 2 + is_shop_only = TRUE + +/datum/heretic_knowledge/miraculous_mirror/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) + . = ..() + for(var/obj/item/organ/eyes/eye in atoms) + if(!IS_ORGANIC_ORGAN(eye)) + atoms -= eye + +/datum/heretic_knowledge/spell/cloak_of_shadows + name = "Cloak of Shadow" + desc = "Grants you the spell Cloak of Shadow.
\ + This spell will completely conceal your identity in a purple smoke for three minutes, assisting you in keeping secrecy." + notice = "Can only be cast if you have a Living Heart." + action_to_add = /datum/action/cooldown/spell/shadow_cloak + cost = 1 + drafting_tier = 1 + max_charges = INFINITY + is_shop_only = TRUE // not actually but it's got special requirements + +/datum/heretic_knowledge/spell/cloak_of_shadows/spell_check(datum/action/the_spell, feedback) + var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(the_spell.owner) + if(heretic_datum?.has_living_heart()) + return ..() + + if(feedback) + to_chat(the_spell.owner, span_mansus("You need a Living Heart to cast [the_spell]!")) + return SPELL_CANCEL_CAST diff --git a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_three.dm b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_three.dm index 03b27b7965e8..358a2e511c78 100644 --- a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_three.dm +++ b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_three.dm @@ -4,8 +4,9 @@ /datum/heretic_knowledge/summon/rusty name = "Rusted Ritual" - desc = "Allows you to transmute a pool of vomit, some cable coil, and 10 sheets of iron into a Rust Walker. \ + desc = "Summon a Rust Walker.
\ Rust Walkers excel at spreading rust and are moderately strong in combat." + transmute_text = "Transmute a pool of vomit, some cable coil, and 10 sheets of iron." gain_text = "I combined my knowledge of creation with my desire for corruption. The Marshal knew my name, and the Rusted Hills echoed out." required_atoms = list( /obj/effect/decal/cleanable/vomit = 1, @@ -19,9 +20,10 @@ /datum/heretic_knowledge/summon/maid_in_mirror name = "Maid in the Mirror" - desc = "Allows you to transmute five sheets of glass, any suit, and a pair of lungs to create a Maid in the Mirror. \ - Maid in the Mirrors are decent combatants that can become incorporeal by phasing in and out of the mirror realm, serving as powerful scouts and ambushers. \ - Their attacks also apply a stack of void chill." + desc = "Summon a Maid in the Mirror.
\ + Maid in the Mirrors are decent combatants that can become incorporeal by phasing in and out of the mirror realm, \ + serving as powerful scouts and ambushers. Their attacks also apply a stack of void chill." + transmute_text = "Transmute five sheets of glass, any suit, and a pair of lungs." gain_text = "Within each reflection, lies a gateway into an unimaginable world of colors never seen and \ people never met. The ascent is glass, and the walls are knives. Each step is blood, if you do not have a guide." @@ -38,10 +40,11 @@ /datum/heretic_knowledge/summon/ashy name = "Ashen Ritual" - desc = "Allows you to transmute a Bonfire and a book to create an Ash Spirit. \ + desc = "Summon an Ash Spirit.
\ Ash Spirits have a short range jaunt and the ability to cause bleeding in foes at range. \ - They also have the ability to create a ring of fire around themselves for a length of time. \ + They also have the ability to create a ring of fire around themselves for a length of time.
\ They have a low amount of health, but will passively recover given enough time to do so." + transmute_text = "Transmute a pool of ash, a book, and a bonfire." gain_text = "I combined my principle of hunger with my desire for destruction. The Marshal knew my name, and the Nightwatcher gazed on." required_atoms = list( /obj/effect/decal/cleanable/ash = 1, @@ -59,10 +62,10 @@ /datum/heretic_knowledge/limited_amount/risen_corpse name = "Shattered Ritual" - desc = "Allows you to transmute a corpse with a soul, a pair of latex or nitrile gloves, and \ - and any exosuit clothing (such as armor) to create a Shattered Risen. \ + desc = "Enchant a corpse into a Shattered Risen.
\ Shattered Risen are strong ghouls that have 125 health, but cannot hold items, \ instead having two brutal weapons for hands. You can only create one at a time." + transmute_text = "Transmute a corpse with a soul, a pair of latex or nitrile gloves." gain_text = "I witnessed a cold, rending force drag this corpse back to near-life. \ When it moves, it crunches like broken glass. Its hands are no longer recognizable as human - \ each clenched fist contains a brutal nest of sharp bone-shards instead." @@ -159,9 +162,10 @@ /datum/heretic_knowledge/summon/fire_shark name = "Scorching Shark" - desc = "Allows you to transmute a pool of ash, a liver, and a sheet of plasma into a Fire Shark. \ - Fire Sharks are fast and strong in groups, but die quickly. They are also highly resistant against fire attacks. \ - Fire Sharks inject phlogiston into its victims and spawn plasma once they die." + desc = "Summon a Fire Shark.
\ + Fire Sharks are fast and strong in groups, but fragile to non-burning damage.
\ + They also inject phlogiston on attack and spawn plasma on death." + transmute_text = "Transmute a pool of ash, a liver, and a sheet of plasma." gain_text = "The cradle of the nebula was cold, but not dead. Light and heat flits even through the deepest darkness, and is hunted by its own predators." required_atoms = list( @@ -176,3 +180,169 @@ research_tree_icon_dir = EAST drafting_tier = 3 + +/datum/heretic_knowledge/mad_mask + name = "Mask of Madness" + desc = "Create a Mask of Madness.
\ + The mask instills fear into heathens who witness it, causing stamina damage, hallucinations, and insanity.
\ + It can also be forced onto a heathen, to make them unable to take it off..." + transmute_text = "Transmute any mask, four lit candles, a stun baton, and a liver." + gain_text = "The Watch wore strange garb on duty. It allowed them to walk the city, seemingly unnoticed by the masses." + required_atoms = list( + /obj/item/organ/liver = 1, + /obj/item/melee/baton/security = 1, // Technically means a cattleprod is valid + /obj/item/clothing/mask = 1, + /obj/item/flashlight/flare/candle = 4, + ) + result_atoms = list(/obj/item/clothing/mask/madness_mask) + cost = 2 + research_tree_icon_path = 'icons/obj/clothing/masks.dmi' + research_tree_icon_state = "mad_mask" + drafting_tier = 3 + +/datum/heretic_knowledge/mad_mask/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) + . = ..() + for(var/obj/item/flashlight/flare/candle/candle in atoms) + if(!candle.light_on) + atoms -= candle + +/datum/heretic_knowledge/mad_mask/prepare_atom_for_ritual_test(atom/what) + . = ..() + if(istype(what, /obj/item/flashlight/flare/candle)) + what.set_light_on(TRUE) + +/datum/heretic_knowledge/mansus_gate + name = "Keys to the Backdoor" + desc = "Open a backdoor to the Mansus.
\ + Entering the container will transport you to the Mansus, granting you a safe haven to transmute or store equipment." + transmute_text = "Transmute a locker and a Codex Cicatrix or Codex Morbus." + notice = "Only willing individuals or the deceased can pass through the backdoor.\ +
Attempting to perform a sacrifice so close to the gods may anger them.\ +
You can only create one backdoor." + gain_text = "With any domain, its security is only as strong as its weakest point. \ + The Codex speaks of backdoors to the Mansus - gates with shoddy chains, doors with brittle locks, trapdoors with rusted hinges. \ + With the right key, I could easily exploit these." + required_atoms = list( + /obj/structure/closet = 1, + list(/obj/item/codex_cicatrix, /obj/item/codex_cicatrix/morbus) = 1, + ) + cost = 2 + research_tree_icon_path = 'icons/effects/effects.dmi' + research_tree_icon_state = "anom" + drafting_tier = 3 + is_shop_only = TRUE + VAR_PRIVATE/used = FALSE + +/datum/heretic_knowledge/mansus_gate/can_be_invoked(datum/antagonist/heretic/invoker) + return !used + +/datum/heretic_knowledge/mansus_gate/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) + var/obj/structure/closet/locker = locate() in selected_atoms + if(isnull(locker)) + stack_trace("Locker not found in selected atoms for mansus gate recipe!") + return FALSE + + used = TRUE + INVOKE_ASYNC(src, PROC_REF(setup_link), locker) + return TRUE + +/datum/heretic_knowledge/mansus_gate/cleanup_atoms(list/selected_atoms) + for(var/obj/structure/closet/locker in selected_atoms) + selected_atoms -= locker + return ..() + +/datum/heretic_knowledge/mansus_gate/proc/setup_link(obj/structure/closet/crate/locker) + var/datum/map_template/masus_backdoor/backdoor = new() + var/datum/turf_reservation/reservation = SSmapping.request_turf_block_reservation( + width = backdoor.width, + height = backdoor.height, + reservation_type = /datum/turf_reservation/indestructible_plating, + ) + var/turf/bottom_left = reservation.bottom_left_turfs[1] + backdoor.load(bottom_left) + var/obj/structure/closet/new_link = locate() in backdoor.created_atoms + new_link.resistance_flags |= INDESTRUCTIBLE + new_link.set_anchored(TRUE) + new_link.anchorable = FALSE + new_link.divable = FALSE + new_link.contents_pressure_protection = 1 + new_link.contents_thermal_insulation = 1 + locker.resistance_flags |= INDESTRUCTIBLE + locker.divable = FALSE + locker.contents_pressure_protection = 1 + locker.contents_thermal_insulation = 1 + GLOB.closet_teleport_controller.create_new_link(list(locker, new_link), subtle = TRUE) + RegisterSignal(new_link, COMSIG_CLOSET_TELEPORTER_PRE_SENDING, PROC_REF(closet_teleport_logic)) + RegisterSignal(locker, COMSIG_CLOSET_TELEPORTER_PRE_SENDING, PROC_REF(closet_teleport_logic)) + +/datum/heretic_knowledge/mansus_gate/proc/closet_teleport_logic(obj/structure/closet/crate/locker, atom/movable/sending_through) + SIGNAL_HANDLER + + if(!is_station_level(locker.z)) + return CLOSET_TELEPORT_FORCED + + if(isliving(sending_through) && !consents_to_entry(sending_through)) + locker.balloon_alert(sending_through, "the door refuses you!") + return CLOSET_TELEPORT_BLOCKED + + for(var/mob/living/entering in sending_through.get_all_contents()) + if(!consents_to_entry(entering)) + if(isliving(sending_through)) + locker.balloon_alert(sending_through, "the door refuses you!") + return CLOSET_TELEPORT_BLOCKED + + return CLOSET_TELEPORT_FORCED + +/datum/heretic_knowledge/mansus_gate/proc/consents_to_entry(mob/living/entering) + if(IS_HERETIC_OR_MONSTER(entering)) + return TRUE + if(!INCAPACITATED_IGNORING(entering, INCAPABLE_GRAB|INCAPABLE_STASIS)) + return TRUE + return FALSE + +/datum/map_template/masus_backdoor + name = "The Mansus" + mappath = "_maps/templates/mansus_backdoor.dmm" + width = 59 + height = 51 + returns_created_atoms = TRUE + +/area/centcom/heretic_backdoor + name = "Mansus" + icon_state = "heretic" + requires_power = TRUE + always_unpowered = TRUE + ambience_index = AMBIENCE_SPOOKY + sound_environment = SOUND_ENVIRONMENT_PLAIN + area_flags = NOTELEPORT | HIDDEN_AREA | BLOCK_SUICIDE | NO_BOH + static_lighting = FALSE + base_lighting_alpha = 200 + base_lighting_color = "#FFF4AA" + +/area/centcom/heretic_backdoor/Entered(atom/movable/arrived, area/old_area) + . = ..() + if(isliving(arrived)) + var/mob/living/arrived_mob = arrived + arrived_mob.add_movespeed_modifier(/datum/movespeed_modifier/heretic_backdoor_slowdown) + arrived_mob.adjust_temp_blindness(1 SECONDS) + arrived_mob.adjust_eye_blur(2 SECONDS) + addtimer(CALLBACK(src, PROC_REF(greet_message), arrived_mob), 2 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + if(!IS_HERETIC_OR_MONSTER(arrived_mob)) + arrived_mob.apply_status_effect(/datum/status_effect/necropolis_curse, CURSE_BLINDING) + +/area/centcom/heretic_backdoor/Exited(atom/movable/gone, direction) + . = ..() + if(isliving(gone)) + var/mob/living/gone_mob = gone + gone_mob.remove_movespeed_modifier(/datum/movespeed_modifier/heretic_backdoor_slowdown) + gone_mob.remove_status_effect(/datum/status_effect/necropolis_curse) + gone_mob.adjust_temp_blindness(1 SECONDS) + gone_mob.adjust_eye_blur(2 SECONDS) + +/area/centcom/heretic_backdoor/proc/greet_message(mob/living/arrived_mob) + if(QDELETED(arrived_mob) || get_area(arrived_mob) != src) + return + to_chat(arrived_mob, span_mansus("A hollow sun shines down from above.")) + +/datum/movespeed_modifier/heretic_backdoor_slowdown + multiplicative_slowdown = 0.5 diff --git a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm index 5d155281e5eb..9b8923b66038 100644 --- a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm +++ b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm @@ -9,9 +9,10 @@ */ /datum/heretic_knowledge/codex_morbus name = "Codex Morbus" - desc = "Allows you to to combine a codex cicatrix, and a body into a Codex Morbus. \ - It draws runes and siphons essences a bit faster. \ - Right Click on a rune to curse crewmembers, the target's blood is required in your off hand for a curse to take effect (Best combined with Phylactery Of Damnation)." + desc = "Author the Codex Morbus.
Draws runes and siphons essences a bit faster than a Codex Cicatrix.
\ + Right Click on a rune to curse crewmembers - the target's blood is required in your off hand for a curse to take effect \ + (Best combined with Phylactery Of Damnation)." + transmute_text = "Transmute the Codex Cicatrix and a body into a Codex Morbus." gain_text = "The spine of this leather-bound tome creaks with an eerily pained sigh. \ To ply page from place takes considerable effort, and I dare not linger on the suggestions the book makes for longer than necessary. \ It speaks of coming plagues, of waiting supplicants of dead and forgotten gods, and the undoing of mortal kind. \ @@ -37,8 +38,9 @@ return TRUE /datum/heretic_knowledge/greaves_of_the_prophet - name = "Greaves Of The Prophet" - desc = "Allows you to combine a pair of shoes and 2 sheets of titanium or silver into a pair of Armored Greaves, they confer to the user full immunity to slips." + name = "Greaves of the Prophet" + desc = "Forge a pair of Armored Greaves, which confer to the user full immunity to slips." + transmute_text = "Transmute a pair of shoes and two sheets of titanium or silver." gain_text = " \ Gristle churns into joint, a pop, and the fool twists a blackened foot from the \ jaws of another. At their game for centuries, this mangled tree of limbs twists, \ @@ -59,19 +61,25 @@ /datum/heretic_knowledge/spell/opening_blast name = "Wave Of Desperation" - desc = "Grants you Wave Of Desparation, a spell which can only be cast while restrained. \ + desc = "Grants you Wave Of Desparation, a spell which can only be cast while restrained.
\ It removes your restraints, repels and knocks down adjacent people, and applies the Mansus Grasp to everything nearby." gain_text = "My shackles undone in dark fury, their feeble bindings crumble before my power." - + required_atoms = list( + /obj/item/clothing/suit/jacket/straight_jacket = 1, + ) action_to_add = /datum/action/cooldown/spell/aoe/wave_of_desperation cost = 2 drafting_tier = 2 + max_charges = 1 + path_recharge_amount = 0.0 + transmute_text = "To recharge, complete a ritual with a straight jacket." /datum/heretic_knowledge/rune_carver name = "Carving Knife" - desc = "Allows you to transmute a knife, a shard of glass, and a piece of paper to create a Carving Knife. \ - The Carving Knife allows you to etch difficult to see traps that trigger on heathens who walk overhead. \ + desc = "Create a Carving Knife.
\ + The Carving Knife allows you to etch difficult to see traps that trigger on heathens who walk overhead.
\ Also makes for a handy throwing weapon." + transmute_text = "Transmute a knife, a shard of glass, and a piece of paper." gain_text = "Etched, carved... eternal. There is power hidden in everything. I can unveil it! \ I can carve the monolith to reveal the chains!" required_atoms = list( @@ -87,8 +95,10 @@ /datum/heretic_knowledge/ether name = "Ether Of The Newborn" - desc = "Transmutes a pool of vomit and a shard into a single use potion, drinking it will remove any sort of abnormality from your body including diseases, traumas and implants \ - on top of restoring it to full health, at the cost of losing consciousness for an entire minute." + desc = "Brews a single use potion.
Imbibing it will restore you to full health and \ + remove any sort of abnormality from your body (including diseases, traumas and implants) - \ + however, you will lose consciousness for a full minute." + transmute_text = "Transmute a pool of vomit and a shard." gain_text = "Vision and thought grow hazy as the fumes of this ichor swirl up to meet me. \ Through the haze, I find myself staring back in relief, or something grossly resembling my visage. \ It is this wretched thing that I consign to my fate, and whose own that I snatch through the haze of dreams. Fools that we are." @@ -104,15 +114,21 @@ /datum/heretic_knowledge/painting name = "Unsealed Arts" - desc = "Allows you to transmute a canvas and an additional item to create a painting. \ - Each painting has a unique effect and recipe. Possible paintings: \ - The Sister and He Who Wept: Requires a pair of Eyes. Clears your own mind, and curses non-heretics with hallucinations. \ - The Feast of Desire: Requires a severed limb. Supplies you with random organs, and curses non-heretics with a hunger for flesh. \ - Great Chaparral Over Rolling Hills: Requires any plant produce. Spreads kudzu when placed, and supplies you with poppies and harebells. \ - Lady of the Gate: Requires any pair of Gloves. Clears your mutations, mutates non-heretics and curses them with scratching. \ - Master of the Rusted Mountain: Requires a piece of Trash. Curses non-heretics to rust the floor they walk on." + desc = "Paint a curse into existence. \ + Each painting has a unique effect and recipe. \ +
• The Sister and He Who Wept: Clears your mind, while cursing heathens with hallucinations. \ +
• The Feast of Desire: Supplies you with random organs, while cursing heathens with a hunger for flesh. \ +
• Great Chaparral Over Rolling Hills: Spreads kudzu when placed, and supplies you with poppies and harebells. \ +
• Lady of the Gate: Clears your mutations, while mmutating and cursing heathens them with scratching. \ +
• Master of the Rusted Mountain: Curses heathens to rust the floor they walk on." + transmute_text = "Transmute a canvas and an additional item to create a painting. \ +
• A pair of eyes for The Sister and He Who Wept \ +
• A severed limb for The Feast of Desire \ +
• Any plant produce for Great Chaparral Over Rolling Hills \ +
• Any pair of gloves for Lady of the Gate \ +
• A piece of trash for Master of the Rusted Mountain" gain_text = "A wind of inspiration blows through me. Beyond the veil and past the gate great works exist, yet to be painted. \ - They yearn for mortal eyes, so I shall give them an audience." + They yearn for mortal eyes, so I shall give them an audience." required_atoms = list(/obj/item/canvas = 1) result_atoms = list(/obj/item/canvas) cost = 2 @@ -163,3 +179,65 @@ user.balloon_alert(user, "no additional atom present!") return FALSE + +/datum/heretic_knowledge/hypnosis_ritual + name = "Unwrap Minds" + desc = "Exposes a healthen directly to the horrors of the Mansus, hypnotizing them." + transmute_text = "Transmute a scalpel, a shard of glass, a piece of paper, and a living heathen." + notice = "Whatever is written on the paper supplied, the heathen will be hypnotized with.\ +
If the heathen is mindshielded, it will shatter - but the resulting hypnosis may not be what you expect.\ +
Other Heretics are unaffected by this ritual." + gain_text = "My rise has been lonely, but I had realized it did not have to be. \ + I can show them the truth. Their weak, mortal minds may not be able to withstand the revelation, but in its ashes, a phoenix will rise, free and true." + required_atoms = list( + /obj/item/scalpel = 1, + /obj/item/shard = 1, + /obj/item/paper = 1, + /mob/living/carbon/human = 1, + ) + cost = 2 + research_tree_icon_path = 'icons/hud/screen_alert.dmi' + research_tree_icon_state = "hypnosis" + drafting_tier = 2 + +/datum/heretic_knowledge/hypnosis_ritual/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) + for(var/mob/living/carbon/human/victim in atoms) + if(victim.stat == DEAD || IS_HERETIC(victim) || victim.has_trauma_type(/datum/brain_trauma/hypnosis)) + atoms -= victim + + var/has_paper = FALSE + var/has_written_text = FALSE + for(var/obj/item/paper/paper in atoms) + has_paper = TRUE + for(var/datum/paper_input/text as anything in paper.raw_text_inputs) + has_written_text = TRUE + + if(!has_written_text && has_paper) + loc.balloon_alert(user, "write your hypnosis on the paper!") + return FALSE + + return ..() + +/datum/heretic_knowledge/hypnosis_ritual/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) + var/hypnosis_text = "" + for(var/obj/item/paper/paper in selected_atoms) + for(var/datum/paper_input/text as anything in paper.raw_text_inputs) + hypnosis_text += "[STRIP_HTML_FULL(text.raw_text, MAX_MESSAGE_LEN)] " + paper.burn() + selected_atoms -= paper + + hypnosis_text = trim(hypnosis_text, MAX_MESSAGE_LEN) + for(var/mob/living/carbon/human/victim in selected_atoms) + var/specific_hypnosis_text = (HAS_TRAIT(victim, TRAIT_UNCONVERTABLE) || !hypnosis_text) ? pick_list(HERETIC_INFLUENCE_FILE, "hypnosis") : hypnosis_text + for(var/obj/item/implant/mindshield/shield in victim.implants) + shield.removed(victim, silent = FALSE) + shield.forceMove(victim.drop_location()) + shield.burn() + + selected_atoms -= victim + // lobotomy resistance because it might be a bit rough to make this permanent aye + // future note: if people just spam hypnosis to make an army to rush ascension, make it so hypnosis is broken by witnessing sacrifices + var/datum/brain_trauma/hypnosis/trauma = new(specific_hypnosis_text) + victim.gain_trauma(trauma, TRAUMA_RESILIENCE_LOBOTOMY) + + return TRUE diff --git a/code/modules/antagonists/heretic/knowledge/starting_lore.dm b/code/modules/antagonists/heretic/knowledge/starting_lore.dm index 19ccec3353a5..d66b6ce3575e 100644 --- a/code/modules/antagonists/heretic/knowledge/starting_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/starting_lore.dm @@ -18,18 +18,29 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) */ /datum/heretic_knowledge/spell/basic name = "Break of Dawn" - desc = "Starts your journey into the Mansus. \ - Grants you the Mansus Grasp, a powerful and upgradable \ - disabling spell that can be cast regardless of having a focus." + desc = "Starts your journey into the Mansus.
\ + Grants you the Mansus Grasp, a powerful and upgradable disabling spell." action_to_add = /datum/action/cooldown/spell/touch/mansus_grasp cost = 0 is_starting_knowledge = TRUE + max_charges = 8 + focus_recharge_amount = 0.25 + holywater_drain_amount = 0.125 + transmute_text = "Tapping influences and completing sacrifices will recharge the spell." // Heretics can enhance their fishing rods to fish better - fishing content. // Lasts until successfully fishing something up. /datum/heretic_knowledge/spell/basic/on_gain(mob/user, datum/antagonist/heretic/our_heretic) - ..() + . = ..() RegisterSignal(user, COMSIG_TOUCH_HANDLESS_CAST, PROC_REF(on_grasp_cast)) + RegisterSignal(our_heretic, COMSIG_HERETIC_INFLUENCE_DRAINED, PROC_REF(on_influence_tap)) + RegisterSignal(our_heretic, COMSIG_HERETIC_SACRIFICE, PROC_REF(on_sacrifice)) + +/datum/heretic_knowledge/spell/basic/on_lose(mob/user, datum/antagonist/heretic/our_heretic) + . = ..() + UnregisterSignal(user, COMSIG_TOUCH_HANDLESS_CAST) + UnregisterSignal(our_heretic, COMSIG_HERETIC_INFLUENCE_DRAINED) + UnregisterSignal(our_heretic, COMSIG_HERETIC_SACRIFICE) /datum/heretic_knowledge/spell/basic/proc/on_grasp_cast(mob/living/carbon/cast_on, datum/action/cooldown/spell/touch/touch_spell) SIGNAL_HANDLER @@ -58,6 +69,14 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) REMOVE_TRAIT(item, TRAIT_ROD_MANSUS_INFUSED, REF(item)) item.difficulty_modifier += 20 +/datum/heretic_knowledge/spell/basic/proc/on_influence_tap(...) + SIGNAL_HANDLER + add_charges(max_charges) + +/datum/heretic_knowledge/spell/basic/proc/on_sacrifice(...) + SIGNAL_HANDLER + add_charges(max_charges) + /** * The Living Heart heretic knowledge. * @@ -66,10 +85,9 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) */ /datum/heretic_knowledge/living_heart name = "The Living Heart" - desc = "Grants you a Living Heart, allowing you to track sacrifice targets. \ - Should you lose your heart, you can transmute a poppy and a pool of blood \ - to awaken your heart into a Living Heart. If your heart is Cybernetic, \ - you will be unable to reawaken it." + desc = "Grants you a Living Heart, allowing you to track sacrifice targets." + transmute_text = "Should you lose your heart, you can transmute a poppy and a pool of blood \ + to awaken your heart into a Living Heart." required_atoms = list( /obj/effect/decal/cleanable/blood = 1, /obj/item/food/grown/flower/poppy = 1, @@ -80,6 +98,7 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) research_tree_icon_path = 'icons/obj/antags/eldritch.dmi' research_tree_icon_state = "living_heart" research_tree_icon_frame = 1 + notice = "If your heart is Cybernetic, you will be unable to reawaken it." /datum/heretic_knowledge/living_heart/on_research(mob/user, datum/antagonist/heretic/our_heretic) . = ..() @@ -111,8 +130,8 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) if(where_to_put_our_heart) where_to_put_our_heart.AddComponent(/datum/component/living_heart) - desc = "Grants you a Living Heart, tied to your [where_to_put_our_heart.name], allowing you to track sacrifice targets. \ - Should you lose your [where_to_put_our_heart.name], you can transmute a poppy and a pool of blood \ + desc = "Grants you a Living Heart, tied to your [where_to_put_our_heart.name], allowing you to track sacrifice targets." + transmute_text = "Should you lose your [where_to_put_our_heart.name], you can transmute a poppy and a pool of blood \ to awaken your [where_to_put_our_heart.name] into a Living Heart. \ Cybernetic [where_to_put_our_heart.name]\s will block the ritual!" @@ -173,41 +192,15 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) return TRUE -/** - * Allows the heretic to craft a spell focus. - * They require a focus to cast advanced spells. - */ -/datum/heretic_knowledge/amber_focus - name = "Amber Focus" - desc = "Allows you to transmute a sheet of glass and a pair of eyes to create an Amber Focus. \ - A focus must be worn in order to cast more advanced spells." - required_atoms = list( - /obj/item/organ/eyes = 1, - /obj/item/stack/sheet/glass = 1, - ) - result_atoms = list(/obj/item/clothing/neck/heretic_focus) - cost = 0 - priority = MAX_KNOWLEDGE_PRIORITY - 2 // Not as important as making a heart or sacrificing, but important enough. - is_starting_knowledge = TRUE - research_tree_icon_path = 'icons/obj/clothing/neck.dmi' - research_tree_icon_state = "eldritch_necklace" - -/datum/heretic_knowledge/spell/cloak_of_shadows - name = "Cloak of Shadow" - desc = "Grants you the spell Cloak of Shadow. This spell will completely conceal your identity in a purple smoke \ - for three minutes, assisting you in keeping secrecy. Requires a focus to cast." - action_to_add = /datum/action/cooldown/spell/shadow_cloak - cost = 0 - is_starting_knowledge = TRUE - /datum/heretic_knowledge/feast_of_owls name = "Feast of Owls" - desc = "Allows you to undergo a ritual that gives you 5 knowledge points but locks you out of ascension. This can only be done once and cannot be reverted." + desc = "Allows you to undergo a ritual that grants you five knowledge points, but locks you out of ascension." gain_text = "Under the soft glow of unreason there is a beast that stalks the night. I shall bring it forth and let it enter my presence. It will feast upon my amibitions and leave knowledge in its wake." is_starting_knowledge = TRUE required_atoms = list() research_tree_icon_path = 'icons/mob/actions/actions_animal.dmi' research_tree_icon_state = "god_transmit" + notice = "This can only be done once and cannot be reverted." /// amount of research points granted var/reward = 5 diff --git a/code/modules/antagonists/heretic/knowledge/void_lore.dm b/code/modules/antagonists/heretic/knowledge/void_lore.dm index b5deceaca5f2..44fc3a08a257 100644 --- a/code/modules/antagonists/heretic/knowledge/void_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/void_lore.dm @@ -27,7 +27,7 @@ ) tips = list( "Your Mansus Grasp allows you to mute your targets, making it ideal for silent assassinations (keep in mind that it won't short circuit their suit sensors, make sure you turn them off after you kill them). Yhe grasp also applies a mark that when triggered by the void blade will apply the maximum amount of stacks of void chill to your target, slowing them down to a crawl.", - "Void Cloak can be used to hide one of your blades and a Codex Cicatrix when the hood is down, while acting as a focus when it's up.", + "Void Cloak can be used to hide one of your blades and a Codex Cicatrix.", "Void chill is a debuff applied by your spells, your grasp, your mark and your blade once you unlock the upgrade. Each stack slows your target movement speed by 10% and make them gradually colder, up to a maximum of 5 stacks.", "At 5 stacks void chill will also prevent your target from heating up.", "You are immune to low pressure and cold damage at the start of the shift. Upgrade your passive to level 2 to no longer need to breathe. Use this to your advantage.", @@ -38,20 +38,21 @@ start = /datum/heretic_knowledge/limited_amount/starting/base_void knowledge_tier1 = /datum/heretic_knowledge/spell/void_phase guaranteed_side_tier1 = /datum/heretic_knowledge/void_cloak - knowledge_tier2 = /datum/heretic_knowledge/spell/void_prison + knowledge_tier2 = /datum/heretic_knowledge/void_prison guaranteed_side_tier2 = /datum/heretic_knowledge/ether robes = /datum/heretic_knowledge/armor/void knowledge_tier3 = /datum/heretic_knowledge/spell/void_pull guaranteed_side_tier3 = /datum/heretic_knowledge/summon/maid_in_mirror blade = /datum/heretic_knowledge/blade_upgrade/void - knowledge_tier4 = /datum/heretic_knowledge/spell/void_conduit + knowledge_tier4 = /datum/heretic_knowledge/void_conduit ascension = /datum/heretic_knowledge/ultimate/void_final /datum/heretic_knowledge/limited_amount/starting/base_void name = "Glimmer of Winter" - desc = "Opens up the Path of Void to you. \ - Allows you to transmute a knife in sub-zero temperatures into a Void Blade. \ + desc = "Opens up the Path of Void to you.
\ + Allows you to create Void Blades. \ You can only create two at a time." + transmute_text = "Transmute a knife in sub-zero temperatures." gain_text = "I feel a shimmer in the air, the air around me gets colder. \ I start to realize the emptiness of existence. Something's watching me." required_atoms = list(/obj/item/knife = 1) @@ -85,35 +86,66 @@ /datum/heretic_knowledge/spell/void_phase name = "Void Phase" - desc = "Grants you Void Phase, a long range targeted teleport spell. \ - Additionally causes damage to heathens around your original and target destination." + desc = "Grants you Void Phase, a long range targeted teleport spell.
\ + Causes damage to heathens around your original and target destination." gain_text = "The entity calls themself the Aristocrat. They effortlessly walk through air like \ nothing - leaving a harsh, cold breeze in their wake. They disappear, and I am left in the blizzard." action_to_add = /datum/action/cooldown/spell/pointed/void_phase cost = 2 research_tree_icon_frame = 7 + max_charges = 4 + path_recharge_amount = 0.25 + focus_recharge_amount = 0.25 + holywater_drain_amount = 0.25 -/datum/heretic_knowledge/spell/void_prison +/datum/heretic_knowledge/void_prison name = "Void Prison" - desc = "Grants you Void Prison, a spell that places your victim into a ball, making them unable to do anything or speak. \ - Applies void chill afterwards." + desc = "Transmute a set of handcuffs, a stun baton, and a closet in sub-zero temperatures to gain a Void Prison.
\ + A Void Prison is an orb that, when used, traps all nearby unmarked heathens into a stasis ball for 10 seconds. \ + While in the ball, they are unable to speak, act, or be harmed. The Void Prison is consumed after one use." + transmute_text = "Transmute a set of handcuffs, a stun baton, and a closet in sub-zero temperatures." gain_text = "At first, I see myself, waltzing along a snow-laden street. \ I try to yell, grab hold of this fool and tell them to run. \ But the only welts made are on my own beating fist. \ My smiling face turns to regard me, reflecting back in glassy eyes the empty path I have been lead down." - - action_to_add = /datum/action/cooldown/spell/pointed/void_prison + required_atoms = list( + /obj/item/restraints/handcuffs = 1, + /obj/structure/closet = 1, + /obj/item/melee/baton/security = 1, + ) + result_atoms = list(/obj/item/void_prison) cost = 2 drafting_tier = 5 + research_tree_icon_path = 'icons/mob/actions/actions_ecult.dmi' + research_tree_icon_state = "voidball" + + var/list/closet_blacklist = list( + /obj/structure/closet/crate, + /obj/structure/closet/body_bag, + /obj/structure/closet/cardboard, + /obj/structure/closet/infinite, + ) + +/datum/heretic_knowledge/void_prison/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc) + . = ..() + for(var/obj/structure/closet/closet in atoms) + if(is_type_in_list(closet, closet_blacklist)) + atoms -= closet + +/datum/heretic_knowledge/void_prison/cleanup_atoms(list/selected_atoms) + for(var/obj/structure/closet/closet in selected_atoms) + closet.dump_contents() + + return ..() /datum/heretic_knowledge/armor/void - name = "Hollow Weave" - desc = "Allows you to transmute a table (or a suit) and a mask in sub-zero temperatures to create a Hollow Weave, this armor will periodically nullify attacks and grant you a short stealth camoflage to reposition yourself. \ - Acts as a focus while hooded." + desc = "Create a Hollow Weave.
\ + This armor will periodically nullify attacks and grant you a short stealth camouflage to reposition yourself." + transmute_text = "Transmute a table (or a suit) and a mask in sub-zero temperatures." gain_text = "Stepping through the cold air, I am shocked by a new sensation. \ - Thousands of almost imperceivable threads cling to my form. \ - I am left adrift with every step. \ - Even as I hear the crunch of snow as I plant my foot to the ground, I feel nothing." + Thousands of almost imperceivable threads cling to my form. \ + I am left adrift with every step. \ + Even as I hear the crunch of snow as I plant my foot to the ground, I feel nothing." result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/void) research_tree_icon_state = "void_armor" required_atoms = list( @@ -142,13 +174,17 @@ action_to_add = /datum/action/cooldown/spell/aoe/void_pull cost = 2 research_tree_icon_frame = 6 + max_charges = 4 + path_recharge_amount = 0.25 + focus_recharge_amount = 0.25 + holywater_drain_amount = 0.25 /datum/heretic_knowledge/blade_upgrade/void name = "Seeking Blade" - desc = "Your blade now freezes enemies. Additionally, you can now attack distant marked targets with your Void Blade, teleporting directly next to them." + desc = "Your blade now freezes enemies.
\ + Additionally, you can now attack distant marked targets with your Void Blade, teleporting directly next to them." gain_text = "Fleeting memories, fleeting feet. I mark my way with frozen blood upon the snow. Covered and forgotten." - research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi' research_tree_icon_state = "blade_upgrade_void" @@ -158,8 +194,11 @@ target.apply_status_effect(/datum/status_effect/void_chill, 2) -/datum/heretic_knowledge/blade_upgrade/void/do_ranged_effects(mob/living/user, mob/living/target, obj/item/melee/sickly_blade/blade) - if(!target.has_status_effect(/datum/status_effect/eldritch)) +/datum/heretic_knowledge/blade_upgrade/void/do_ranged_effects(mob/living/user, atom/target, obj/item/melee/sickly_blade/blade) + if(!isliving(target)) + return + var/mob/living/living_target = target + if(!living_target.has_status_effect(/datum/status_effect/eldritch)) return var/dir = angle2dir(dir2angle(get_dir(user, target)) + 180) @@ -170,23 +209,94 @@ /datum/heretic_knowledge/blade_upgrade/void/proc/follow_up_attack(mob/living/user, mob/living/target, obj/item/melee/sickly_blade/blade) blade.melee_attack_chain(user, target) -/datum/heretic_knowledge/spell/void_conduit +/datum/heretic_knowledge/void_conduit name = "Void Conduit" - desc = "Grants you Void Conduit, a spell which summons a pulsing gate to the Void itself. Every pulse breaks windows and airlocks, while afflicting Heathens with an eldritch chill and shielding Heretics against low pressure." + desc = "Empowers your blade, allowing you to rip a hole through space itself.
\ + Attacking space with one of your void blades will create conduit to the void, \ + damaging and chilling nearby heathens, and destroying windows and airlocks in the area." + notice = "The blade is consumed in the process. You can also use this ability on snow, or any tile in a complete vacuum." gain_text = "The hum in the still, cold air turns to a cacophonous rattle. \ Over the noise, there is no distinction to the clattering of window panes and the yawning knowledge that ricochets through my skull. \ The doors won't close. I can't keep the cold out now." - action_to_add = /datum/action/cooldown/spell/conjure/void_conduit cost = 2 is_final_knowledge = TRUE + research_tree_icon_path = 'icons/mob/actions/actions_ecult.dmi' + research_tree_icon_state = "void_rift" + research_tree_icon_frame = 12 + +/datum/heretic_knowledge/void_conduit/on_gain(mob/user, datum/antagonist/heretic/our_heretic) + . = ..() + RegisterSignal(user, COMSIG_HERETIC_BLADE_PREATTACK, PROC_REF(on_blade_preattack)) + +/datum/heretic_knowledge/void_conduit/on_lose(mob/user, datum/antagonist/heretic/our_heretic) + . = ..() + UnregisterSignal(user, COMSIG_HERETIC_BLADE_PREATTACK) + +/datum/heretic_knowledge/void_conduit/proc/is_valid_turf(turf/open/affected_turf) + // space is the obvious one, snow is for icebox + if(isspaceturf(affected_turf) || issnowturf(affected_turf)) + return TRUE + // works in any vacuum as a backup + var/datum/gas_mixture/air = affected_turf.return_air() + if(air?.return_pressure() <= 0) + return TRUE + return FALSE + +/datum/heretic_knowledge/void_conduit/proc/is_valid_turf_callback(turf/open/affected_turf, mob/living/source, obj/item/sword) + if(!source.is_holding(sword)) + return FALSE + if(!is_valid_turf(affected_turf)) + return FALSE + if(affected_turf.GetTemperature() > T0C) + return FALSE + return TRUE + +/datum/heretic_knowledge/void_conduit/proc/on_blade_preattack(mob/living/source, atom/target, obj/item/sword) + SIGNAL_HANDLER + if(!isopenturf(target)) + return NONE + + var/turf/open/affected_turf = target + if(!is_valid_turf(affected_turf)) + return NONE + + if(affected_turf.GetTemperature() <= T0C) + INVOKE_ASYNC(src, PROC_REF(create_conduit), affected_turf, source, sword) + return COMPONENT_CANCEL_ATTACK_CHAIN + + to_chat(source, span_mansus("[sword] hums with power, but [target] is not cold enough to create a conduit!")) + return NONE + +/datum/heretic_knowledge/void_conduit/proc/create_conduit(turf/open/affected_turf, mob/living/source, obj/item/sword) + playsound(source, 'sound/effects/cloth_rip.ogg', 50, TRUE) // funny thing is, can't hear sound in a vacuum + to_chat(source, span_mansus("You plunge [sword] deep into [affected_turf], trying to rip open a conduit to the void!")) + source.visible_message( + span_hypnophrase("[source] plunges [source.p_their()] [sword.name] into [affected_turf] - \ + [isspaceturf(affected_turf) ? "but instead of nothing happening" : "contrary to what you expected"], a dark energy begins to flow from the site!"), + ignored_mobs = source, + ) + var/obj/effect/temp_visual/void_conduit_opening/animation = new(affected_turf) + if(!do_after(source, 5 SECONDS, affected_turf, extra_checks = CALLBACK(src, PROC_REF(is_valid_turf_callback), affected_turf, source, sword))) + animate(animation, alpha = 0, time = 1 SECONDS) + QDEL_IN(animation, 1 SECONDS) + return + to_chat(source, span_mansus("The conduit opens, releasing a storm of void energy! [sword] shatters into a million tiny shards!")) + source.visible_message( + span_hypnophrase("A conduit to the void opens, releasing a storm of void energy!"), + ignored_mobs = source, + ) + new /obj/structure/void_conduit(affected_turf) + source.dropItemToGround(sword) + qdel(sword) + playsound(source, SFX_SHATTER, 50, FALSE) /datum/heretic_knowledge/ultimate/void_final name = "Waltz at the End of Time" - desc = "The ascension ritual of the Path of Void. \ - Bring 3 corpses to a transmutation rune in sub-zero temperatures to complete the ritual. \ + desc = "The ascension ritual of the Path of Void.
\ When completed, causes a violent storm of void snow \ - to assault the station, freezing and damaging heathens. Those nearby will be silenced and frozen even quicker. \ + to assault the station, freezing and damaging heathens. Those nearby will be silenced and frozen even quicker.
\ Additionally, you will become immune to the effects of space." + transmute_text = "Transmute three corpses in sub-zero temperatures." gain_text = "The world falls into darkness. I stand in an empty plane, small flakes of ice fall from the sky. \ The Aristocrat stands before me, beckoning. We will play a waltz to the whispers of dying reality, \ as the world is destroyed before our eyes. The void will return all to nothing, WITNESS MY ASCENSION!" diff --git a/code/modules/antagonists/heretic/magic/ash_jaunt.dm b/code/modules/antagonists/heretic/magic/ash_jaunt.dm index 6aaac43be905..7029cfabdf8a 100644 --- a/code/modules/antagonists/heretic/magic/ash_jaunt.dm +++ b/code/modules/antagonists/heretic/magic/ash_jaunt.dm @@ -20,59 +20,6 @@ jaunt_type = /obj/effect/dummy/phased_mob/spell_jaunt/red jaunt_in_type = /obj/effect/temp_visual/dir_setting/ash_shift jaunt_out_type = /obj/effect/temp_visual/dir_setting/ash_shift/out - /// If we are on fire while wearing ash robes, we can empower our next cast - var/empowered_cast = FALSE - -/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/Grant(mob/grant_to) - . = ..() - RegisterSignal(grant_to, COMSIG_FIRE_STACKS_UPDATED, PROC_REF(update_status_on_signal)) - -/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/Remove(mob/remove_from) - . = ..() - UnregisterSignal(remove_from, COMSIG_FIRE_STACKS_UPDATED) - -/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/apply_button_overlay(atom/movable/screen/movable/action_button/current_button, force) - . = ..() - // Put an active border whenever our spell is able to be casted empowered - if(!ishuman(owner)) - return - var/mob/living/carbon/human/human_owner = owner - if(!istype(human_owner.wear_suit, /obj/item/clothing/suit/hooded/cultrobes/eldritch/ash)) - return - if(human_owner.fire_stacks <= 3) - return - - current_button.cut_overlay(current_button.button_overlay) - current_button.button_overlay = mutable_appearance(icon = overlay_icon, icon_state = "bg_spell_border_active_green") - current_button.add_overlay(current_button.button_overlay) - -/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/cast(mob/living/cast_on) - if(!iscarbon(owner)) - return ..() - - // Wearing Ash heretic armor empowers your spells if you have over 3 fire stacks - if(!ishuman(owner)) - return ..() - var/mob/living/carbon/human/human_owner = owner - if(human_owner.fire_stacks <= 3) - return ..() - if(!istype(human_owner.wear_suit, /obj/item/clothing/suit/hooded/cultrobes/eldritch/ash)) - return ..() - - empowered_cast = TRUE - human_owner.set_stamina_loss(0) - human_owner.SetAllImmobility(0) - var/mob/living/carbon/carbon_owner = owner - carbon_owner.uncuff() - var/obj/item/clothing/shoes/shoes = carbon_owner.get_item_by_slot(ITEM_SLOT_FEET) - if(istype(shoes) && shoes.tied == SHOES_KNOTTED) - shoes.adjust_laces(SHOES_TIED, carbon_owner) - - return ..() - -/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/do_jaunt(mob/living/cast_on) - jaunt_duration = (empowered_cast ? 1.5 SECONDS : initial(jaunt_duration)) - return ..() /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/do_steam_effects() return diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm index 237fd52ec02d..e8004e088cf9 100644 --- a/code/modules/antagonists/heretic/magic/furious_steel.dm +++ b/code/modules/antagonists/heretic/magic/furious_steel.dm @@ -27,24 +27,6 @@ /// A ref to the status effect surrounding our heretic on activation. var/datum/status_effect/protective_blades/blade_effect -/datum/action/cooldown/spell/pointed/projectile/furious_steel/Grant(mob/grant_to) - . = ..() - if(!owner) - return - - if(IS_HERETIC(owner)) - RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING), PROC_REF(on_focus_lost)) - -/datum/action/cooldown/spell/pointed/projectile/furious_steel/Remove(mob/remove_from) - UnregisterSignal(remove_from, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING)) - return ..() - -/// Signal proc for [SIGNAL_REMOVETRAIT], via [TRAIT_ALLOW_HERETIC_CASTING], to remove the effect when we lose the focus trait -/datum/action/cooldown/spell/pointed/projectile/furious_steel/proc/on_focus_lost(mob/source) - SIGNAL_HANDLER - - unset_click_ability(source, refund_cooldown = TRUE) - /datum/action/cooldown/spell/pointed/projectile/furious_steel/InterceptClickOn(mob/living/clicker, params, atom/target) // Let the caster prioritize using items like guns over blade casts if(clicker.get_active_held_item()) @@ -112,7 +94,8 @@ /datum/action/cooldown/spell/pointed/projectile/furious_steel/proc/on_status_effect_triggered(datum/status_effect/protective_blades/source, atom/target) SIGNAL_HANDLER - current_amount-- + if(blade_effect == source) + current_amount -= 1 /obj/projectile/floating_blade name = "blade" diff --git a/code/modules/antagonists/heretic/magic/lunatic_track.dm b/code/modules/antagonists/heretic/magic/lunatic_track.dm index ae41e77aabeb..9d3f7ce8e4ef 100644 --- a/code/modules/antagonists/heretic/magic/lunatic_track.dm +++ b/code/modules/antagonists/heretic/magic/lunatic_track.dm @@ -24,7 +24,7 @@ owner.balloon_alert(owner, get_balloon_message(ascended_heretic)) if(ascended_heretic.stat == DEAD) - to_chat(owner, span_hierophant("[ascended_heretic] is dead. Weep for the lie has struck out.")) + to_chat(owner, span_mansus("[ascended_heretic] is dead. Weep for the lie has struck out.")) StartCooldown() return TRUE diff --git a/code/modules/antagonists/heretic/magic/rust_charge.dm b/code/modules/antagonists/heretic/magic/rust_charge.dm index 506ef1949b4b..b8fbd895a278 100644 --- a/code/modules/antagonists/heretic/magic/rust_charge.dm +++ b/code/modules/antagonists/heretic/magic/rust_charge.dm @@ -2,8 +2,7 @@ /datum/action/cooldown/mob_cooldown/charge/rust name = "Rust Charge" desc = "A charge that must be started on a rusted tile and will destroy any rusted objects you come into contact with, \ - will deal high damage to others and rust around you during the charge. \ - As it is the rust that empowers you with this ability, no focus is needed." + will deal high damage to others and rust around you during the charge." charge_distance = 10 charge_damage = 25 cooldown_time = 45 SECONDS diff --git a/code/modules/antagonists/heretic/magic/shadow_cloak.dm b/code/modules/antagonists/heretic/magic/shadow_cloak.dm index 5b7eae74e2b3..bf9c8fdb203c 100644 --- a/code/modules/antagonists/heretic/magic/shadow_cloak.dm +++ b/code/modules/antagonists/heretic/magic/shadow_cloak.dm @@ -33,6 +33,9 @@ return FALSE return isliving(cast_on) +/datum/action/cooldown/spell/shadow_cloak/is_action_active(atom/movable/screen/movable/action_button/current_button) + return !!active_cloak + /datum/action/cooldown/spell/shadow_cloak/before_cast(mob/living/cast_on) . = ..() sound = pick( @@ -49,7 +52,7 @@ /datum/action/cooldown/spell/shadow_cloak/cast(mob/living/cast_on) . = ..() if(active_cloak) - var/new_cd = max((uncloak_time - timeleft(uncloak_timer)) / 3, cooldown_time) + var/new_cd = max((uncloak_time - timeleft(uncloak_timer)), cooldown_time) uncloak_mob(cast_on) StartCooldown(new_cd) @@ -63,7 +66,7 @@ return uncloak_mob(cast_on) - StartCooldown(uncloak_timer / 3) + StartCooldown(uncloak_timer) /datum/action/cooldown/spell/shadow_cloak/proc/cloak_mob(mob/living/cast_on) playsound(cast_on, 'sound/effects/chemistry/ahaha.ogg', 50, TRUE, -1, extrarange = SILENCED_SOUND_EXTRARANGE, frequency = 0.5) @@ -74,7 +77,6 @@ active_cloak = cast_on.apply_status_effect(/datum/status_effect/shadow_cloak) RegisterSignal(active_cloak, COMSIG_QDELETING, PROC_REF(on_early_cloak_loss)) - RegisterSignal(cast_on, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING), PROC_REF(on_focus_lost)) /datum/action/cooldown/spell/shadow_cloak/proc/uncloak_mob(mob/living/cast_on, show_message = TRUE) if(!QDELETED(active_cloak)) @@ -82,7 +84,6 @@ qdel(active_cloak) active_cloak = null - UnregisterSignal(cast_on, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING)) playsound(cast_on, 'sound/effects/curse/curseattack.ogg', 50) if(show_message) cast_on.visible_message( @@ -108,18 +109,7 @@ removed.Knockdown(0.5 SECONDS) removed.add_movespeed_modifier(/datum/movespeed_modifier/shadow_cloak/early_remove) addtimer(CALLBACK(removed, TYPE_PROC_REF(/mob, remove_movespeed_modifier), /datum/movespeed_modifier/shadow_cloak/early_remove), 2 MINUTES, TIMER_UNIQUE|TIMER_OVERRIDE) - StartCooldown(uncloak_time * 2/3) - -/// Signal proc for [SIGNAL_REMOVETRAIT] via [TRAIT_ALLOW_HERETIC_CASTING], losing our focus midcast will throw us out. -/datum/action/cooldown/spell/shadow_cloak/proc/on_focus_lost(mob/living/source) - SIGNAL_HANDLER - - uncloak_mob(source, show_message = FALSE) - source.visible_message( - span_warning("[source] suddenly appears from the shadows!"), - span_userdanger("As you lose your focus, you are pulled out of the shadows!"), - ) - StartCooldown(uncloak_time / 3) + StartCooldown(uncloak_time) /// Shadow cloak effect. Conceals the owner in a cloud of purple smoke, making them unidentifiable. /// Also comes with some other buffs and debuffs - faster movespeed, slower actionspeed, etc. diff --git a/code/modules/antagonists/heretic/magic/space_crawl.dm b/code/modules/antagonists/heretic/magic/space_crawl.dm index 6ff60144f019..89e53c9b0f6c 100644 --- a/code/modules/antagonists/heretic/magic/space_crawl.dm +++ b/code/modules/antagonists/heretic/magic/space_crawl.dm @@ -90,11 +90,6 @@ RegisterSignal(holder, COMSIG_MOVABLE_MOVED, PROC_REF(update_status_on_signal)) if(iscarbon(jaunter)) jaunter.drop_all_held_items() - // Sanity check to ensure we didn't lose our focus as a result. - if(!HAS_TRAIT(jaunter, TRAIT_ALLOW_HERETIC_CASTING)) - REMOVE_TRAIT(jaunter, TRAIT_NO_TRANSFORM, REF(src)) - exit_jaunt(jaunter, our_turf) - return FALSE // Give them some space hands to prevent them from doing things var/obj/item/space_crawl/left_hand = new(jaunter) var/obj/item/space_crawl/right_hand = new(jaunter) diff --git a/code/modules/antagonists/heretic/magic/wolves_among_sheep.dm b/code/modules/antagonists/heretic/magic/wolves_among_sheep.dm index 72c1826b154a..cc107510b5b1 100644 --- a/code/modules/antagonists/heretic/magic/wolves_among_sheep.dm +++ b/code/modules/antagonists/heretic/magic/wolves_among_sheep.dm @@ -39,7 +39,6 @@ center_turf = get_turf(owner) playsound(center_turf,'sound/machines/airlock/airlockopen.ogg', 750, TRUE) to_transform = list() - new /obj/effect/heretic_rune/big(center_turf) addtimer(CALLBACK(src, PROC_REF(create_arena), center_turf), 1 SECONDS) revert_timer = addtimer(CALLBACK(src, PROC_REF(revert_effects)), 61 SECONDS, TIMER_STOPPABLE) // 1 second to spread out, 60 seconds to fight diff --git a/code/modules/antagonists/heretic/status_effects/buffs.dm b/code/modules/antagonists/heretic/status_effects/buffs.dm index 3c8c2f5b342f..f0a10abf8c59 100644 --- a/code/modules/antagonists/heretic/status_effects/buffs.dm +++ b/code/modules/antagonists/heretic/status_effects/buffs.dm @@ -227,6 +227,7 @@ return SEND_SIGNAL(src, COMSIG_BLADE_BARRIER_TRIGGERED) + SEND_SIGNAL(source, COMSIG_MOB_BLADE_BARRIER_TRIGGERED, src) ADD_TRAIT(source, TRAIT_BEING_BLADE_SHIELDED, TRAIT_STATUS_EFFECT(id)) addtimer(TRAIT_CALLBACK_REMOVE(source, TRAIT_BEING_BLADE_SHIELDED, TRAIT_STATUS_EFFECT(id)), 0.1 SECONDS) @@ -296,7 +297,6 @@ /datum/status_effect/caretaker_refuge/on_apply() animate(owner, alpha = 45, time = 0.5 SECONDS) owner.set_density(FALSE) - RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING), PROC_REF(on_focus_lost)) RegisterSignal(owner, COMSIG_MOB_BEFORE_SPELL_CAST, PROC_REF(prevent_spell_usage)) RegisterSignal(owner, COMSIG_ATOM_HOLYATTACK, PROC_REF(nullrod_handler)) RegisterSignal(owner, COMSIG_CARBON_CUFF_ATTEMPTED, PROC_REF(prevent_cuff)) @@ -308,7 +308,6 @@ owner.remove_traits(caretaking_traits, TRAIT_STATUS_EFFECT(id)) owner.alpha = initial(owner.alpha) owner.density = initial(owner.density) - UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING)) UnregisterSignal(owner, COMSIG_MOB_BEFORE_SPELL_CAST) UnregisterSignal(owner, COMSIG_ATOM_HOLYATTACK) UnregisterSignal(owner, COMSIG_CARBON_CUFF_ATTEMPTED) @@ -327,11 +326,6 @@ owner.visible_message(span_warning("[weapon] repels the haze around [owner]!")) owner.remove_status_effect(type) -/datum/status_effect/caretaker_refuge/proc/on_focus_lost() - SIGNAL_HANDLER - to_chat(owner, span_danger("Without a focus, your refuge weakens and dissipates!")) - qdel(src) - /datum/status_effect/caretaker_refuge/proc/no_strip(atom/source, mob/user, obj/item/equipping) SIGNAL_HANDLER to_chat(user, span_warning("You fail to put anything on [source] as they are incorporeal!")) diff --git a/code/modules/antagonists/heretic/status_effects/heretic_passive.dm b/code/modules/antagonists/heretic/status_effects/heretic_passive.dm index 08d2292d677a..c30ce76318c0 100644 --- a/code/modules/antagonists/heretic/status_effects/heretic_passive.dm +++ b/code/modules/antagonists/heretic/status_effects/heretic_passive.dm @@ -14,6 +14,9 @@ var/passive_level = HERETIC_LEVEL_START /// Name of the passive, used by the UI var/name = "Heretic Passive" + /// Describes how users of this path recharge their spells + var/recharge_description = "Recharge spells by doing things." + /// Describes the various levels of the passive var/list/passive_descriptions = list( "Grants you a passive ability based on your heretic type. This ability will upgrade as you gain more power.", "Your passive ability has been upgraded, doing something else.", @@ -64,6 +67,17 @@ heretic_datum.passive_level = HERETIC_LEVEL_FINAL heretic_datum.update_data_for_all_viewers() +/datum/status_effect/heretic_passive/proc/recharge_spells() + owner.balloon_alert(owner, "spells recharged") + var/list/main_path_knowledge = heretic_datum.get_researched_knowledge_by_category(HERETIC_KNOWLEDGE_TREE) \ + + heretic_datum.get_researched_knowledge_by_category(HERETIC_KNOWLEDGE_START) + var/list/side_path_knowledge = heretic_datum.get_researched_knowledge_by_category(HERETIC_KNOWLEDGE_DRAFT) \ + + heretic_datum.get_researched_knowledge_by_category(HERETIC_KNOWLEDGE_SHOP) + + for(var/datum/heretic_knowledge/spell/spell in main_path_knowledge) + spell.add_charges(ceil(spell.max_charges * spell.path_recharge_amount), uncapped = spell.path_recharge_can_surpass_cap) + for(var/datum/heretic_knowledge/spell/spell in side_path_knowledge) + spell.add_charges(ceil(spell.max_charges * spell.path_recharge_amount * 0.5)) //---- Ash Passive // Level 1 grants heat and ash storm immunity @@ -71,11 +85,14 @@ // Level 3 grants resistance to high pressure /datum/status_effect/heretic_passive/ash name = "Vow of Destruction" + recharge_description = "Recharge spells by standing near living, burning foes." passive_descriptions = list( "Heat and ash storm immunity.", "Lava immunity.", "Resistance to high and low pressure." ) + /// Tracks total seconds nearby mobs are on fire, used to determine when to recharge spells + var/seconds_of_fire = 0 /datum/status_effect/heretic_passive/ash/on_apply() . = ..() @@ -93,6 +110,26 @@ owner.remove_traits(list(TRAIT_RESISTHEAT, TRAIT_ASHSTORM_IMMUNE, TRAIT_LAVA_IMMUNE, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTLOWPRESSURE), REF(src)) return ..() +/datum/status_effect/heretic_passive/ash/tick(seconds_between_ticks) + . = ..() + var/seconds_gained = 0 + for(var/mob/living/nearby_guy in view(owner, 3)) + if(!nearby_guy.on_fire || nearby_guy.stat == DEAD || nearby_guy == owner) + continue + if(ismonkey(nearby_guy) && isnull(nearby_guy.mind)) + continue + + seconds_gained += seconds_between_ticks + if(seconds_gained >= 6) + break + + seconds_of_fire += seconds_gained + if(seconds_of_fire < 30) + return + + seconds_of_fire = 0 + recharge_spells() + //---- Blade Passive // Gives you riposte while wielding a heretic blade // Cooldown starts at 20 and goes down 5 seconds per level @@ -102,6 +139,8 @@ /datum/status_effect/heretic_passive/blade name = "Dance of the Brand" id = "blade_passive" + recharge_description = "Recharge spells by knocking brave foes into critical condition. \ + (Brave foes are anyone who struck you first.)" passive_descriptions = list( "Being attacked while wielding a Heretic Blade in either hand will deliver a free, instant counterattack to the attacker. This effect can only trigger once every 20 seconds.", "Immunity to fall damage.", @@ -114,10 +153,34 @@ /// Whether the counter-attack is ready or not. /// Used so we can give feedback when it's ready again var/riposte_ready = TRUE + /// Lazylist of refs()s to mobs we've attacked in the last 30 seconds + VAR_PRIVATE/list/recently_attacked_refs + /// Lazylist of refs()s to mobs that have attacked us in the last 2 minutes before we attacked them + VAR_PRIVATE/list/recently_attacked_us_refs /datum/status_effect/heretic_passive/blade/on_apply() . = ..() RegisterSignal(owner, COMSIG_LIVING_CHECK_BLOCK, PROC_REF(on_shield_reaction)) + RegisterSignal(owner, COMSIG_USER_PRE_ITEM_ATTACK, PROC_REF(hit_someone)) + +/datum/status_effect/heretic_passive/blade/proc/hit_someone(mob/living/source, mob/living/target, obj/item/used_weapon) + SIGNAL_HANDLER + + if(!isliving(target) || target == source) + return + + // place the attacked mob in the "recently attacked" list so we can mark honorable foes + var/target_key = REF(target) + LAZYOR(recently_attacked_refs, target_key) + addtimer(CALLBACK(src, PROC_REF(remove_attacked_ref), target_key), 30 SECONDS, TIMER_DELETE_ME|TIMER_UNIQUE|TIMER_OVERRIDE) + + if(target.stat == STABLE && LAZYFIND(recently_attacked_us_refs, target_key)) + addtimer(CALLBACK(src, PROC_REF(check_crit), target), 0.2 SECONDS, TIMER_DELETE_ME|TIMER_UNIQUE) + +/datum/status_effect/heretic_passive/blade/proc/check_crit(mob/living/target) + if(QDELETED(target) || target.stat == STABLE) + return + recharge_spells() /datum/status_effect/heretic_passive/blade/heretic_level_upgrade() . = ..() @@ -125,7 +188,13 @@ /datum/status_effect/heretic_passive/blade/on_remove() . = ..() - UnregisterSignal(owner, list(COMSIG_LIVING_CHECK_BLOCK, COMSIG_LIVING_Z_IMPACT)) + UnregisterSignal(owner, list(COMSIG_LIVING_CHECK_BLOCK, COMSIG_LIVING_Z_IMPACT, COMSIG_USER_PRE_ITEM_ATTACK)) + +/datum/status_effect/heretic_passive/blade/proc/remove_attacked_ref(attacked_key) + LAZYREMOVE(recently_attacked_refs, attacked_key) + +/datum/status_effect/heretic_passive/blade/proc/remove_attacker_ref(attacker_key) + LAZYREMOVE(recently_attacked_us_refs, attacker_key) /// Blocks the effects from falling /datum/status_effect/heretic_passive/blade/proc/z_impact_react(datum/source, levels, turf/fell_on) @@ -148,6 +217,16 @@ ) SIGNAL_HANDLER + var/mob/living/attacker = hitby.loc + if(!istype(attacker)) + return + + // place the attacker in the list of "recently attacked us" if we did not attack them first + var/attacker_key = REF(attacker) + if(!LAZYFIND(recently_attacked_refs, attacker_key)) + LAZYADD(recently_attacked_us_refs, attacker_key) + addtimer(CALLBACK(src, PROC_REF(remove_attacker_ref), attacker_key), 2 MINUTES, TIMER_DELETE_ME|TIMER_UNIQUE|TIMER_OVERRIDE) + if(attack_type != MELEE_ATTACK) return @@ -157,10 +236,6 @@ if(INCAPACITATED_IGNORING(source, INCAPABLE_GRAB)) return - var/mob/living/attacker = hitby.loc - if(!istype(attacker)) - return - if(!source.Adjacent(attacker)) return @@ -216,6 +291,7 @@ /datum/status_effect/heretic_passive/cosmic name = "Chosen of the Stars" id = "cosmic_passive" + recharge_description = "Recharge spells by knocking foes standing in cosmic fields into critical condition." passive_descriptions = list( "Cosmic fields speed you up and regenerate stamina.", "Cosmic fields disrupt grenades or signalers from being activated and turn off already primed grenades.", @@ -228,6 +304,28 @@ var/delta_time = DELTA_WORLD_TIME(SSmobs) * 0.5 // SSmobs.wait is 2 secs, so this should be halved. owner.adjust_stamina_loss(-15 * delta_time, updating_stamina = FALSE) +/datum/status_effect/heretic_passive/cosmic/on_apply() + . = ..() + RegisterSignal(owner, COMSIG_USER_PRE_ITEM_ATTACK, PROC_REF(hit_someone)) + +/datum/status_effect/heretic_passive/cosmic/on_remove() + . = ..() + UnregisterSignal(owner, COMSIG_USER_PRE_ITEM_ATTACK) + +/datum/status_effect/heretic_passive/cosmic/proc/hit_someone(mob/living/source, mob/living/target, obj/item/used_weapon) + SIGNAL_HANDLER + + if(!isliving(target) || target == source || target.stat <= HARD_CRIT) + return + + if(locate(/obj/effect/forcefield/cosmic_field) in target.loc) + addtimer(CALLBACK(src, PROC_REF(check_crit), target), 0.2 SECONDS, TIMER_DELETE_ME|TIMER_UNIQUE) + +/datum/status_effect/heretic_passive/cosmic/proc/check_crit(mob/living/target) + if(QDELETED(target) || target.stat == STABLE) + return + recharge_spells() + /** * Creates a cosmic field at a given loc * @@ -261,6 +359,7 @@ /datum/status_effect/heretic_passive/flesh name = "Ravenous Hunger" id = "flesh_passive" + recharge_description = "Recharge spells by summoning monsters into reality." passive_descriptions = list( "Immunity to Diseases, Disgust and space ants.", "Eating organs or meat now heals you, gain the voracious and gluttonous trait and being fat doesn't slow you down.", @@ -270,6 +369,7 @@ /datum/status_effect/heretic_passive/flesh/on_apply() . = ..() owner.add_traits(list(TRAIT_VIRUSIMMUNE, TRAIT_SPACE_ANT_IMMUNITY), REF(src)) + RegisterSignal(owner, COMSIG_HERETIC_SUMMONED_MOB, PROC_REF(on_summon)) /datum/status_effect/heretic_passive/flesh/tick(seconds_between_ticks) . = ..() @@ -325,6 +425,7 @@ /// Gives/Removes damage resistance when we become/lose fatness /datum/status_effect/heretic_passive/flesh/proc/on_fat(datum/source) + SIGNAL_HANDLER if(!ishuman(owner)) return var/mob/living/carbon/human/heretic = owner @@ -335,10 +436,14 @@ heretic.physiology.damage_resistance -= 25 REMOVE_TRAIT(heretic, TRAIT_BATON_RESISTANCE, REF(src)) +/datum/status_effect/heretic_passive/flesh/proc/on_summon(datum/source, mob/living/summoned) + SIGNAL_HANDLER + recharge_spells() + /datum/status_effect/heretic_passive/flesh/on_remove() . = ..() owner.remove_traits(list(TRAIT_VIRUSIMMUNE, TRAIT_SPACE_ANT_IMMUNITY, TRAIT_FAT_IGNORE_SLOWDOWN, TRAIT_VORACIOUS, TRAIT_GLUTTON, TRAIT_BATON_RESISTANCE), REF(src)) - UnregisterSignal(owner, list(COMSIG_LIVING_EAT_FOOD, SIGNAL_ADDTRAIT(TRAIT_FAT), SIGNAL_REMOVETRAIT(TRAIT_FAT))) + UnregisterSignal(owner, list(COMSIG_LIVING_EAT_FOOD, SIGNAL_ADDTRAIT(TRAIT_FAT), SIGNAL_REMOVETRAIT(TRAIT_FAT), COMSIG_HERETIC_SUMMONED_MOB)) if(!ishuman(owner)) return var/mob/living/carbon/human/heretic = owner @@ -355,16 +460,20 @@ /datum/status_effect/heretic_passive/lock name = "Open Invitation" id = "lock_passive" + recharge_description = "Recharge spells by researching knowledge." passive_descriptions = list( "Shock insulation, all knowledges researched from the shop are cheaper", "X-ray vision, you can see through walls and objects.", "Grasp no longer goes on cooldown when used to open a door or locker." ) + /// Tracks points needed to be spent to recharge + var/points_to_recharge = 2 /datum/status_effect/heretic_passive/lock/on_apply() . = ..() ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, REF(src)) RegisterSignal(heretic_datum, COMSIG_HERETIC_SHOP_SETUP, PROC_REF(on_shop_setup)) // Just in case we are applying this after the shop was set up + RegisterSignal(heretic_datum, COMSIG_HERETIC_RESEARCHED_KNOWLEDGE, PROC_REF(on_research)) // Recharge spells whenever we research something /datum/status_effect/heretic_passive/lock/heretic_level_upgrade() . = ..() @@ -376,7 +485,8 @@ ADD_TRAIT(owner, TRAIT_LOCK_GRASP_UPGRADED, REF(src)) /datum/status_effect/heretic_passive/lock/on_remove() - UnregisterSignal(owner, COMSIG_HERETIC_SHOP_SETUP) + UnregisterSignal(heretic_datum, COMSIG_HERETIC_SHOP_SETUP) + UnregisterSignal(heretic_datum, COMSIG_HERETIC_RESEARCHED_KNOWLEDGE) owner.remove_traits(list(TRAIT_SHOCKIMMUNE, TRAIT_XRAY_VISION, TRAIT_LOCK_GRASP_UPGRADED), REF(src)) owner.update_sight() return ..() @@ -389,6 +499,14 @@ if(heretic_info) heretic_info[HKT_COST] = max(1, heretic_info[HKT_COST] - 1) // Reduce cost by 1, minimum of 1 +/datum/status_effect/heretic_passive/lock/proc/on_research(datum/source, datum/heretic_knowledge/researched_knowledge) + SIGNAL_HANDLER + + points_to_recharge -= researched_knowledge.cost + if(points_to_recharge <= 0) + recharge_spells() + points_to_recharge = initial(points_to_recharge) + //---- Moon Passive // Heals 5 brain damage per level // Prevents brain trauma @@ -397,13 +515,14 @@ /datum/status_effect/heretic_passive/moon name = "Do You Hear The Voices Too?" id = "moon_passive" + recharge_description = "Recharge spells by applying Moonlight Amulets to heathens." passive_descriptions = list( "Can no longer develop brain traumas, passively regenerates brain health, (this bonus is halved in combat).", "Sleep immunity, increases the ratio at which your brain damage regenerates.", "Mind gate and Ringleader's rise will channel the moon amulet effects, further inreases brain regeneration." ) /// Built-in moon amulet which channels through your spells - var/obj/item/clothing/neck/heretic_focus/moon_amulet/amulet + var/obj/item/clothing/neck/moon_amulet/amulet /// When were we last attacked? var/last_attack = 0 /// How long the combat tag lasts for @@ -413,6 +532,7 @@ /datum/status_effect/heretic_passive/moon/on_apply() . = ..() + RegisterSignal(owner, COMSIG_MOB_APPLIED_MOONLIGHT_AMULET, PROC_REF(on_amulet)) var/obj/item/organ/brain/our_brain = owner.get_organ_slot(ORGAN_SLOT_BRAIN) if(!our_brain) return @@ -420,6 +540,10 @@ owner.AddElement(/datum/element/relay_attackers) RegisterSignal(owner, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) +/datum/status_effect/heretic_passive/moon/proc/on_amulet(datum/source, ...) + SIGNAL_HANDLER + recharge_spells() + /// Saves world.time when we are attacked by anything /datum/status_effect/heretic_passive/moon/proc/on_attacked(mob/victim, atom/attacker) SIGNAL_HANDLER @@ -450,6 +574,7 @@ amulet = new() /datum/status_effect/heretic_passive/moon/on_remove() + UnregisterSignal(owner, COMSIG_MOB_APPLIED_MOONLIGHT_AMULET) var/obj/item/organ/brain/our_brain = owner.get_organ_slot(ORGAN_SLOT_BRAIN) if(!our_brain) return ..() @@ -467,20 +592,27 @@ /datum/status_effect/heretic_passive/rust name = "Leeching Walk" id = "rust_passive" + recharge_description = "Recharge spells by a rusting a large number of tiles. \ + (Rusting from items and summons are not counted.)" passive_descriptions = list( "Standing on Rusted tiles heals and purge chems off your body.", "Standing on Rusted tiles closes up your wounds and heals your organs, you may now rust reinforced floors and walls, healing effect increased.", "Standing on Rusted tiles regenerates your limbs, you may now rust titanium and plastitanium walls, healing effect increased." ) + /// Counts tiles rusted, total + var/rust_counter = 0 + /// Counts down as tiles are rusted, when it hits 0 we reset and give spell charges + var/rust_counter_required = 60 /datum/status_effect/heretic_passive/rust/on_apply() . = ..() RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) RegisterSignal(owner, COMSIG_LIVING_LIFE, PROC_REF(on_life)) + RegisterSignal(owner, COMSIG_MOB_RUST_HERETIC_ACT, PROC_REF(on_rust_tile_rusted)) /datum/status_effect/heretic_passive/rust/on_remove() . = ..() - UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_LIFE)) + UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_LIFE, COMSIG_MOB_RUST_HERETIC_ACT)) /datum/status_effect/heretic_passive/rust/heretic_level_upgrade() . = ..() @@ -492,6 +624,18 @@ if(heretic_datum.rust_strength < 3) heretic_datum.increase_rust_strength() // Bring us up to 3 +/datum/status_effect/heretic_passive/rust/proc/on_rust_tile_rusted(mob/living/heretic, turf/rusted_target, result) + SIGNAL_HANDLER + + if(!isturf(rusted_target) || !HAS_TRAIT(rusted_target, TRAIT_RUSTY) || !result) + return + + rust_counter += 1 + rust_counter_required -= 1 + if(rust_counter_required <= 0) + rust_counter_required = initial(rust_counter_required) + recharge_spells() + /* * Signal proc for [COMSIG_MOVABLE_MOVED]. * @@ -560,15 +704,19 @@ /datum/status_effect/heretic_passive/void name = "Aristocrat's Way" id = "void_passive" + recharge_description = "Recharge spells by knocking foes who are exposed to sub-zero temperature into critical condition." passive_descriptions = list( "Cold and low pressure immunity.", "You no longer need to breathe.", "Water, ice and slippery surfaces no slip you." ) + var/list/gained_charges_from + /datum/status_effect/heretic_passive/void/on_apply() . = ..() owner.add_traits(list(TRAIT_RESISTCOLD, TRAIT_RESISTLOWPRESSURE), REF(src)) + RegisterSignal(owner, COMSIG_USER_PRE_ITEM_ATTACK, PROC_REF(hit_someone)) /datum/status_effect/heretic_passive/void/heretic_level_upgrade() . = ..() @@ -581,6 +729,27 @@ /datum/status_effect/heretic_passive/void/on_remove() . = ..() owner.remove_traits(list(TRAIT_RESISTCOLD, TRAIT_RESISTLOWPRESSURE, TRAIT_NOBREATH, TRAIT_NO_SLIP_WATER, TRAIT_NO_SLIP_ICE, TRAIT_NO_SLIP_SLIDE), REF(src)) + UnregisterSignal(owner, COMSIG_USER_PRE_ITEM_ATTACK) + +/datum/status_effect/heretic_passive/void/proc/hit_someone(mob/living/source, mob/living/target, obj/item/used_weapon) + SIGNAL_HANDLER + + if(!isliving(target) || target == source || target.stat <= HARD_CRIT || LAZYFIND(gained_charges_from, REF(target))) + return + + var/turf/target_turf = get_turf(target) + if(target_turf?.GetTemperature() <= T0C) + addtimer(CALLBACK(src, PROC_REF(check_crit), target), 0.2 SECONDS, TIMER_DELETE_ME|TIMER_UNIQUE) + +/datum/status_effect/heretic_passive/void/proc/check_crit(mob/living/target) + if(QDELETED(target) || target.stat == STABLE) + return + LAZYADD(gained_charges_from, REF(target)) + addtimer(CALLBACK(src, PROC_REF(remove_gained_from), REF(target)), 5 MINUTES, TIMER_DELETE_ME|TIMER_UNIQUE) + recharge_spells() + +/datum/status_effect/heretic_passive/void/proc/remove_gained_from(target_ref) + LAZYREMOVE(gained_charges_from, target_ref) #undef HERETIC_LEVEL_START #undef HERETIC_LEVEL_UPGRADE diff --git a/code/modules/antagonists/heretic/structures/mawed_crucible.dm b/code/modules/antagonists/heretic/structures/mawed_crucible.dm index 51fe408dfefe..56fa5f703766 100644 --- a/code/modules/antagonists/heretic/structures/mawed_crucible.dm +++ b/code/modules/antagonists/heretic/structures/mawed_crucible.dm @@ -75,7 +75,7 @@ // no breaky herety thingy /obj/structure/destructible/eldritch_crucible/rust_heretic_act() - return + return FALSE /obj/structure/destructible/eldritch_crucible/item_interaction(mob/living/user, obj/item/tool, list/modifiers) if(istype(tool, /obj/item/codex_cicatrix) || istype(tool, /obj/item/melee/touch_attack/mansus_fist)) diff --git a/code/modules/antagonists/heretic/magic/void_conduit.dm b/code/modules/antagonists/heretic/structures/void_conduit.dm similarity index 83% rename from code/modules/antagonists/heretic/magic/void_conduit.dm rename to code/modules/antagonists/heretic/structures/void_conduit.dm index 4ba5e0d547b6..7a27d79a9040 100644 --- a/code/modules/antagonists/heretic/magic/void_conduit.dm +++ b/code/modules/antagonists/heretic/structures/void_conduit.dm @@ -1,28 +1,5 @@ -/datum/action/cooldown/spell/conjure/void_conduit - name = "Void Conduit" - desc = "Opens a gate to the Void; it releases an intermittent pulse that damages windows and airlocks, \ - while afflicting Heathens with void chill. \ - Affected Heretics instead receive low pressure resistance." - background_icon_state = "bg_heretic" - overlay_icon_state = "bg_heretic_border" - button_icon = 'icons/mob/actions/actions_ecult.dmi' - button_icon_state = "void_rift" - - cooldown_time = 1 MINUTES - - sound = null - school = SCHOOL_FORBIDDEN - invocation = "MBR'C' TH' V''D!" - invocation_type = INVOCATION_SHOUT - spell_requirements = NONE - - summon_radius = 0 - summon_type = list(/obj/structure/void_conduit) - summon_respects_density = TRUE - summon_respects_prev_spawn_points = TRUE - /obj/structure/void_conduit - name = "Void Conduit" + name = "void conduit" desc = "An open gate which leads to nothingness. Releases pulses which you do not want to get hit by." icon = 'icons/effects/effects.dmi' icon_state = "void_conduit" @@ -124,3 +101,14 @@ /datum/status_effect/void_conduit/on_remove() REMOVE_TRAIT(owner, TRAIT_RESISTLOWPRESSURE, TRAIT_STATUS_EFFECT(id)) + +/obj/effect/temp_visual/void_conduit_opening + icon = /obj/structure/void_conduit::icon + icon_state = /obj/structure/void_conduit::icon_state + duration = 5 SECONDS + +/obj/effect/temp_visual/void_conduit_opening/Initialize(mapload) + . = ..() + transform = transform.Scale(0.1) + alpha = 0 + animate(src, time = 5 SECONDS, transform = transform.Scale(1), alpha = /obj/structure/void_conduit::alpha) diff --git a/code/modules/antagonists/heretic/transmutation_rune.dm b/code/modules/antagonists/heretic/transmutation_rune.dm index 0bce4ce6ffac..5715565aa8e0 100644 --- a/code/modules/antagonists/heretic/transmutation_rune.dm +++ b/code/modules/antagonists/heretic/transmutation_rune.dm @@ -59,12 +59,45 @@ is_in_use = FALSE return - var/chosen = tgui_input_list(user, "Chose a ritual to attempt.", "Chose a Ritual", rituals) - if(!chosen || !istype(rituals[chosen], /datum/heretic_knowledge) || QDELETED(src) || QDELETED(user) || QDELETED(heretic_datum)) + var/list/ritual_radial = list() + var/list/ritual_to_name = list() + for(var/datum/heretic_knowledge/ritual as anything in rituals) + var/ritual_info = "" + var/list/ritual_requirements = list() + for(var/req_type, req_amount in ritual.required_atoms) + if(islist(req_type)) + var/list/req_type_list = req_type + var/list/req_text_list = list() + for(var/atom/possible_type as anything in req_type_list) + req_text_list += ritual.parse_required_item(possible_type) + ritual_requirements += english_list(req_text_list, and_text = "or") + + else + ritual_requirements += ritual.parse_required_item(req_type) + + if(length(ritual_requirements)) + ritual_info = "Requires: [english_list(ritual_requirements)]" + + var/list/ritual_icon_info = heretic_datum.get_icon_of_knowledge(ritual.type) + var/icon/ritual_icon = icon(ritual_icon_info["icon"], ritual_icon_info["state"], ritual_icon_info["dir"], ritual_icon_info["frame"]) + var/image/ritual_background = image(icon = 'icons/ui_icons/antags/heretic/knowledge.dmi', icon_state = heretic_datum.researched_knowledge[ritual.type][HKT_UI_BGR]) + + var/image/ritual_image = image(ritual_icon) + ritual_image.underlays += ritual_background + + var/datum/radial_menu_choice/choice = new() + choice.name = ritual.name + choice.info = ritual_info + choice.image = ritual_image + ritual_radial[ritual.name] = choice + ritual_to_name[ritual.name] = ritual + + var/chosen = show_radial_menu(user, loc, ritual_radial, radius = 48, require_near = TRUE) + if(!chosen || !istype(ritual_to_name[chosen], /datum/heretic_knowledge) || QDELETED(src) || QDELETED(user) || QDELETED(heretic_datum)) is_in_use = FALSE return - do_ritual(user, rituals[chosen]) + do_ritual(user, ritual_to_name[chosen]) is_in_use = FALSE /** @@ -143,32 +176,28 @@ // All of the atoms have been checked, let's see if the ritual was successful var/list/what_are_we_missing = list() - for(var/req_type in requirements_list) - var/number_of_things = requirements_list[req_type] + for(var/req_type, fulfilled_amount in requirements_list) // <= 0 means it's fulfilled, skip - if(number_of_things <= 0) + if(fulfilled_amount <= 0) continue // > 0 means it's unfilfilled - the ritual has failed, we should tell them why // Lets format the thing they're missing and put it into our list - var/formatted_thing = "[number_of_things] " if(islist(req_type)) var/list/req_type_list = req_type var/list/req_text_list = list() - for(var/atom/possible_type as anything in req_type_list) - req_text_list += ritual.parse_required_item(possible_type) - formatted_thing += english_list(req_text_list, and_text = "or") + for(var/possible_type, needed_amount in req_type_list) + req_text_list += ritual.parse_required_item(possible_type, fulfilled_amount) + what_are_we_missing += english_list(req_text_list, and_text = "or") else - formatted_thing = ritual.parse_required_item(req_type) - - what_are_we_missing += formatted_thing + what_are_we_missing += ritual.parse_required_item(req_type) if(length(what_are_we_missing)) // Let them know it screwed up loc.balloon_alert(user, "ritual failed, missing components!") // Then let them know what they're missing - to_chat(user, span_hierophant_warning("You are missing [english_list(what_are_we_missing)] in order to complete the ritual \"[ritual.name]\".")) + to_chat(user, span_mansus("You are missing [english_list(what_are_we_missing)] in order to complete the ritual \"[ritual.name]\".")) return FALSE //Everything's good, proceed and collect from the available stacks what's needed if needed. @@ -188,8 +217,7 @@ // If we made it here, the ritual had all necessary components, and we can try to cast it. // This doesn't necessarily mean the ritual will succeed, but it's valid! // Do the animations and associated feedback. - flick("[icon_state]_active", src) - playsound(user, 'sound/effects/magic/castsummon.ogg', 50, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_exponent = 10, ignore_walls = FALSE) + ritual_animation() // - We temporarily make all of our chosen atoms invisible, as some rituals may sleep, // and we don't want people to be able to run off with ritual items. @@ -207,6 +235,7 @@ if(ritual_result) ritual.cleanup_atoms(selected_atoms) + SSblackbox.record_feedback("tally", "heretic_ritual_completed", 1, ritual.type) // Clean up done, re-appear anything that hasn't been deleted. for(var/atom/to_appear as anything in initial_selected_atoms) @@ -226,6 +255,9 @@ return ritual_result +/obj/effect/heretic_rune/proc/ritual_animation() + flick("[icon_state]_active", src) + playsound(src, 'sound/effects/magic/castsummon.ogg', 50, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_exponent = 10, ignore_walls = FALSE) /// A 3x3 heretic rune. The kind heretics actually draw in game. /obj/effect/heretic_rune/big diff --git a/code/modules/fishing/sources/subtypes/rifts.dm b/code/modules/fishing/sources/subtypes/rifts.dm index c7b832bba8f7..5ab994f8e470 100644 --- a/code/modules/fishing/sources/subtypes/rifts.dm +++ b/code/modules/fishing/sources/subtypes/rifts.dm @@ -158,7 +158,7 @@ var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user) if(heretic_datum) heretic_datum.adjust_knowledge_points(1) - to_chat(user, "[span_hear("You hear a whisper...")] [span_hypnophrase("THE HIGHER I RISE, THE MORE I FISH.")]") + to_chat(user, "[span_hear("You hear a whisper...")] [span_mansus("THE HIGHER I RISE, THE MORE I FISH.")]") // They can also gain an extra influence point if they infused their rod. if(HAS_TRAIT(challenge.used_rod, TRAIT_ROD_MANSUS_INFUSED)) heretic_datum.adjust_knowledge_points(1) diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index e6da3fe12311..6898879b50ba 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -788,6 +788,7 @@ GLOBAL_LIST_INIT(command_strings, list( /mob/living/basic/bot/rust_heretic_act() adjust_brute_loss(400) + return TRUE /mob/living/basic/bot/proc/retrieve_access(mob/bot, list/player_access) SIGNAL_HANDLER diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index fda2ed6b1154..d9a788cacc00 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -300,6 +300,7 @@ /mob/living/silicon/rust_heretic_act() adjust_brute_loss(500) + return TRUE /mob/living/silicon/on_floored_start() return // Silicons are always standing by default. diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 495fee12c8d5..f8dfc05b3b36 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -315,6 +315,7 @@ randomized_spawns = REAGENT_SPAWN_ALL_RANDOM_SPAWNS default_container = /obj/item/reagent_containers/cup/glass/bottle/holywater metabolized_traits = list(TRAIT_HOLY) + COOLDOWN_DECLARE(spell_clear_cd) /datum/glass_style/drinking_glass/holywater required_drink_type = /datum/reagent/water/holywater @@ -350,14 +351,26 @@ affected_mob.adjust_jitter_up_to(2 SECONDS * metabolization_ratio * seconds_per_tick, 20 SECONDS) var/need_mob_update = FALSE - if(IS_CULTIST(affected_mob)) - for(var/datum/action/innate/cult/blood_magic/BM in affected_mob.actions) + if(COOLDOWN_FINISHED(src, spell_clear_cd)) + if(IS_CULTIST(affected_mob)) + for(var/datum/action/innate/cult/blood_magic/BM in affected_mob.actions) + var/removed_any = FALSE + for(var/datum/action/innate/cult/blood_spell/BS in BM.spells) + removed_any = TRUE + qdel(BS) + if(removed_any) + to_chat(affected_mob, span_cult_large("Your blood rites falter as holy water scours your body!")) + COOLDOWN_START(src, spell_clear_cd, 3 SECONDS) + + if(IS_HERETIC(affected_mob)) + var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(affected_mob) var/removed_any = FALSE - for(var/datum/action/innate/cult/blood_spell/BS in BM.spells) - removed_any = TRUE - qdel(BS) + for(var/datum/heretic_knowledge/spell/spell in heretic_datum.get_researched_knowledge()) + if(spell.remove_charges(ceil(spell.max_charges * spell.holywater_drain_amount))) + removed_any = TRUE if(removed_any) - to_chat(affected_mob, span_cult_large("Your blood rites falter as holy water scours your body!")) + to_chat(affected_mob, span_mansus("Your intricate rituals are distrupted by the holy water scouring your body!")) + COOLDOWN_START(src, spell_clear_cd, 3 SECONDS) if(data["deciseconds_metabolized"] >= (25 SECONDS)) // 10 units affected_mob.adjust_stutter_up_to(2 SECONDS * metabolization_ratio * seconds_per_tick, 20 SECONDS) diff --git a/code/modules/spells/spell_types/touch/_touch.dm b/code/modules/spells/spell_types/touch/_touch.dm index 507cba274adb..16781e9cbf78 100644 --- a/code/modules/spells/spell_types/touch/_touch.dm +++ b/code/modules/spells/spell_types/touch/_touch.dm @@ -216,6 +216,7 @@ else if(!cast_on_hand_hit(hand, victim, caster)) return NONE + SEND_SIGNAL(caster, COMSIG_SPELL_TOUCH_SPELL_ACTUALLY_CAST, src, victim) log_combat(caster, victim, "cast the touch spell [name] on", hand) INVOKE_ASYNC(src, PROC_REF(spell_feedback), caster) caster.do_attack_animation(victim) diff --git a/code/modules/unit_tests/heretic_rituals.dm b/code/modules/unit_tests/heretic_rituals.dm index 63c4c76ba8b5..ebc4798d4e5f 100644 --- a/code/modules/unit_tests/heretic_rituals.dm +++ b/code/modules/unit_tests/heretic_rituals.dm @@ -69,8 +69,7 @@ ritual_item_path = pick(ritual_item_path) for(var/i in 1 to amount_to_create) var/obj/item/item = new ritual_item_path(get_turf(our_heretic)) - if(isitem(item)) - item.item_flags &= ~ABSTRACT + knowledge.prepare_atom_for_ritual_test(item) created_atoms += item // Now, we can ACTUALLY run the ritual. Let's do it. diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index c1928dfe8b1d..17efb7432b83 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -441,7 +441,8 @@ return cell /obj/vehicle/sealed/mecha/rust_heretic_act() - take_damage(500, BRUTE) + take_damage(500, BRUTE) + return TRUE /obj/vehicle/sealed/mecha/proc/restore_equipment() equipment_disabled = FALSE diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm index d2be2166e0ae..4060bcc9cfc1 100644 --- a/interface/stylesheet.dm +++ b/interface/stylesheet.dm @@ -154,6 +154,6 @@ h1.alert, h2.alert {color: #000000;} .policy {color: #9730db; font-style: italic; text-align: center; font-size: 2;} .upside_down {display: inline; -moz-transform: scale(-1, -1); -webkit-transform: scale(-1, -1); -o-transform: scale(-1, -1); -ms-transform: scale(-1, -1); transform: scale(-1, -1);} - +.mansus {color: #0b8000;} .cyan {color: #bde0dc;} "} diff --git a/strings/antagonist_flavor/heretic_influences.json b/strings/antagonist_flavor/heretic_influences.json index 0b15c074a635..73ebf1ccaf8c 100644 --- a/strings/antagonist_flavor/heretic_influences.json +++ b/strings/antagonist_flavor/heretic_influences.json @@ -80,5 +80,13 @@ "We don't deserve this... do we?", "Are we prepared for this?", "We thought we were predators, but we are prey." + ], + "hypnosis": [ + "I AM BEING WATCHED. FROM EVERYWHERE. THEY MUST BE BLINDED.", + "I CAN SEE EVERYTHING. TOO MUCH. MY EYES ARE TOO OPEN, TOO WEAK.", + "I CAN SEE THE FUTURE. DREAD COMES. IT MUST BE STOPPED.", + "WE ARE SO INSIGNIFICANT. I MUST BE GREATER THAN THIS.", + "ARMS, WEAPONS, TOOLS, I NEED THEM ALL. I MUST BE PREPARED.", + "THEY ARE COMING. THEY ARE COMING FOR ME. I MUST BE READY." ] } diff --git a/tgstation.dme b/tgstation.dme index c9138785aa99..bb2ea6f5fc56 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3596,7 +3596,6 @@ #include "code\modules\antagonists\heretic\cosmic_effect.dm" #include "code\modules\antagonists\heretic\heretic_antag.dm" #include "code\modules\antagonists\heretic\heretic_curses.dm" -#include "code\modules\antagonists\heretic\heretic_focus.dm" #include "code\modules\antagonists\heretic\heretic_knowledge.dm" #include "code\modules\antagonists\heretic\heretic_living_heart.dm" #include "code\modules\antagonists\heretic\heretic_monsters.dm" @@ -3613,6 +3612,7 @@ #include "code\modules\antagonists\heretic\items\heretic_armor.dm" #include "code\modules\antagonists\heretic\items\heretic_blades.dm" #include "code\modules\antagonists\heretic\items\heretic_grenade.dm" +#include "code\modules\antagonists\heretic\items\heretic_lantern.dm" #include "code\modules\antagonists\heretic\items\heretic_necks.dm" #include "code\modules\antagonists\heretic\items\heretic_shoes.dm" #include "code\modules\antagonists\heretic\items\hunter_rifle.dm" @@ -3620,6 +3620,7 @@ #include "code\modules\antagonists\heretic\items\labyrinth_handbook.dm" #include "code\modules\antagonists\heretic\items\madness_mask.dm" #include "code\modules\antagonists\heretic\items\unfathomable_curio.dm" +#include "code\modules\antagonists\heretic\items\voidballd.dm" #include "code\modules\antagonists\heretic\knowledge\_heretic_paths.dm" #include "code\modules\antagonists\heretic\knowledge\ash_lore.dm" #include "code\modules\antagonists\heretic\knowledge\blade_lore.dm" @@ -3680,9 +3681,7 @@ #include "code\modules\antagonists\heretic\magic\star_blast.dm" #include "code\modules\antagonists\heretic\magic\star_touch.dm" #include "code\modules\antagonists\heretic\magic\void_cold_cone.dm" -#include "code\modules\antagonists\heretic\magic\void_conduit.dm" #include "code\modules\antagonists\heretic\magic\void_phase.dm" -#include "code\modules\antagonists\heretic\magic\void_prison.dm" #include "code\modules\antagonists\heretic\magic\void_pull.dm" #include "code\modules\antagonists\heretic\magic\wave_of_desperation.dm" #include "code\modules\antagonists\heretic\magic\wolves_among_sheep.dm" @@ -3696,6 +3695,7 @@ #include "code\modules\antagonists\heretic\structures\carving_knife.dm" #include "code\modules\antagonists\heretic\structures\lock_final.dm" #include "code\modules\antagonists\heretic\structures\mawed_crucible.dm" +#include "code\modules\antagonists\heretic\structures\void_conduit.dm" #include "code\modules\antagonists\highlander\highlander.dm" #include "code\modules\antagonists\hypnotized\hypnotized.dm" #include "code\modules\antagonists\magic_servant\servant.dm" diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index 8bbc44edfd1d..05218390d4f3 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -954,6 +954,11 @@ em { display: inline-block; } +.mansus { + color: #0d9b00; + text-shadow: 0 0 6px #0d9b00; +} + .cyan { color: #bde0dc; text-shadow: 0 0 6px #bde0dc; diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss index ea2b3aaabc48..38c0715d1099 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss @@ -968,6 +968,11 @@ h2.alert { display: inline-block; } +.mansus { + color: #0b8000; + text-shadow: 0 0 6px #0b8000; +} + .cyan { color: #95afac; text-shadow: 0 0 6px #95afac; diff --git a/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx b/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx index 6a9cf50972fb..8b895f1b0875 100644 --- a/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx +++ b/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx @@ -2,6 +2,7 @@ import '../styles/interfaces/AntagInfoHeretic.scss'; import { useState } from 'react'; import { + BlockQuote, Box, Button, DmIcon, @@ -64,8 +65,9 @@ type Knowledge = { depth: number; done: BooleanLike; ascension: BooleanLike; - disabled: BooleanLike; - tooltip?: string; + disabled?: BooleanLike; + notice?: string; + info?: string; }; enum ShopCategory { @@ -81,6 +83,7 @@ type KnowledgeTier = { type HereticPassive = { name: string; + recharge: string; description: string[]; }; @@ -117,7 +120,7 @@ const IntroductionSection = (props) => { return ( -

- {isCargo && ( + {isCargo && !isMove && ( <>
{ '...'} )} - {task.turf && ...at [{task.turf}]...} + {task.turf && ...{task.task_type === 'move' ? 'to' : 'at'} [{task.turf}]...} {task.time && ...for {task.time} second{task.time > 1 && "s"}...} + {task.sub_name && ...{task.sub_name}} diff --git a/tgui/packages/tgui/interfaces/BigManipulator/types.ts b/tgui/packages/tgui/interfaces/BigManipulator/types.ts index 91283d7a8891..3e7b4ab49ab0 100644 --- a/tgui/packages/tgui/interfaces/BigManipulator/types.ts +++ b/tgui/packages/tgui/interfaces/BigManipulator/types.ts @@ -11,6 +11,7 @@ export type TaskType = | 'Throw...' | 'Use...' | 'Interact with...' + | 'Move to...' | 'Wait for...'; export interface ManipulatorTask { @@ -21,7 +22,6 @@ export interface ManipulatorTask { turf?: string; item_filters?: string[]; filters_status?: BooleanLike; - filtering_mode?: number; settings_list?: PrioritySettings[]; // pickup only pickup_eagerness?: string; @@ -29,14 +29,14 @@ export interface ManipulatorTask { interaction_mode?: string; overflow_status?: string; throw_range?: number; - worker_interaction?: string; - use_post_interaction?: string; worker_use_rmb?: BooleanLike; worker_combat_mode?: BooleanLike; skip_anchored?: BooleanLike; // interact only - // (worker_interaction, use_post_interaction, worker_use_rmb, worker_combat_mode, skip_anchored shared with dropoff) + // (worker_use_rmb, worker_combat_mode, skip_anchored shared with dropoff) time?: number; + // stop only + sub_name?: string; } export interface ManipulatorData { From dcafe9a8734383385fdfe0f8106c34eda7e3c1db Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:46:47 +0000 Subject: [PATCH 346/445] Automatic changelog for PR #97102 [ci skip] --- html/changelogs/AutoChangeLog-pr-97102.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97102.yml diff --git a/html/changelogs/AutoChangeLog-pr-97102.yml b/html/changelogs/AutoChangeLog-pr-97102.yml new file mode 100644 index 000000000000..66217b1f6a53 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97102.yml @@ -0,0 +1,7 @@ +author: "mcbalaam" +delete-after: True +changes: + - bugfix: "The Big Manipulator now uses filters more correctly and spoofs the monkeys location to interact with some machines." + - bugfix: "The Big Manipulator now doesn't reset the priorities when upgraded." + - rscadd: "The Big Manipulator now knows how to Move (TM) and Stop (TM)." + - rscadd: "The Big Manipulator can now interact with turfs properly." \ No newline at end of file From d4f7673ef05c081f5955ee37d10fff7bf5c59ad6 Mon Sep 17 00:00:00 2001 From: Roxy <75404941+TealSeer@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:35:25 -0400 Subject: [PATCH 347/445] Fix biome errors (#97260) ## About The Pull Request Fix an import type and an unnecessary fragment ## Why It's Good For The Game The Lint ## Changelog N/A --- tgui/packages/tgui/interfaces/StateLawUi.tsx | 46 +++++++++----------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/tgui/packages/tgui/interfaces/StateLawUi.tsx b/tgui/packages/tgui/interfaces/StateLawUi.tsx index 73c8f1a985c0..1b87991660da 100644 --- a/tgui/packages/tgui/interfaces/StateLawUi.tsx +++ b/tgui/packages/tgui/interfaces/StateLawUi.tsx @@ -1,5 +1,5 @@ import { BlockQuote, Button, Flex, Section, Stack } from 'tgui-core/components'; -import { BooleanLike } from 'tgui-core/react'; +import type { BooleanLike } from 'tgui-core/react'; import { useBackend } from '../backend'; import { Window } from '../layouts'; @@ -53,30 +53,26 @@ export const StateLawUi = () => {
No laws available!
) : ( - <> - {all_laws.map((law, index) => ( - - - - - act('toggle_stated', { law: law.text }) - } - /> - - -
- {law.number}. {law.text} -
-
-
-
- ))} - + all_laws.map((law, index) => ( + + + + act('toggle_stated', { law: law.text })} + /> + + +
+ {law.number}. {law.text} +
+
+
+
+ )) )}
From 988896b4d2dceed934196428a7ca077ca9183766 Mon Sep 17 00:00:00 2001 From: QuiteLiterallyAnything <154708292+QuiteLiterallyAnything@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:37:47 -0500 Subject: [PATCH 348/445] Fishes checkered fix suicide (#97255) ## About The Pull Request Fixes #97249 It seems `add_bodypart_overlay()` was accidentally used instead of `add_bodypart_texture()`. Also there was an erroneous parent call. Also the parent `suicide_act()` was formatted incorrectly. Additionally, there was no message to accompany the suicide, so I've taken the liberty of adding one. ## Why It's Good For The Game This fixes an issue and makes nonfunctional code functional. ## Changelog :cl: fix: Fixed missing texture fish suicide. /:cl: --- code/modules/fishing/fish/types/holographic.dm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/code/modules/fishing/fish/types/holographic.dm b/code/modules/fishing/fish/types/holographic.dm index d79711debfd9..6398e779d32d 100644 --- a/code/modules/fishing/fish/types/holographic.dm +++ b/code/modules/fishing/fish/types/holographic.dm @@ -37,7 +37,7 @@ QDEL_IN(src, 3 SECONDS) /obj/item/fish/holo/suicide_act(mob/living/user) - visible_message(span_suicide("[user] swallows [src] whole! It looks like [user.p_theyre()] trying to derez [user.p_them()]selves!")) + visible_message(span_suicide("[user] swallows [src] whole! It looks like [user.p_theyre()] trying to derez [user.p_themselves()]!")) var/area/station/holodeck/holo_area = get_area(src) if(!istype(holo_area)) user.dust(just_ash = TRUE, drop_items = TRUE) @@ -115,14 +115,16 @@ if(!iscarbon(user)) return ..() + visible_message(span_suicide("[user] slaps the [name] to their skin, causing it to slide under and meld in! It looks like [user.p_theyre()] trying to 0xF800F8 0x000000 [user.p_themselves()]!")) for(var/obj/item/bodypart/limb in user.get_bodyparts()) - limb.add_bodypart_overlay(/datum/bodypart_texture/checkered, update = FALSE) + limb.add_bodypart_texture(/datum/bodypart_texture/checkered, update = FALSE) var/obj/item/bodypart/head/head = user.get_bodypart(BODY_ZONE_HEAD) if(!isnull(head)) head.head_flags &= ~HEAD_EYESPRITES user.update_body() - return ..() + qdel(src) + return TOXLOSS /obj/item/fish/holo/halffish name = "holographic half-fish" From 8a1f0c1f1000b69b53c09d58c52759db799f1311 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:38:18 +0000 Subject: [PATCH 349/445] Automatic changelog for PR #97255 [ci skip] --- html/changelogs/AutoChangeLog-pr-97255.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97255.yml diff --git a/html/changelogs/AutoChangeLog-pr-97255.yml b/html/changelogs/AutoChangeLog-pr-97255.yml new file mode 100644 index 000000000000..2650ab4389c0 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97255.yml @@ -0,0 +1,4 @@ +author: "QuiteLiterallyAnything" +delete-after: True +changes: + - bugfix: "Fixed missing texture fish suicide." \ No newline at end of file From ea3858dd23f82519813009e07be1f4d6de27fa72 Mon Sep 17 00:00:00 2001 From: Fghj240 <43589874+Fghj240@users.noreply.github.com> Date: Thu, 30 Jul 2026 15:51:41 -0700 Subject: [PATCH 350/445] You can see the body part coverage of anything with armor (#97222) ## About The Pull Request Examining something's protection tag shows you what parts of you it covers. image Spaceproofing no longer lists the body parts that it protects from pressure damage because those are the same as the parts that it covers. image ## Why It's Good For The Game It took me several months of playing this video game to learn that jumpskirts don't cover your legs despite the sprite visually doing so. Also it's just handy to know that kind of stuff in general. If something doesn't have armor it usually doesn't matter where it covers so it's not displayed on everything. ## Changelog :cl: qol: You can see what bodyparts a piece of clothing with armor covers by examining its tag. /:cl: Co-authored-by: Fghj240 --- code/modules/clothing/clothing.dm | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 9eea6d7830fd..5795d1295e94 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -437,21 +437,23 @@ readout += "COVERAGE" readout += "It will block [english_list(things_blocked)]." + var/list/parts_covered = list() + if(body_parts_covered & HEAD) + parts_covered += "head" + if(body_parts_covered & CHEST) + parts_covered += "torso" + if(body_parts_covered & (ARMS|HANDS)) + parts_covered += "arms" + if(body_parts_covered & (LEGS|FEET)) + parts_covered += "legs" + if(length(parts_covered)) + readout += "It covers the wearer's [english_list(parts_covered)]." + if((clothing_flags & STOPSPRESSUREDAMAGE) || (visor_flags & STOPSPRESSUREDAMAGE)) - var/list/parts_covered = list() var/output_string = "It" if(!(clothing_flags & STOPSPRESSUREDAMAGE)) output_string = "When sealed, it" - if(body_parts_covered & HEAD) - parts_covered += "head" - if(body_parts_covered & CHEST) - parts_covered += "torso" - if(body_parts_covered & (ARMS|HANDS)) - parts_covered += "arms" - if(body_parts_covered & (LEGS|FEET)) - parts_covered += "legs" - if(length(parts_covered)) - readout += "[output_string] will protect the wearer's [english_list(parts_covered)] from [span_tooltip("The extremely low pressure is the biggest danger posed by the vacuum of space.", "low pressure")]." + readout += "[output_string] will protect the wearer's bodyparts that it covers from [span_tooltip("The extremely low pressure is the biggest danger posed by the vacuum of space.", "low pressure")]." var/heat_prot switch (max_heat_protection_temperature) From 0bcfa1515959c38dc21b87cb0320864a63244cb6 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:52:02 +0000 Subject: [PATCH 351/445] Automatic changelog for PR #97222 [ci skip] --- html/changelogs/AutoChangeLog-pr-97222.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97222.yml diff --git a/html/changelogs/AutoChangeLog-pr-97222.yml b/html/changelogs/AutoChangeLog-pr-97222.yml new file mode 100644 index 000000000000..b94fdee63db9 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97222.yml @@ -0,0 +1,4 @@ +author: "Fghj240" +delete-after: True +changes: + - qol: "You can see what bodyparts a piece of clothing with armor covers by examining its tag." \ No newline at end of file From 9e020e9a855c4a7d6f375c30b493ffd3325f760c Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 00:00:22 +0000 Subject: [PATCH 352/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97102.yml | 7 ------- html/changelogs/AutoChangeLog-pr-97222.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97252.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97255.yml | 4 ---- html/changelogs/archive/2026-07.yml | 15 +++++++++++++++ 5 files changed, 15 insertions(+), 19 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97102.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97222.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97252.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97255.yml diff --git a/html/changelogs/AutoChangeLog-pr-97102.yml b/html/changelogs/AutoChangeLog-pr-97102.yml deleted file mode 100644 index 66217b1f6a53..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97102.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "mcbalaam" -delete-after: True -changes: - - bugfix: "The Big Manipulator now uses filters more correctly and spoofs the monkeys location to interact with some machines." - - bugfix: "The Big Manipulator now doesn't reset the priorities when upgraded." - - rscadd: "The Big Manipulator now knows how to Move (TM) and Stop (TM)." - - rscadd: "The Big Manipulator can now interact with turfs properly." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97222.yml b/html/changelogs/AutoChangeLog-pr-97222.yml deleted file mode 100644 index b94fdee63db9..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97222.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Fghj240" -delete-after: True -changes: - - qol: "You can see what bodyparts a piece of clothing with armor covers by examining its tag." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97252.yml b/html/changelogs/AutoChangeLog-pr-97252.yml deleted file mode 100644 index 99b6934a2638..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97252.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "san7890" -delete-after: True -changes: - - bugfix: "Jellypeople are now no longer able to cannibalize limbs that were not originally generated by them (AKA belonging to their set species)." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97255.yml b/html/changelogs/AutoChangeLog-pr-97255.yml deleted file mode 100644 index 2650ab4389c0..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97255.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "QuiteLiterallyAnything" -delete-after: True -changes: - - bugfix: "Fixed missing texture fish suicide." \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index da1d1af1aab9..9d88799a653f 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -955,3 +955,18 @@ - bugfix: You can once again scan pod people's health with advanced/debug health analyzers. - bugfix: Regal condor's magazine now properly accepts .45 instead of 10mm. +2026-07-31: + Fghj240: + - qol: You can see what bodyparts a piece of clothing with armor covers by examining + its tag. + QuiteLiterallyAnything: + - bugfix: Fixed missing texture fish suicide. + mcbalaam: + - bugfix: The Big Manipulator now uses filters more correctly and spoofs the monkeys + location to interact with some machines. + - bugfix: The Big Manipulator now doesn't reset the priorities when upgraded. + - rscadd: The Big Manipulator now knows how to Move (TM) and Stop (TM). + - rscadd: The Big Manipulator can now interact with turfs properly. + san7890: + - bugfix: Jellypeople are now no longer able to cannibalize limbs that were not + originally generated by them (AKA belonging to their set species). From 169e1a1d19dfd58138aff85357b8878a57428e73 Mon Sep 17 00:00:00 2001 From: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:36:25 -0400 Subject: [PATCH 353/445] Fixes slapcrafting + unit test (#97206) ## About The Pull Request It treated what might have been an instance like it is always a path. Unit test covers nothing but the item being created, the actual crafting unit test covers everything else ## Why It's Good For The Game fixes #97175 ## Changelog :cl: fix: All slapcrafting recipes now work again /:cl: --- code/datums/elements/slapcrafting.dm | 14 +++++++------ code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/slapcrafting.dm | 26 +++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 code/modules/unit_tests/slapcrafting.dm diff --git a/code/datums/elements/slapcrafting.dm b/code/datums/elements/slapcrafting.dm index 73db855a0262..9f4ba4218b45 100644 --- a/code/datums/elements/slapcrafting.dm +++ b/code/datums/elements/slapcrafting.dm @@ -74,7 +74,7 @@ var/list/result_to_recipe = list() - var/final_recipe = valid_recipes[1] + var/datum/crafting_recipe/final_recipe = valid_recipes[1] var/string_chosen_recipe if(length(valid_recipes) > 1) for(var/datum/crafting_recipe/recipe as anything in valid_recipes) @@ -92,15 +92,17 @@ if(string_chosen_recipe) final_recipe = result_to_recipe[string_chosen_recipe] - var/datum/crafting_recipe/actual_recipe = GLOB.cooking_recipes_by_typepath[final_recipe] || GLOB.crafting_recipes_by_typepath[final_recipe] - if(!actual_recipe) - CRASH("Recipe not located in cooking or crafting recipes: [final_recipe]") + if(ispath(final_recipe)) + var/recipe_path = final_recipe + final_recipe = GLOB.cooking_recipes_by_typepath[recipe_path] || GLOB.crafting_recipes_by_typepath[recipe_path] + if(!final_recipe) + CRASH("Recipe not located in cooking or crafting recipes: [recipe_path]") - var/atom/final_result = initial(actual_recipe.result) + var/atom/final_result = initial(final_recipe.result) to_chat(user, span_notice("You start crafting \a [initial(final_result.name)]...")) - var/error_string = craft_sheet.construct_item(user, actual_recipe) + var/error_string = craft_sheet.construct_item(user, final_recipe) if(istext(error_string)) to_chat(user, span_warning("Crafting failed[error_string]")) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 0475aa2fc906..2f7ffdb585f7 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -317,6 +317,7 @@ #include "serving_tray.dm" #include "simple_animal_freeze.dm" #include "siunit.dm" +#include "slapcrafting.dm" #include "slime_mood.dm" #include "slips.dm" #include "spawn_humans.dm" diff --git a/code/modules/unit_tests/slapcrafting.dm b/code/modules/unit_tests/slapcrafting.dm new file mode 100644 index 000000000000..76e777ccd441 --- /dev/null +++ b/code/modules/unit_tests/slapcrafting.dm @@ -0,0 +1,26 @@ +/datum/unit_test/slapcrafting + +/datum/unit_test/slapcrafting/Run() + var/mob/living/carbon/human/consistent/expert = EASY_ALLOCATE() + var/obj/item/paper/disguise = EASY_ALLOCATE() + var/obj/item/toy/mecha/odysseus/cool_robot = EASY_ALLOCATE() + + var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/slapcraft_tester) + cool_robot.AddElement(/datum/element/slapcrafting, slapcraft_recipe_list) + + expert.put_in_active_hand(disguise) + click_wrapper(expert, cool_robot) + var/obj/item/toy/mecha/reticence/cooler_robot = locate() in run_loc_floor_bottom_left + TEST_ASSERT_NOTNULL(cooler_robot, "Slapcrafting did not successfully produce an item") + qdel(cooler_robot) + +/datum/crafting_recipe/slapcraft_tester + name = "Cooler Robot" + result = /obj/item/toy/mecha/reticence + reqs = list( + /obj/item/paper = 1, + /obj/item/toy/mecha/odysseus = 1, + ) + time = 0 + category = CAT_ENTERTAINMENT + crafting_flags = CRAFT_SKIP_MATERIALS_PARITY From ad0234600896354aeeaa7bfd78c0d7d720aa536d Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 00:36:44 +0000 Subject: [PATCH 354/445] Automatic changelog for PR #97206 [ci skip] --- html/changelogs/AutoChangeLog-pr-97206.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97206.yml diff --git a/html/changelogs/AutoChangeLog-pr-97206.yml b/html/changelogs/AutoChangeLog-pr-97206.yml new file mode 100644 index 000000000000..b07bdd6f4f66 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97206.yml @@ -0,0 +1,4 @@ +author: "lelandkemble" +delete-after: True +changes: + - bugfix: "All slapcrafting recipes now work again" \ No newline at end of file From 9132e4231a739916c2681a908ff6afa8a39f4504 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:33:23 -0500 Subject: [PATCH 355/445] Stasis bodybags don't decay in freezing temperatures (#97262) ## About The Pull Request Statis bag decay only occurs above 0c. ## Why It's Good For The Game Just thought it'd be quaint to be able to preserve them in morgue slabs ## Changelog :cl: Melbert qol: Stasis bodybags don't decay in freezing temperatures, such as those present in morgue slabs /:cl: --- code/game/atom/_atom.dm | 7 ++----- .../objects/structures/crates_lockers/closets/bodybag.dm | 5 +++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index 5f703f9e6b63..b7b17bdbd11a 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -397,11 +397,8 @@ return null ///Return the current air environment in this atom -/atom/proc/return_air() - if(loc) - return loc.return_air() - else - return null +/atom/proc/return_air() as /datum/gas_mixture + return loc?.return_air() ///Return the air if we can analyze it /atom/proc/return_analyzable_air() diff --git a/code/game/objects/structures/crates_lockers/closets/bodybag.dm b/code/game/objects/structures/crates_lockers/closets/bodybag.dm index 0e11c8bb9e48..4c7d83059311 100644 --- a/code/game/objects/structures/crates_lockers/closets/bodybag.dm +++ b/code/game/objects/structures/crates_lockers/closets/bodybag.dm @@ -573,8 +573,9 @@ if(SPT_PROB(2 * (seconds_freezing / 60), seconds_per_tick)) freezing.Unconscious(1 SECONDS) - // Bout two minutes of time - take_damage(max_integrity * 0.004 * seconds_per_tick, sound_effect = FALSE) + if(loc?.return_air()?.return_temperature() > T0C) + // Bout two minutes of time + take_damage(max_integrity * 0.004 * seconds_per_tick, sound_effect = FALSE) /obj/structure/closet/body_bag/environmental/stasis/after_open(mob/living/user, force = FALSE) . = ..() From 619ff0ddd1a9ab79b145c0161af076547ef67160 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 01:33:42 +0000 Subject: [PATCH 356/445] Automatic changelog for PR #97262 [ci skip] --- html/changelogs/AutoChangeLog-pr-97262.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97262.yml diff --git a/html/changelogs/AutoChangeLog-pr-97262.yml b/html/changelogs/AutoChangeLog-pr-97262.yml new file mode 100644 index 000000000000..5827a5da78c1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97262.yml @@ -0,0 +1,4 @@ +author: "Melbert" +delete-after: True +changes: + - qol: "Stasis bodybags don't decay in freezing temperatures, such as those present in morgue slabs" \ No newline at end of file From f0a31b3f935d96e5fc63ac8dc847778931f9898b Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:34:57 -0500 Subject: [PATCH 357/445] Surplus crate items shows up in roundend report (again?) (#97197) ## About The Pull Request Buying a surplus crate shows the items you got in it in the roundend report image (Hovering over them says `Surplus item: [xyz]`) ## Why It's Good For The Game I could've sworn it used to do this but I don't remember It's nice to know where all the guy's loot came from though ## Changelog :cl: Melbert qol: Surplus crate items show up in roundend report /:cl: --- code/modules/uplink/uplink_items.dm | 8 +++-- code/modules/uplink/uplink_items/bundle.dm | 31 +++++++++++++--- code/modules/uplink/uplink_purchase_log.dm | 41 ++++++++++++++-------- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm index f653faff9f00..73561cd0f407 100644 --- a/code/modules/uplink/uplink_items.dm +++ b/code/modules/uplink/uplink_items.dm @@ -151,10 +151,14 @@ var/atom/spawned_item = spawn_item(item, user, uplink_handler, source) log_uplink("[key_name(user)] purchased [src] for [cost] telecrystals from [source]'s uplink") user.playsound_local(get_turf(user), 'sound/effects/kaching.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) - if(purchase_log_vis && uplink_handler.purchase_log) - uplink_handler.purchase_log.LogPurchase(spawned_item, src, cost) + if(uplink_handler.purchase_log) + log_purchase(spawned_item, uplink_handler) if(lock_other_purchases) uplink_handler.shop_locked = TRUE + return spawned_item + +/datum/uplink_item/proc/log_purchase(atom/spawned_item, datum/uplink_handler/uplink_handler) + uplink_handler.purchase_log.log_purchase(spawned_item, src, cost) /// Spawns an item in the world /datum/uplink_item/proc/spawn_item(spawn_path, mob/user, datum/uplink_handler/uplink_handler, atom/movable/source) diff --git a/code/modules/uplink/uplink_items/bundle.dm b/code/modules/uplink/uplink_items/bundle.dm index decaf1ba271c..e2feec18ce3a 100644 --- a/code/modules/uplink/uplink_items/bundle.dm +++ b/code/modules/uplink/uplink_items/bundle.dm @@ -37,11 +37,13 @@ desc = "A telecrystal in its rawest and purest form; can be utilized on active uplinks to increase their telecrystal count." item = /obj/item/stack/telecrystal cost = 1 - // Don't add telecrystals to the purchase_log since - // it's just used to buy more items (including itself!) - purchase_log_vis = FALSE purchasable_from = NONE +// Don't add telecrystals to the purchase_log since +// it's just used to buy more items (including itself!) +/datum/uplink_item/bundles_tc/telecrystal/log_purchase(atom/spawned_item, datum/uplink_handler/uplink_handler) + return + /datum/uplink_item/bundles_tc/bundle_a name = "Syndi-kit Tactical" desc = "Syndicate Bundles, also known as Syndi-Kits, are specialized groups of items that arrive in a plain box. \ @@ -132,7 +134,28 @@ "style" = /datum/pod_style/syndicate, "spawn" = surplus_crate, )) - return source //For log icon + return surplus_crate //For log icon + +/datum/uplink_item/bundles_tc/surplus/log_purchase(atom/spawned_item, datum/uplink_handler/uplink_handler) + . = ..() + var/list/entries = list() + for(var/obj/item/loot in spawned_item) + if(entries[loot.type]) + var/datum/uplink_purchase_entry/existing_entry = entries[loot.type] + existing_entry.amount_purchased += 1 + continue + + var/datum/uplink_purchase_entry/custom_entry = new() + custom_entry.set_item(loot) + custom_entry.base_cost = 0 + custom_entry.spent_cost = 0 + custom_entry.amount_purchased = 1 + custom_entry.name = "Surplus Item: [format_text(loot.name)]" + custom_entry.desc = "Obtained via surplus crate." + entries[loot.type] = custom_entry + + for(var/entry_type, entry_datum in entries) + uplink_handler.purchase_log.log_purchase_custom(entry_datum) /datum/uplink_item/bundles_tc/surplus/united name = "United Surplus Crate" diff --git a/code/modules/uplink/uplink_purchase_log.dm b/code/modules/uplink/uplink_purchase_log.dm index c9ac13fa13fd..b3041e2aec6f 100644 --- a/code/modules/uplink/uplink_purchase_log.dm +++ b/code/modules/uplink/uplink_purchase_log.dm @@ -39,30 +39,37 @@ GLOBAL_LIST(uplink_purchase_logs_by_key) //assoc key = /datum/uplink_purchase_lo /datum/uplink_purchase_log/proc/TotalTelecrystalsSpent() . = total_spent +#define FORMAT_COST(uplink_entry) (uplink_entry.spent_cost ? "[uplink_entry.spent_cost] TC" : (uplink_entry.base_cost ? "Free" : "Surplus")) + /datum/uplink_purchase_log/proc/generate_render(show_key = TRUE) . = "" for(var/hash in purchase_log) var/datum/uplink_purchase_entry/UPE = purchase_log[hash] - . += "\[[UPE.icon_b64][show_key?"([owner])":""][UPE.name]
[UPE.spent_cost ? "[UPE.spent_cost] TC" : "[UPE.base_cost] TC
(Surplus)"]
[UPE.desc]
[(UPE.amount_purchased > 1) ? "x[UPE.amount_purchased]" : ""]\]
" + . += "\[[UPE.icon_b64][show_key ? "([owner])":""][UPE.name]
[FORMAT_COST(UPE)]
[UPE.desc]
[(UPE.amount_purchased > 1) ? "x[UPE.amount_purchased]" : ""]\]
" + +#undef FORMAT_COST -/datum/uplink_purchase_log/proc/LogPurchase(atom/A, datum/uplink_item/uplink_item, spent_cost) - var/datum/uplink_purchase_entry/UPE +/// Logs that an uplink item was purchased +/datum/uplink_purchase_log/proc/log_purchase(atom/spawned_item, datum/uplink_item/uplink_item, spent_cost) var/hash = hash_purchase(uplink_item, spent_cost) - if(purchase_log[hash]) - UPE = purchase_log[hash] - else - UPE = new - purchase_log[hash] = UPE - UPE.path = A.type - UPE.icon_b64 = "[icon2base64html(A)]" - UPE.desc = uplink_item.desc - UPE.name = uplink_item.name - UPE.base_cost = initial(uplink_item.cost) - UPE.spent_cost = spent_cost + var/datum/uplink_purchase_entry/purchase_entry = purchase_log[hash] + if(isnull(purchase_entry)) + purchase_entry = new() + purchase_entry.set_item(spawned_item) + purchase_entry.name = uplink_item.name + purchase_entry.desc = uplink_item.desc + purchase_entry.base_cost = initial(uplink_item.cost) + purchase_entry.spent_cost = spent_cost + purchase_log[hash] = purchase_entry - UPE.amount_purchased++ + purchase_entry.amount_purchased += 1 total_spent += spent_cost +/// Used to add custom items into the purchase log +/datum/uplink_purchase_log/proc/log_purchase_custom(datum/uplink_purchase_entry/custom_entry) + purchase_log[REF(custom_entry)] = custom_entry + total_spent += custom_entry.spent_cost + /datum/uplink_purchase_log/proc/hash_purchase(datum/uplink_item/uplink_item, spent_cost) return "[uplink_item.type]|[uplink_item.name]|[uplink_item.cost]|[spent_cost]" @@ -74,3 +81,7 @@ GLOBAL_LIST(uplink_purchase_logs_by_key) //assoc key = /datum/uplink_purchase_lo var/base_cost var/spent_cost var/name + +/datum/uplink_purchase_entry/proc/set_item(obj/item/to_set) + path = to_set.type + icon_b64 = "[icon2base64html(to_set)]" From 403ad9a9bcb159340938f277ee77023a2d0c89ba Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 01:35:24 +0000 Subject: [PATCH 358/445] Automatic changelog for PR #97197 [ci skip] --- html/changelogs/AutoChangeLog-pr-97197.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97197.yml diff --git a/html/changelogs/AutoChangeLog-pr-97197.yml b/html/changelogs/AutoChangeLog-pr-97197.yml new file mode 100644 index 000000000000..07bb5ac602d1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97197.yml @@ -0,0 +1,4 @@ +author: "Melbert" +delete-after: True +changes: + - qol: "Surplus crate items show up in roundend report" \ No newline at end of file From b7278bc489d81c374c601c7fe8a7f12f2459a48a Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 02:36:39 +0000 Subject: [PATCH 359/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-1228.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1241.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1245.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1253.yml | 5 ----- html/changelogs/AutoChangeLog-pr-1263.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1281.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1289.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1295.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1296.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1299.yml | 4 ---- html/changelogs/archive/2026-07.yml | 21 +++++++++++++++++++++ 11 files changed, 21 insertions(+), 41 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-1228.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1241.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1245.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1253.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1263.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1281.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1289.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1295.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1296.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1299.yml diff --git a/html/changelogs/AutoChangeLog-pr-1228.yml b/html/changelogs/AutoChangeLog-pr-1228.yml deleted file mode 100644 index 0575c808c405..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1228.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "chazzyjazzy" -delete-after: True -changes: - - rscadd: "expands lore primers to help new players figure out what morality paths are as well as some stuff about Garou, Clans, etc" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1241.yml b/html/changelogs/AutoChangeLog-pr-1241.yml deleted file mode 100644 index 39c166197404..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1241.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "chazzyjazzy" -delete-after: True -changes: - - rscadd: "Adds the red news reporter role which can broadcast to the city using the captain-caster camera and mapped-in greenscreens as well as 'entertainment monitors' and the mobile cameras" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1245.yml b/html/changelogs/AutoChangeLog-pr-1245.yml deleted file mode 100644 index 3583f09a5ec3..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1245.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - admin: "2 VV topics for managing actions or gifts" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1253.yml b/html/changelogs/AutoChangeLog-pr-1253.yml deleted file mode 100644 index ddd69966935f..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1253.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Faengi" -delete-after: True -changes: - - rscadd: "Gave the Society of Leopold intel reports to find breachers with" - - rscadd: "Added more breach phrases to the list" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1263.yml b/html/changelogs/AutoChangeLog-pr-1263.yml deleted file mode 100644 index eef027120ec4..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1263.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Faengi" -delete-after: True -changes: - - config: "Added a config setting to limit high generations to a limited selection of jobs" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1281.yml b/html/changelogs/AutoChangeLog-pr-1281.yml deleted file mode 100644 index f066a9cc985f..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1281.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Absolucy" -delete-after: True -changes: - - bugfix: "made smartphone contacts lag the server less" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1289.yml b/html/changelogs/AutoChangeLog-pr-1289.yml deleted file mode 100644 index 96270ee30473..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1289.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "buffyuwu" -delete-after: True -changes: - - rscadd: "Admins can now set generations below 8" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1295.yml b/html/changelogs/AutoChangeLog-pr-1295.yml deleted file mode 100644 index c926fe3dd6fb..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1295.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "chazzyjazzy" -delete-after: True -changes: - - bugfix: "fixes firearms recoil roll difficulty never being reset" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1296.yml b/html/changelogs/AutoChangeLog-pr-1296.yml deleted file mode 100644 index 8922710bfb9f..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1296.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - balance: "You can guestbook from a full screen width" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1299.yml b/html/changelogs/AutoChangeLog-pr-1299.yml deleted file mode 100644 index fe096a2d5772..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1299.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - bugfix: "Delirium will properly be applied" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index 61c132715478..c3842bdfb7e4 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -191,3 +191,24 @@ - rscadd: adds functionality for clearing masquerade breaches upon the death of any mob/living, admins can also use the 'AdjustMasquerade' proc to restore people's breaches now as it wasnt working before +2026-07-31: + Absolucy: + - bugfix: made smartphone contacts lag the server less + Faengi: + - config: Added a config setting to limit high generations to a limited selection + of jobs + - rscadd: Gave the Society of Leopold intel reports to find breachers with + - rscadd: Added more breach phrases to the list + FalloutFalcon: + - balance: You can guestbook from a full screen width + - bugfix: Delirium will properly be applied + - admin: 2 VV topics for managing actions or gifts + buffyuwu: + - rscadd: Admins can now set generations below 8 + chazzyjazzy: + - rscadd: Adds the red news reporter role which can broadcast to the city using + the captain-caster camera and mapped-in greenscreens as well as 'entertainment + monitors' and the mobile cameras + - rscadd: expands lore primers to help new players figure out what morality paths + are as well as some stuff about Garou, Clans, etc + - bugfix: fixes firearms recoil roll difficulty never being reset From 5f093a8cfbbe269b15bc73535f230909218e38d6 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 06:00:28 +0000 Subject: [PATCH 360/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97197.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97206.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97262.yml | 4 ---- html/changelogs/archive/2026-07.yml | 6 ++++++ 4 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97197.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97206.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97262.yml diff --git a/html/changelogs/AutoChangeLog-pr-97197.yml b/html/changelogs/AutoChangeLog-pr-97197.yml deleted file mode 100644 index 07bb5ac602d1..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97197.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - qol: "Surplus crate items show up in roundend report" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97206.yml b/html/changelogs/AutoChangeLog-pr-97206.yml deleted file mode 100644 index b07bdd6f4f66..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97206.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "lelandkemble" -delete-after: True -changes: - - bugfix: "All slapcrafting recipes now work again" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97262.yml b/html/changelogs/AutoChangeLog-pr-97262.yml deleted file mode 100644 index 5827a5da78c1..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97262.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - qol: "Stasis bodybags don't decay in freezing temperatures, such as those present in morgue slabs" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index 9d88799a653f..b0b15a16c53c 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -959,8 +959,14 @@ Fghj240: - qol: You can see what bodyparts a piece of clothing with armor covers by examining its tag. + Melbert: + - qol: Surplus crate items show up in roundend report + - qol: Stasis bodybags don't decay in freezing temperatures, such as those present + in morgue slabs QuiteLiterallyAnything: - bugfix: Fixed missing texture fish suicide. + lelandkemble: + - bugfix: All slapcrafting recipes now work again mcbalaam: - bugfix: The Big Manipulator now uses filters more correctly and spoofs the monkeys location to interact with some machines. From 59e9084e5d4ed39e69e3704cab7a1abbe9493e99 Mon Sep 17 00:00:00 2001 From: Faengi Date: Fri, 31 Jul 2026 10:25:44 -0400 Subject: [PATCH 361/445] Uncontrollable trait (#1286) ## About The Pull Request adds uncontrollable from v3 brujahbook, sets frenzy difficulty to 10 ## Why It's Good For The Game more book traits ! ## Changelog :cl: add: Added the Uncontrollable flaw for Brujah /:cl: --- code/__DEFINES/~darkpack/traits/declarations.dm | 1 + code/_globalvars/traits/_traits.dm | 1 + code/_globalvars/traits/admin_tooling.dm | 1 + modular_darkpack/modules/frenzy/code/frenzy.dm | 2 ++ .../code/negative_quirks/uncontrollable.dm | 15 +++++++++++++++ tgstation.dme | 1 + 6 files changed, 21 insertions(+) create mode 100644 modular_darkpack/modules/merits_flaws/code/negative_quirks/uncontrollable.dm diff --git a/code/__DEFINES/~darkpack/traits/declarations.dm b/code/__DEFINES/~darkpack/traits/declarations.dm index d91f50270980..00643bc96b5e 100644 --- a/code/__DEFINES/~darkpack/traits/declarations.dm +++ b/code/__DEFINES/~darkpack/traits/declarations.dm @@ -198,5 +198,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_GRAVE_SMELL "grave_smell" #define TRAIT_GLOWING_EYES "glowing_eyes" #define TRAIT_PROMETHEAN_CLAY "promethean_clay" +#define TRAIT_UNCONTROLLABLE "uncontrollable" // END TRAIT DEFINES diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index bee13c2114d2..6bdc61f5a5ff 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -778,6 +778,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_TRANSFORM_UPDATES_ICON" = TRAIT_TRANSFORM_UPDATES_ICON, // DARKPACK EDIT ADD - WEREWOLF "TRAIT_UNAGING" = TRAIT_UNAGING, // DARKPACK EDIT ADD "TRAIT_UNBONDABLE" = TRAIT_UNBONDABLE, // DARKPACK EDIT ADD + "TRAIT_UNCONTROLLABLE" = TRAIT_UNCONTROLLABLE, //DARKPACK EDIT ADD - MERITS_FLAWS "TRAIT_UNMASQUERADE" = TRAIT_UNMASQUERADE, // DARKPACK EDIT ADD "TRAIT_VAMPIRE_TERRITORIAL" = TRAIT_VAMPIRE_TERRITORIAL, // DARKPACK EDIT ADD - MERITS_FLAWS "TRAIT_VICTIM_OF_THE_MASQUERADE" = TRAIT_VICTIM_OF_THE_MASQUERADE, // DARKPACK EDIT ADD - MERITS_FLAWS diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 8803705df590..da65cfa50859 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -447,6 +447,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_TRANSFORM_UPDATES_ICON" = TRAIT_TRANSFORM_UPDATES_ICON, // DARKPACK EDIT ADD - WEREWOLF "TRAIT_UNAGING" = TRAIT_UNAGING, // DARKPACK EDIT ADD "TRAIT_UNBONDABLE" = TRAIT_UNBONDABLE, // DARKPACK EDIT ADD + "TRAIT_UNCONTROLLABLE" = TRAIT_UNCONTROLLABLE, //DARKPACK EDIT ADD - MERITS_FLAWS "TRAIT_UNMASQUERADE" = TRAIT_UNMASQUERADE, // DARKPACK EDIT ADD "TRAIT_VIOLATOR" = TRAIT_VIOLATOR, // DARKPACK EDIT ADD "TRAIT_VITAE_ADDICTION" = TRAIT_VITAE_ADDICTION, // DARKPACK EDIT ADD diff --git a/modular_darkpack/modules/frenzy/code/frenzy.dm b/modular_darkpack/modules/frenzy/code/frenzy.dm index 5f3e38e8d905..d2b127e4827c 100644 --- a/modular_darkpack/modules/frenzy/code/frenzy.dm +++ b/modular_darkpack/modules/frenzy/code/frenzy.dm @@ -48,6 +48,8 @@ // V20 p.51 if(HAS_TRAIT(roller, TRAIT_DIFFICULT_FRENZY)) . += 2 + if(HAS_TRAIT(roller, TRAIT_UNCONTROLLABLE)) + . = 10 /datum/storyteller_roll/frenzy/kindred/calculate_used_dice(mob/living/roller, bonus) . = ..() diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/uncontrollable.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/uncontrollable.dm new file mode 100644 index 000000000000..7ddc835807e2 --- /dev/null +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/uncontrollable.dm @@ -0,0 +1,15 @@ +//Clanbook: Brujah Revised (69) +/datum/quirk/darkpack/uncontrollable + name = "Uncontrollable" + desc = {"Rage and passion constantly war in the soul of a + volatile Brujah. Perhaps you were ill tempered before + the Embrace, or perhaps your Brujah lineage awakened + some latent fury. In any case, even more so than your + clanmates, you are prone to frenzy. Difficulties to + resist frenzy are always 10 for this character. Prepare + for a short, hellish ride."} + value = -5 + mob_trait = TRAIT_UNCONTROLLABLE + icon = FA_ICON_HEART_CRACK + allowed_splats = list(SPLAT_KINDRED) + included_clans = list(VAMPIRE_CLAN_BRUJAH) diff --git a/tgstation.dme b/tgstation.dme index 0c2b16690428..cbdf7ae388bd 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -7613,6 +7613,7 @@ #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\thaumaturgically_inept.dm" #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\the_largest_maw.dm" #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\third_eye.dm" +#include "modular_darkpack\modules\merits_flaws\code\negative_quirks\uncontrollable.dm" #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\vengeful.dm" #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\victim_of_the_masquerade.dm" #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\weak_willed.dm" From db7c807b1c599ebe1844ea52262adcd8f8c15258 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:26:15 +0000 Subject: [PATCH 362/445] Automatic changelog for PR #1286 [ci skip] --- html/changelogs/AutoChangeLog-pr-1286.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1286.yml diff --git a/html/changelogs/AutoChangeLog-pr-1286.yml b/html/changelogs/AutoChangeLog-pr-1286.yml new file mode 100644 index 000000000000..1f73ae0632d9 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1286.yml @@ -0,0 +1,4 @@ +author: "Faengi" +delete-after: True +changes: + - rscadd: "Added the Uncontrollable flaw for Brujah" \ No newline at end of file From 27c23c8fc52bb7e0e4968926ef65edb884eb9b72 Mon Sep 17 00:00:00 2001 From: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Date: Fri, 31 Jul 2026 09:26:40 -0500 Subject: [PATCH 363/445] Source book datums (#1100) ## About The Pull Request Cool datum we can use to parse out some behavoir based on what book types come from (including displaying such to the end-user so they can look it up themselves) ## Why It's Good For The Game We already like to mark where stuff comes from with comments, why not codify it and let it be used for behavior. ## Changelog :cl: code: new var on datum level for storing info on what source book it comes from config: Adds config for limiting merit based on official status /:cl: --- code/__DEFINES/~darkpack/book_sources.dm | 9 +++ .../subsystem/processing/quirks.dm | 10 ++- config/darkpack_config.txt | 3 + .../master_files/code/datums/datum.dm | 4 ++ .../book_sources/code/source_book_datum.dm | 63 +++++++++++++++++++ .../modules/merits_flaws/code/config.dm | 4 ++ .../code/negative_quirks/animal_musk.dm | 1 + .../negative_quirks/cast_no_reflection.dm | 2 +- .../code/negative_quirks/dulled_bite.dm | 2 +- .../code/negative_quirks/one_armed.dm | 1 + .../code/negative_quirks/permanent_fangs.dm | 1 + .../code/negative_quirks/pierced_veil.dm | 2 +- .../code/negative_quirks/prey_exclusion.dm | 1 + .../code/negative_quirks/speech_impediment.dm | 2 +- .../code/negative_quirks/territorial.dm | 1 + .../code/negative_quirks/vengeful.dm | 1 + .../victim_of_the_masquerade.dm | 1 + .../code/negative_quirks/weak_willed.dm | 5 +- .../code/neutral_quirks/illegal_identity.dm | 1 + .../code/positive_quirks/beserker.dm | 2 +- .../positive_quirks/blush_of_health_quirk.dm | 1 + .../code/positive_quirks/calm_heart.dm | 1 + .../code/positive_quirks/deceptive_aura.dm | 2 +- .../code/positive_quirks/eat_food_quirk.dm | 1 + .../positive_quirks/efficient_digestion.dm | 1 + .../code/positive_quirks/fair_glabro.dm | 2 +- .../code/positive_quirks/hidden_diablerie.dm | 2 +- .../code/positive_quirks/metamorph.dm | 2 +- .../code/positive_quirks/pale_aura.dm | 5 +- .../code/positive_quirks/time_sense.dm | 2 +- .../code/positive_quirks/touch_of_the_wyld.dm | 1 + .../code/positive_quirks/unbondable.dm | 2 +- .../code/positive_quirks/untamable.dm | 1 + .../code/positive_quirks/wolf_sight.dm | 1 + tgstation.dme | 3 + 35 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 code/__DEFINES/~darkpack/book_sources.dm create mode 100644 modular_darkpack/master_files/code/datums/datum.dm create mode 100644 modular_darkpack/modules/book_sources/code/source_book_datum.dm diff --git a/code/__DEFINES/~darkpack/book_sources.dm b/code/__DEFINES/~darkpack/book_sources.dm new file mode 100644 index 000000000000..0f6b4c9eb087 --- /dev/null +++ b/code/__DEFINES/~darkpack/book_sources.dm @@ -0,0 +1,9 @@ +// BOOK_SOURCES +/// Content is based on offical white wolf media +#define SOURCE_OFFICAL "Offical" +/// Contents based on media published through white wolf's storytellers vault which makes it akin to secondary canon +#define SOURCE_STORYTELLER_VAULT "Storyteller Vault" +/// Content is based on entirely homebrew (wether made up for ss13 or otherwise) +#define SOURCE_HOMEBREW "Homebrew" + +#define WE_MADE_IT_UP "Darkpack 13" diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm index 99623edb6288..5d97b05e5995 100644 --- a/code/controllers/subsystem/processing/quirks.dm +++ b/code/controllers/subsystem/processing/quirks.dm @@ -92,14 +92,18 @@ PROCESSING_SUBSYSTEM_DEF(quirks) for(var/type in quirk_list) var/datum/quirk/quirk_type = type - // DARKPACK EDIT ADD START - MERITS_FLAWS + // DARKPACK EDIT CHANGE START - MERITS_FLAWS if(!quirk_type::darkpack_allowed) continue if(quirk_type::roleplay_only && !CONFIG_GET(flag/roleplay_only_merits)) continue - // DARKPACK EDIT ADD END - quirk_prototypes[type] = new type + var/datum/quirk/quirk_datum = new type + if(!quirk_datum.soure_book_allowed(CONFIG_GET(string/ttrpg_accurate_cuttoff_merits))) + continue + + quirk_prototypes[type] = quirk_datum + // DARKPACK EDIT CHANGE END quirks[initial(quirk_type.name)] = quirk_type quirk_points[initial(quirk_type.name)] = initial(quirk_type.value) diff --git a/config/darkpack_config.txt b/config/darkpack_config.txt index 268ec9051026..d7b4aee82cda 100644 --- a/config/darkpack_config.txt +++ b/config/darkpack_config.txt @@ -70,6 +70,9 @@ LOG_STATS ## Bool for if roleplay only merits/quirks are enabled ROLEPLAY_ONLY_MERITS 1 +## String for how ttrpg accurate our merits must be. Offical, Storyteller Vault, or Homebrew +TTRPG_ACCURATE_CUTOFF_MERITS Homebrew + ## Configs for the type of attack we use when we click at a ranged distance. Only one can be enabled at a time. #DIRECTIONAL_COMBAT SWING_COMBAT diff --git a/modular_darkpack/master_files/code/datums/datum.dm b/modular_darkpack/master_files/code/datums/datum.dm new file mode 100644 index 000000000000..6c5d0246d7aa --- /dev/null +++ b/modular_darkpack/master_files/code/datums/datum.dm @@ -0,0 +1,4 @@ + +/datum + /// Lazy list of books/sources of where the type is bassd off of. Page Number indexed by datum. Ex. + var/list/datum/source_book/ttrpg_sources // BOOK_SOURCES diff --git a/modular_darkpack/modules/book_sources/code/source_book_datum.dm b/modular_darkpack/modules/book_sources/code/source_book_datum.dm new file mode 100644 index 000000000000..c280d8432b5e --- /dev/null +++ b/modular_darkpack/modules/book_sources/code/source_book_datum.dm @@ -0,0 +1,63 @@ +GLOBAL_LIST_INIT(source_book_priority, list( + SOURCE_OFFICAL = 1, + SOURCE_STORYTELLER_VAULT = 2, + SOURCE_HOMEBREW = 3, +)) + +/datum/source_book + var/name + /// Determiner for how accurate it is to whitewolf published media. + var/offical_status + /// Media does not have or care about page numbers + var/ignore_pages = FALSE + +/datum/source_book/vtm20 + name = "Vampire The Masquerade 20th Anniversary Edition" + offical_status = SOURCE_OFFICAL + +/datum/source_book/hunterhunted2 + name = "The Hunters Hunted II" + offical_status = SOURCE_OFFICAL + +/datum/source_book/ghouls_and_revenants + name = "Ghouls & Revenants" + offical_status = SOURCE_OFFICAL + +/datum/source_book/wta20 + name = "Werewolf The Apocalypse 20th Anniversary Edition" + offical_status = SOURCE_OFFICAL + +/datum/source_book/vampire_wild_west + name = "Vampire The Wild West" + offical_status = SOURCE_STORYTELLER_VAULT + +/datum/source_book/homebrew + name = "Homebrew" + offical_status = SOURCE_HOMEBREW + +/datum/proc/highest_source_offical_status() + var/highest_book + for(var/datum/source_book/book, page_number in ttrpg_sources) + if(!highest_book || GLOB.source_book_priority[book] < highest_book) + highest_book = GLOB.source_book_priority[book] + + return highest_book + +/datum/proc/soure_book_allowed(source_cutoff) + var/main_source = highest_source_offical_status() + if(!main_source) + return TRUE + + if(GLOB.source_book_priority[main_source] > GLOB.source_book_priority[source_cutoff]) + return FALSE + + return TRUE + + +/datum/proc/get_book_sources_readable() + var/books = list() + for(var/datum/source_book/book, page_number in ttrpg_sources) + if(isnum(page_number)) + books += "[book::name]: p. [page_number]" + else + books += "[book::name]: [page_number]" diff --git a/modular_darkpack/modules/merits_flaws/code/config.dm b/modular_darkpack/modules/merits_flaws/code/config.dm index a65fdfca9719..e834505cd85d 100644 --- a/modular_darkpack/modules/merits_flaws/code/config.dm +++ b/modular_darkpack/modules/merits_flaws/code/config.dm @@ -1,3 +1,7 @@ /datum/config_entry/flag/roleplay_only_merits default = TRUE protection = CONFIG_ENTRY_LOCKED // Config does nothing after initlize. dont trick people into thinking they can edit + +/datum/config_entry/string/ttrpg_accurate_cuttoff_merits + default = SOURCE_HOMEBREW + protection = CONFIG_ENTRY_LOCKED // Config does nothing after initlize. dont trick people into thinking they can edit diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/animal_musk.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/animal_musk.dm index e74c921fbec8..ebb33239ad35 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/animal_musk.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/animal_musk.dm @@ -9,6 +9,7 @@ Outdoors or in situations where you can distance yourself from humans, your odor is not noticeable. Wolves (and lupus-born Garou) take little notice of this Flaw.."} */ + ttrpg_sources = list(/datum/source_book/wta20 = 473) value = -1 mob_trait = TRAIT_ANIMAL_MUSK icon = FA_ICON_SPRAY_CAN_SPARKLES // icon = FA_ICON_BUGS diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/cast_no_reflection.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/cast_no_reflection.dm index 6400eec41ded..48d95bebdab5 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/cast_no_reflection.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/cast_no_reflection.dm @@ -1,7 +1,7 @@ -//Vampire The Masquerade 20th Anniversary Edition, 494 /datum/quirk/darkpack/cast_no_reflection name = "Cast No Reflection" desc = "You actually cast no reflection, just like the vampires of legend. This can have a detrimental effect when trying to pass as a human." + ttrpg_sources = list(/datum/source_book/vtm20 = 494) value = -1 mob_trait = TRAIT_NO_MIRROR_REFLECTION icon = FA_ICON_PERSON_THROUGH_WINDOW diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/dulled_bite.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/dulled_bite.dm index 2f6c0afa4048..a98c21e8fe84 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/dulled_bite.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/dulled_bite.dm @@ -1,7 +1,7 @@ -// V20 p. 481 /datum/quirk/darkpack/dulled_bite name = "Dulled Bite" desc = "For some reason your fangs never developed fully, or they may not have manifested at all. When feeding, you need to find some other method of making the blood flow. A number of Caitiff and high Generation vampires often manifest this Flaw." + ttrpg_sources = list(/datum/source_book/vtm20 = 481) value = -2 mob_trait = TRAIT_DULLFANGS gain_text = span_notice("Your fangs feel dull.") diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/one_armed.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/one_armed.dm index 9ab156118298..806ef6057f9e 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/one_armed.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/one_armed.dm @@ -3,6 +3,7 @@ name = "One Arm" desc = "You're missing your an arm. That just ain't right." icon = FA_ICON_HAND + ttrpg_sources = list(/datum/source_book/wta20 = 474) value = -3 gain_text = span_warning("You can't feel your arm!") lose_text = span_notice("Huh? Your arm is back...") diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/permanent_fangs.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/permanent_fangs.dm index d4463c3c5acd..41300af654bb 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/permanent_fangs.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/permanent_fangs.dm @@ -2,6 +2,7 @@ /datum/quirk/darkpack/permafangs name = "Permanent Fangs" desc = "Your fangs do not retract, making it impossible for you to hide your true nature. While some mortals may think you've had your teeth filed or are wearing prosthetics, sooner or later you're going to run into someone who knows what you truly are." + ttrpg_sources = list(/datum/source_book/vtm20 = 482) // TTRPG accurate would be -3? But this is also missing the max Appearance lock.. value = -1 mob_trait = TRAIT_PERMAFANGS diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/pierced_veil.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/pierced_veil.dm index c05a8d72e10f..e3223d259ca8 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/pierced_veil.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/pierced_veil.dm @@ -1,9 +1,9 @@ -// W20 p. 484 /datum/quirk/darkpack/pierced_veil name = "Pierced Veil" // A little unsure who to do the logic on the social roll rn. desc = {"Unlike most Garou, your Crinos form does not trigger the Delirium in mortals. This makes you particularly vulnerable to werewolf hunters, who may find it less difficult to pursue you back to your caern, putting the members of your sept in considerable danger."} + ttrpg_sources = list(/datum/source_book/wta20 = 484) value = -3 mob_trait = TRAIT_PIERCED_VEIL icon = FA_ICON_MASKS_THEATER diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/prey_exclusion.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/prey_exclusion.dm index ff408b73e056..f9f0b11f60ac 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/prey_exclusion.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/prey_exclusion.dm @@ -11,6 +11,7 @@ GLOBAL_LIST_INIT(prey_exclusion_choice, list( /datum/quirk/darkpack/prey_exclusion name = "Prey Exclusion" desc = "You refuse to hunt a certain kind of prey. When joining the game, you'll have to select what type of NPC you cant feed from. Ventrue cannot take this flaw." + ttrpg_sources = list(/datum/source_book/vtm20 = 485) value = -1 mob_trait = TRAIT_PREY_EXCLUSION gain_text = span_notice("You feel very picky about what type of person you feed from.") diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/speech_impediment.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/speech_impediment.dm index 97acbb989ea8..5d76dc3c03b3 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/speech_impediment.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/speech_impediment.dm @@ -1,8 +1,8 @@ -//Vampire The Masquerade 20th Anniversary Edition, 485 /datum/quirk/darkpack/speech_impediment name = "Speech Impediment" desc = "You have a stammer that hampers verbal communication." icon = FA_ICON_COMMENT_SLASH + ttrpg_sources = list(/datum/source_book/vtm20 = 485) value = -1 gain_text = span_danger("You start worrying about what you're saying.") lose_text = span_notice("You feel easier about talking again.") diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/territorial.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/territorial.dm index 95e9cf311895..156e3922a1c1 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/territorial.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/territorial.dm @@ -35,6 +35,7 @@ GLOBAL_LIST_INIT(territorial_type_choices, init_territorial_type_choices()) /datum/quirk/darkpack/territorial name = "Territorial" desc = "You are extremely territorial, and can only feed in one particular area. You react with hostility if another vampire enters your territory without your notice - and if they feed without your permission, violence is sure to break out, as they're taking your food and resources. You are reluctant to leave your territory unless necessary. Your territory is the only area where you may feed." + ttrpg_sources = list(/datum/source_book/vtm20 = 486) value = -2 mob_trait = TRAIT_VAMPIRE_TERRITORIAL gain_text = span_notice("You need to protect your hunting grounds, your herd, your territory.") diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/vengeful.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/vengeful.dm index 4f9c46d4e14c..34564b4b56c7 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/vengeful.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/vengeful.dm @@ -1,6 +1,7 @@ /datum/quirk/darkpack/vengeful name = "Vengeful" desc = "You have a score to settle, incurred either during your mortal days or when you were Embraced. You are obsessed with taking vengeance against a particular individual or group, and it is the overriding priority in every situation. This is a roleplay trait and does not incur any gameplay mechanics - by taking it, you will be expected to roleplay a character obsessed with vengeance." + ttrpg_sources = list(/datum/source_book/vtm20 = 486) value = -1 // its a 2pt flaw in tabletop but lets be honest bro gain_text = span_notice("You become obsessed with vengeance.") lose_text = span_notice("You don't really care about vengeance anymore.") diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/victim_of_the_masquerade.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/victim_of_the_masquerade.dm index f93e0b974d2e..d9f84919a22c 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/victim_of_the_masquerade.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/victim_of_the_masquerade.dm @@ -1,6 +1,7 @@ /datum/quirk/darkpack/victim_of_the_masquerade name = "Victim of the Masquerade" desc = "The Camarilla's propaganda machine did a good job with you. Even after your character's Embrace, they refused to believe they are a vampire. They remain convinced there must be some logical explanation for their condition, and spend as much time as possible justifying it. You must make a willpower roll whenever feeding and each time you draw a bloodpoint from your victim. When you fail, your character faints for five seconds while also losing a point in their Humanity. You are expected to roleplay this flaw - such as insisting on eating regular food and not feeding." + ttrpg_sources = list(/datum/source_book/vtm20 = 486) value = -2 mob_trait = TRAIT_VICTIM_OF_THE_MASQUERADE gain_text = span_notice("Pff, I'm not a vamprie. Those don't exist.") diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/weak_willed.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/weak_willed.dm index bc2c12965d19..babf37115eed 100644 --- a/modular_darkpack/modules/merits_flaws/code/negative_quirks/weak_willed.dm +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/weak_willed.dm @@ -1,4 +1,3 @@ -// W20 p. 478, V20 p. 486 /datum/quirk/darkpack/weak_willed name = "Weak Willed" desc = {"You have little resistance to attempts to dominate or intimidate you. @@ -10,6 +9,10 @@ Your difficulties to resist Social Talents such as Intimidation or Leadership, as well as mind-altering spells or magic, are increased by two. Your Willpower may never rise above 4"} */ + ttrpg_sources = list( + /datum/source_book/vtm20 = 486, + /datum/source_book/wta20 = 478, + ) value = -1 mob_trait = TRAIT_WEAK_WILLED icon = FA_ICON_BRAIN diff --git a/modular_darkpack/modules/merits_flaws/code/neutral_quirks/illegal_identity.dm b/modular_darkpack/modules/merits_flaws/code/neutral_quirks/illegal_identity.dm index e798c18f029b..58ec488b337c 100644 --- a/modular_darkpack/modules/merits_flaws/code/neutral_quirks/illegal_identity.dm +++ b/modular_darkpack/modules/merits_flaws/code/neutral_quirks/illegal_identity.dm @@ -2,6 +2,7 @@ /datum/quirk/darkpack/illegal_identity name = "Illegal Identity" desc = "Illegal immigrant? Died legally? Born a wolf? The cops aren't happy." + ttrpg_sources = list(/datum/source_book/homebrew = WE_MADE_IT_UP) value = 0 quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_HIDE_FROM_SCAN icon = FA_ICON_PERSON_CIRCLE_QUESTION diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/beserker.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/beserker.dm index bd74f3d98208..bfd1804e86f0 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/beserker.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/beserker.dm @@ -1,10 +1,10 @@ -// W20 p. 476 /datum/quirk/darkpack/beserker name = "Berserker" desc = {"You have uncanny control over your inner anger, and can use your Rage as most Garou cannot. You can enter a berserk frenzy at will, ignoring your wound penalties. You still suffer the consequences of any actions committed in the throes of frenzy. When circumstances might cause you to frenzy, you must make a standard roll to see if you do so or not."} + ttrpg_sources = list(/datum/source_book/wta20 = 476) value = 2 icon = FA_ICON_ANGRY allowed_splats = SPLAT_SHIFTERS diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/blush_of_health_quirk.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/blush_of_health_quirk.dm index c0f8c98e1765..fc3cd93d7d91 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/blush_of_health_quirk.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/blush_of_health_quirk.dm @@ -1,6 +1,7 @@ /datum/quirk/darkpack/blush_of_health name = "Blush of Health" desc = "Some Kindred are capable of maintaining the illusion of life more convincingly than their peers. With minimal effort, you can appear flushed, warm, and breathing, making it significantly harder for others to identify you as one of the Undead. While active, you seem more alive than before." + ttrpg_sources = list(/datum/source_book/vtm20 = 480) value = 1 mob_trait = TRAIT_BLUSH_OF_HEALTH gain_text = span_notice("A faint warmth spreads across your skin as the blush of health settles over you.") diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/calm_heart.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/calm_heart.dm index 3fc01b8e447f..ae91ece210df 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/calm_heart.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/calm_heart.dm @@ -3,6 +3,7 @@ desc = {"You are naturally calm and do not easily fly off the handle. You receive two extra dice when attempting to resist a frenzy. Brujah may not take this Merit."} + ttrpg_sources = list(/datum/source_book/vtm20 = 485) value = 3 mob_trait = TRAIT_CALM_HEART icon = FA_ICON_HEART diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/deceptive_aura.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/deceptive_aura.dm index 58ff2d253c8d..31234e6a5842 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/deceptive_aura.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/deceptive_aura.dm @@ -1,7 +1,7 @@ -// V20 p. 493 /datum/quirk/darkpack/deceptive_aura name = "Deceptive Aura" desc = {"Your aura is unnaturally bright and colorful for a vampire. You register as a mortal on all attempts to read your aura."} + ttrpg_sources = list(/datum/source_book/vtm20 = 493) value = 1 mob_trait = TRAIT_DECEPTIVE_AURA icon = FA_ICON_TEMPERATURE_HIGH diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/eat_food_quirk.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/eat_food_quirk.dm index d6733ff44ca0..97c295be8d8d 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/eat_food_quirk.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/eat_food_quirk.dm @@ -1,6 +1,7 @@ /datum/quirk/darkpack/eat_food name = "Eat Food" desc = "Unlike most of the Undead, you retain the ability to eat and digest food normally, a semblance of your mortal life. While you gain no nourishment from it, you can consume food without the usual revulsion Kindred experience. Be warned: what goes down must come up, eventually." + ttrpg_sources = list(/datum/source_book/vtm20 = 480) value = 1 mob_trait = TRAIT_EAT_FOOD gain_text = span_notice("Your stomach stirs as you feel the organ come to life. You can now eat food.") diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/efficient_digestion.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/efficient_digestion.dm index 9915decd2c99..23ed52a5bb0b 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/efficient_digestion.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/efficient_digestion.dm @@ -1,6 +1,7 @@ /datum/quirk/darkpack/efficient_digestion name = "Efficient Digestion" desc = "You are able to draw more than the usual amount of nourishment from blood. When feeding, you gain an additional point to your blood pool for every two points of blood you consume." + ttrpg_sources = list(/datum/source_book/vtm20 = 480) value = 3 mob_trait = TRAIT_EFFICIENT_DIGESTION gain_text = span_notice("You feel as if you'll be satiated with ease.") diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/fair_glabro.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/fair_glabro.dm index 508eef62a44d..3ee14547e0f4 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/fair_glabro.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/fair_glabro.dm @@ -1,7 +1,7 @@ -// W20 p. 472 /datum/quirk/darkpack/fair_glabro name = "Fair Glabro" desc = "You have a Glabro form that can pass for human, though it's still larger than normal people. You have no penalties to Social Attributes in Glabro form." + ttrpg_sources = list(/datum/source_book/wta20 = 472) value = 2 mob_trait = TRAIT_FAIR_GLABRO icon = FA_ICON_PERSON_HALF_DRESS diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/hidden_diablerie.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/hidden_diablerie.dm index 1f42e7d71d3c..d518b6d24289 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/hidden_diablerie.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/hidden_diablerie.dm @@ -1,7 +1,7 @@ -//Vampire the Masquerade 20th Anniversary Edition, 493 /datum/quirk/darkpack/hidden_diablerie name = "Hidden Diablerie" desc = "The tell-tale black streaks of diablerie do not manifest in your aura." + ttrpg_sources = list(/datum/source_book/vtm20 = 493) value = 3 mob_trait = TRAIT_HIDDEN_DIABLERIE allowed_splats = list(SPLAT_KINDRED) diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/metamorph.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/metamorph.dm index 06b96e22c2c8..f5c0b3fd1716 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/metamorph.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/metamorph.dm @@ -1,10 +1,10 @@ -// W20 p. 473 /datum/quirk/darkpack/metamorph name = "Metamorph" desc = {"Shapechanging for you is as easy as breathing. You do not need to roll to change forms, nor is it necessary to spend a Rage point for an instantaneous shift. You make your changes as if you scored five successes on your roll to shift forms. If you lose consciousness from wounds or for some other reason, you may roll Wits + Primal Urge (difficulty 8) to choose which form you assume rather than reverting to breed form."} + ttrpg_sources = list(/datum/source_book/wta20 = 473) value = 7 mob_trait = TRAIT_METAMORPH // I love the greg sam. sa diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/pale_aura.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/pale_aura.dm index ab605f190a8a..f371a817a59e 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/pale_aura.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/pale_aura.dm @@ -1,4 +1,3 @@ -// Ghouls & Revenants p. 137, HH p. 43 /datum/quirk/darkpack/pale_aura name = "Pale Aura" desc = {"Any color your character's aura takes has a pale cast to it, as though he was a vampire."} @@ -6,6 +5,10 @@ Unless her player gains five chapter two or more successes on an Aura Perception roll, any vampire discerns your character's aura as one belonging to the Kindred"} */ + ttrpg_sources = list( + /datum/source_book/ghouls_and_revenants = 137, + /datum/source_book/hunterhunted2 = 43, + ) value = 2 mob_trait = TRAIT_PALE_AURA icon = FA_ICON_TEMPERATURE_EMPTY diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/time_sense.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/time_sense.dm index fbdf2633b20a..c40dafb00c1c 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/time_sense.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/time_sense.dm @@ -1,8 +1,8 @@ -// W20 p. 475 /datum/quirk/darkpack/time_sense name = "Time Sense" desc = {"You have an innate sense of time and are able to estimate the passage of time accurately without using a watch or other mechanical device, even after long periods of unconsciousness. This allows you to know (among other things) what phase the moon is in."} + ttrpg_sources = list(/datum/source_book/wta20 = 475) value = 1 mob_trait = TRAIT_TIME_SENSE icon = FA_ICON_STOPWATCH diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/touch_of_the_wyld.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/touch_of_the_wyld.dm index fc856b52a887..ae3a8523759c 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/touch_of_the_wyld.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/touch_of_the_wyld.dm @@ -2,6 +2,7 @@ /datum/quirk/darkpack/touch_of_the_wyld name = "Touch of the Wyld" desc = "Unlike nearly all other undead, you have no obvious Wyrmtaint. You are invisible to those who have the ability to naturally sense the Wyrm." + ttrpg_sources = list(/datum/source_book/vampire_wild_west = 114) value = 5 mob_trait = TRAIT_HIDDEN_WYRMTAINT icon = FA_ICON_TEMPERATURE_HIGH diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/unbondable.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/unbondable.dm index 2d2faa1b2436..c84c4e169813 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/unbondable.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/unbondable.dm @@ -1,7 +1,7 @@ -// V20 p. 494 /datum/quirk/darkpack/unbondable name = "Unbondable" desc = "You are immune to being blood bound. Tremere cannot take this Merit." + ttrpg_sources = list(/datum/source_book/vtm20 = 494) value = 5 mob_trait = TRAIT_UNBONDABLE icon = FA_ICON_CHAIN_BROKEN diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/untamable.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/untamable.dm index 913de0114813..18dc00b4dd36 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/untamable.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/untamable.dm @@ -5,6 +5,7 @@ You are immune to vampiric Domination (but not emotional manipulations via Presence)"} // Change this line when we add these gifts. // You are immune to vampiric Domination (but not emotional manipulations via Presence) and these Gifts will not work on you: Roll Over, Obedience, and Mastery."} + ttrpg_sources = list(/datum/source_book/wta20 = 476) value = 5 mob_trait = TRAIT_MERIT_UNTAMABLE icon = FA_ICON_SHIELD_DOG diff --git a/modular_darkpack/modules/merits_flaws/code/positive_quirks/wolf_sight.dm b/modular_darkpack/modules/merits_flaws/code/positive_quirks/wolf_sight.dm index 7456724a5caa..902af01fe2e8 100644 --- a/modular_darkpack/modules/merits_flaws/code/positive_quirks/wolf_sight.dm +++ b/modular_darkpack/modules/merits_flaws/code/positive_quirks/wolf_sight.dm @@ -5,6 +5,7 @@ Your night vision, however, far surpasses human nocturnal vision."} // Perception is not real yet. // You also notice movement more readily. You gain an extra die to all visually-based Perception rolls that involve movement or take place at night."} + ttrpg_sources = list(/datum/source_book/wta20 = 472) value = 1 mob_trait = TRAIT_TRUE_NIGHT_VISION icon = FA_ICON_DOG diff --git a/tgstation.dme b/tgstation.dme index cbdf7ae388bd..da4e92230718 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -438,6 +438,7 @@ #include "code\__DEFINES\~darkpack\banning.dm" #include "code\__DEFINES\~darkpack\beastmaster.dm" #include "code\__DEFINES\~darkpack\blooper.dm" +#include "code\__DEFINES\~darkpack\book_sources.dm" #include "code\__DEFINES\~darkpack\branding.dm" #include "code\__DEFINES\~darkpack\colors.dm" #include "code\__DEFINES\~darkpack\combat.dm" @@ -7128,6 +7129,7 @@ #include "modular_darkpack\master_files\code\controllers\configuration\entries\game_options.dm" #include "modular_darkpack\master_files\code\controllers\configuration\entries\general.dm" #include "modular_darkpack\master_files\code\controllers\subsystem\ambience.dm" +#include "modular_darkpack\master_files\code\datums\datum.dm" #include "modular_darkpack\master_files\code\datums\actions\action.dm" #include "modular_darkpack\master_files\code\datums\components\dejavu.dm" #include "modular_darkpack\master_files\code\datums\mapgen\woods_generator.dm" @@ -7272,6 +7274,7 @@ #include "modular_darkpack\modules\blooper\code\changeling.dm" #include "modular_darkpack\modules\blooper\code\preferences\preferences.dm" #include "modular_darkpack\modules\blooper\code\preferences\middleware\blooper.dm" +#include "modular_darkpack\modules\book_sources\code\source_book_datum.dm" #include "modular_darkpack\modules\books\code\bookcases.dm" #include "modular_darkpack\modules\books\code\books.dm" #include "modular_darkpack\modules\brain_damage\code\phobia.dm" From cd48bc57d89f9a6318b131871975cc0a39f426aa Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:27:06 +0000 Subject: [PATCH 364/445] Automatic changelog for PR #1100 [ci skip] --- html/changelogs/AutoChangeLog-pr-1100.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1100.yml diff --git a/html/changelogs/AutoChangeLog-pr-1100.yml b/html/changelogs/AutoChangeLog-pr-1100.yml new file mode 100644 index 000000000000..b7f43295174a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1100.yml @@ -0,0 +1,5 @@ +author: "FalloutFalcon" +delete-after: True +changes: + - code_imp: "new var on datum level for storing info on what source book it comes from" + - config: "Adds config for limiting merit based on official status" \ No newline at end of file From 136549fb5299664b0e9e809e7ac9720055baec7f Mon Sep 17 00:00:00 2001 From: QuiteLiterallyAnything <154708292+QuiteLiterallyAnything@users.noreply.github.com> Date: Fri, 31 Jul 2026 13:04:23 -0500 Subject: [PATCH 365/445] Makes stray Syndicate cargo pods drop with Syndicate-styled crates (#97257) ## About The Pull Request This PR makes the stray Syndicate cargo pod event drop a proper Syndicate crate rather than a generic grey one. The contents are the same, and it is EMAG'd upon drop to prevent the lock from being a balancing factor (as the previous crate type did not have a lock). Existing stray supply pod event code has been slightly tweaked to allow for both the dropping of specific crate types in-code and the EMAG-ing of dropped crates.
Expand for testing screenshot: image
## Why It's Good For The Game It's (thematically) rather odd that this event drops a regular crate when a Syndicate variant exists. ## Changelog :cl: fix: Stray Syndicate cargo pods now drop with Syndicate-styled crates rather than generic grey ones. /:cl: --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/modules/events/stray_cargo.dm | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/code/modules/events/stray_cargo.dm b/code/modules/events/stray_cargo.dm index 562925807e31..a8d362deb3b9 100644 --- a/code/modules/events/stray_cargo.dm +++ b/code/modules/events/stray_cargo.dm @@ -38,6 +38,10 @@ var/atom/admin_override_turf ///Admin setable override to spawn a specific cargo pack type var/admin_override_contents + /// Set to use a crate of a specific type irregardless of supply pack. + var/crate_type + /// Set to TRUE to emag the launched crate rather than just unlocking it. + var/emag_crate = FALSE /datum/round_event/stray_cargo/announce(fake) if(fake) @@ -100,12 +104,15 @@ var/storage_override if(initial(supply_pack.order_flags) & ORDER_GOODY) // We offer goody items inside of briefcases, but regular crates still default to their standard crates. storage_override = /obj/item/storage/briefcase/empty - var/obj/container = supply_pack.generate(null, crate_override = storage_override) + var/obj/container = supply_pack.generate(null, crate_override = (crate_type || storage_override)) if(container && istype(container, /obj/structure/closet/crate)) //empty supply packs are a thing! get memed on. var/obj/structure/closet/crate/crate = container - crate.locked = FALSE //Unlock secure crates - crate.update_appearance() + if(emag_crate) + crate.emag_act() // `emag_act()` already calls `update_appeance()` + else + crate.locked = FALSE //Unlock secure crates + crate.update_appearance() var/obj/structure/closet/supplypod/pod = make_pod() var/obj/effect/pod_landingzone/landing_marker = new(landing_zone, pod, container) var/static/mutable_appearance/target_appearance = mutable_appearance('icons/obj/supplypods_32x32.dmi', "LZ") @@ -191,6 +198,8 @@ /datum/round_event/stray_cargo/syndicate possible_pack_types = list(/datum/supply_pack/misc/syndicate) + crate_type = /obj/structure/closet/crate/secure/syndicate + emag_crate = TRUE ///Apply the syndicate pod skin /datum/round_event/stray_cargo/syndicate/make_pod() From 66dc4b182f4c2f53d5c1a6ef6dcd09df67c48b9d Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:04:51 +0000 Subject: [PATCH 366/445] Automatic changelog for PR #97257 [ci skip] --- html/changelogs/AutoChangeLog-pr-97257.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97257.yml diff --git a/html/changelogs/AutoChangeLog-pr-97257.yml b/html/changelogs/AutoChangeLog-pr-97257.yml new file mode 100644 index 000000000000..a26dbf673702 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97257.yml @@ -0,0 +1,4 @@ +author: "QuiteLiterallyAnything" +delete-after: True +changes: + - bugfix: "Stray Syndicate cargo pods now drop with Syndicate-styled crates rather than generic grey ones." \ No newline at end of file From 80de270a93c40321ae08edd9fdad08bbda109754 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:15:30 +0000 Subject: [PATCH 367/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97257.yml | 4 ---- html/changelogs/archive/2026-07.yml | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97257.yml diff --git a/html/changelogs/AutoChangeLog-pr-97257.yml b/html/changelogs/AutoChangeLog-pr-97257.yml deleted file mode 100644 index a26dbf673702..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97257.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "QuiteLiterallyAnything" -delete-after: True -changes: - - bugfix: "Stray Syndicate cargo pods now drop with Syndicate-styled crates rather than generic grey ones." \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index b0b15a16c53c..193634ae0d4d 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -965,6 +965,8 @@ in morgue slabs QuiteLiterallyAnything: - bugfix: Fixed missing texture fish suicide. + - bugfix: Stray Syndicate cargo pods now drop with Syndicate-styled crates rather + than generic grey ones. lelandkemble: - bugfix: All slapcrafting recipes now work again mcbalaam: From 0992ec02722bbb855ff38172315c16c28f4d2b61 Mon Sep 17 00:00:00 2001 From: QuiteLiterallyAnything <154708292+QuiteLiterallyAnything@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:20:05 -0500 Subject: [PATCH 368/445] Adds a missing wall to Catwalk's upper atmospherics room (#97281) ## About The Pull Request This PR adds a singular wall to Catwalk's upper atmospheric room. ## Why It's Good For The Game Without this wall, Catwalk's upper atmospheric room is exposed to the void of space. --- _maps/map_files/CatwalkStation/CatwalkStation_2023.dmm | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm b/_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm index d609a01c6171..ba90617d069d 100644 --- a/_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm +++ b/_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm @@ -75788,9 +75788,6 @@ "ujb" = ( /turf/open/floor/iron, /area/station/maintenance/port/fore) -"ujd" = ( -/turf/open/floor/plating, -/area/station/engineering/atmos/upper) "ujr" = ( /obj/effect/turf_decal/trimline/yellow/arrow_cw{ dir = 8 @@ -205790,7 +205787,7 @@ gtC gtC gtC fHO -ujd +unl bYd bYd eaU From 0b04e9182f6f5fc92232c6a7a9a7693b6c286442 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 00:20:30 +0000 Subject: [PATCH 369/445] Automatic changelog for PR #97281 [ci skip] --- html/changelogs/AutoChangeLog-pr-97281.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97281.yml diff --git a/html/changelogs/AutoChangeLog-pr-97281.yml b/html/changelogs/AutoChangeLog-pr-97281.yml new file mode 100644 index 000000000000..0a6a02676343 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97281.yml @@ -0,0 +1,4 @@ +author: "QuiteLiterallyAnything" +delete-after: True +changes: + - bugfix: "Fixed Catwalk's upper atmospherics room being exposed to space by default." \ No newline at end of file From ea80d3d55dc521cfdecf19ff200150d793701b83 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 00:30:30 +0000 Subject: [PATCH 370/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97281.yml | 4 ---- html/changelogs/archive/2026-08.yml | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97281.yml create mode 100644 html/changelogs/archive/2026-08.yml diff --git a/html/changelogs/AutoChangeLog-pr-97281.yml b/html/changelogs/AutoChangeLog-pr-97281.yml deleted file mode 100644 index 0a6a02676343..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97281.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "QuiteLiterallyAnything" -delete-after: True -changes: - - bugfix: "Fixed Catwalk's upper atmospherics room being exposed to space by default." \ No newline at end of file diff --git a/html/changelogs/archive/2026-08.yml b/html/changelogs/archive/2026-08.yml new file mode 100644 index 000000000000..d7abce9f8e0d --- /dev/null +++ b/html/changelogs/archive/2026-08.yml @@ -0,0 +1,3 @@ +2026-08-01: + QuiteLiterallyAnything: + - bugfix: Fixed Catwalk's upper atmospherics room being exposed to space by default. From 20a259dd8b3a2e356ad7cc88f53a64dcaa40e8af Mon Sep 17 00:00:00 2001 From: Thunder12345 Date: Sat, 1 Aug 2026 01:33:32 +0100 Subject: [PATCH 371/445] Bitrunning the Gradening (#97209) ## About The Pull Request Shows the best grade achieved for each domain in the quantum console. image ## Why It's Good For The Game Allows bitrunners to see what domains they've already finished without going back through a stack of paper they may have already trashed or just remembering everything. Also provides players with a sense of pride and accomplishment for scoring high on all the domains. --- code/__DEFINES/bitrunning.dm | 7 +++++ code/_globalvars/lists/bitrunning.dm | 9 +++++++ .../subsystem/networks/bitrunning.dm | 3 +++ code/modules/bitrunning/server/loot.dm | 27 ++++++------------- .../virtual_domain/virtual_domain.dm | 10 +++++++ tgstation.dme | 1 + .../tgui/interfaces/QuantumConsole.tsx | 3 +++ 7 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 code/_globalvars/lists/bitrunning.dm diff --git a/code/__DEFINES/bitrunning.dm b/code/__DEFINES/bitrunning.dm index 900182eb43d7..379a302fac22 100644 --- a/code/__DEFINES/bitrunning.dm +++ b/code/__DEFINES/bitrunning.dm @@ -4,6 +4,13 @@ #define BITRUNNER_COST_HIGH 3 #define BITRUNNER_COST_EXTREME 20 +#define BITRUNNING_GRADE_NONE "None" +#define BITRUNNING_GRADE_D "D" +#define BITRUNNING_GRADE_C "C" +#define BITRUNNING_GRADE_B "B" +#define BITRUNNING_GRADE_A "A" +#define BITRUNNING_GRADE_S "S" + /// Yay you did it #define BITRUNNER_REWARD_MIN 1 /// You faced some decent odds diff --git a/code/_globalvars/lists/bitrunning.dm b/code/_globalvars/lists/bitrunning.dm new file mode 100644 index 000000000000..fe514c83f928 --- /dev/null +++ b/code/_globalvars/lists/bitrunning.dm @@ -0,0 +1,9 @@ +/// Assoc list of bitrunning grades in ascending order and their associated fontawesome icons. +GLOBAL_LIST_INIT(bitrunning_grades, list( + BITRUNNING_GRADE_NONE = FA_ICON_MINUS, + BITRUNNING_GRADE_D = FA_ICON_D, + BITRUNNING_GRADE_C = FA_ICON_C, + BITRUNNING_GRADE_B = FA_ICON_B, + BITRUNNING_GRADE_A = FA_ICON_A, + BITRUNNING_GRADE_S = FA_ICON_S +)) diff --git a/code/controllers/subsystem/networks/bitrunning.dm b/code/controllers/subsystem/networks/bitrunning.dm index 1bb8eb49e4d7..7eaae94304f5 100644 --- a/code/controllers/subsystem/networks/bitrunning.dm +++ b/code/controllers/subsystem/networks/bitrunning.dm @@ -24,6 +24,8 @@ SUBSYSTEM_DEF(bitrunning) var/can_view = domain.can_view_name(scanner_tier, points) var/can_view_reward = domain.can_view_reward(scanner_tier, points) + var/grade_symbol = GLOB.bitrunning_grades[domain.best_grade] + UNTYPED_LIST_ADD(levels, list( "announce_ghosts" = domain.announce_to_ghosts, "cost" = domain.cost, @@ -34,6 +36,7 @@ SUBSYSTEM_DEF(bitrunning) "has_secondary_objectives" = counterlist_sum(domain.secondary_loot) ? TRUE : FALSE, "name" = can_view ? domain.name : REDACTED, "reward" = can_view_reward ? domain.reward_points : REDACTED, + "grade_symbol" = grade_symbol, )) return levels diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm index 2d9db6c5a7d0..83a06849555a 100644 --- a/code/modules/bitrunning/server/loot.dm +++ b/code/modules/bitrunning/server/loot.dm @@ -1,10 +1,3 @@ -#define GRADE_D "D" -#define GRADE_C "C" -#define GRADE_B "B" -#define GRADE_A "A" -#define GRADE_S "S" - - /// Handles calculating rewards based on number of players, parts, threats, etc /obj/machinery/quantum_server/proc/calculate_rewards() var/rewards_base = 0.8 @@ -74,6 +67,8 @@ reward_cache.manifest = WEAKREF(certificate) reward_cache.update_appearance() + generated_domain.submit_grade(grade) + if(can_generate_tech_disk(grade)) SSblackbox.record_feedback("tally", "bitrunning_bepis_rewarded", 1, generated_domain.key) new /obj/item/disk/design_disk/bepis/remove_tech(reward_cache) @@ -159,7 +154,7 @@ var/static/list/passing_grades = list() if(!passing_grades.len) - passing_grades = list(GRADE_A,GRADE_S) + passing_grades = list(BITRUNNING_GRADE_A,BITRUNNING_GRADE_S) return generated_domain.difficulty >= BITRUNNER_DIFFICULTY_MEDIUM && (grade in passing_grades) @@ -190,18 +185,12 @@ switch(score) if(1 to 4) - return GRADE_D + return BITRUNNING_GRADE_D if(5 to 7) - return GRADE_C + return BITRUNNING_GRADE_C if(8 to 10) - return GRADE_B + return BITRUNNING_GRADE_B if(11 to 13) - return GRADE_A + return BITRUNNING_GRADE_A else - return GRADE_S - -#undef GRADE_D -#undef GRADE_C -#undef GRADE_B -#undef GRADE_A -#undef GRADE_S + return BITRUNNING_GRADE_S diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index 139e46d5508a..e5d19db30aac 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -91,6 +91,12 @@ /// The role that ghosts will get. Only used for poll text. var/spawner_role = "Antagonist" + /** + * Scorekeeping + */ + + /// The highest score grade achieved so far + var/best_grade = "None" /datum/lazy_template/virtual_domain/proc/can_view_name(scanner_tier, server_points) return difficulty < scanner_tier && cost <= server_points + 5 @@ -134,3 +140,7 @@ /// Overridable proc to be called after the map is loaded. /datum/lazy_template/virtual_domain/proc/setup_domain(list/created_atoms) return + +/datum/lazy_template/virtual_domain/proc/submit_grade(new_grade) + if(GLOB.bitrunning_grades.Find(new_grade) > GLOB.bitrunning_grades.Find(best_grade)) + best_grade = new_grade diff --git a/tgstation.dme b/tgstation.dme index 5cb0714a9f11..fff62c6cbdc9 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -575,6 +575,7 @@ #include "code\_globalvars\time_vars.dm" #include "code\_globalvars\lists\achievements.dm" #include "code\_globalvars\lists\ambience.dm" +#include "code\_globalvars\lists\bitrunning.dm" #include "code\_globalvars\lists\canisters.dm" #include "code\_globalvars\lists\cargo.dm" #include "code\_globalvars\lists\client.dm" diff --git a/tgui/packages/tgui/interfaces/QuantumConsole.tsx b/tgui/packages/tgui/interfaces/QuantumConsole.tsx index 210ba4e9930d..b191549a6ef0 100644 --- a/tgui/packages/tgui/interfaces/QuantumConsole.tsx +++ b/tgui/packages/tgui/interfaces/QuantumConsole.tsx @@ -54,6 +54,7 @@ type Domain = { has_secondary_objectives: BooleanLike; name: string; reward: number | string; + grade_symbol: string; }; type DomainEntryProps = { @@ -261,6 +262,7 @@ function DomainEntry(props: DomainEntryProps) { has_secondary_objectives, name, reward, + grade_symbol, }, } = props; const { act, data } = useBackend(); @@ -308,6 +310,7 @@ function DomainEntry(props: DomainEntryProps) { {!!announce_ghosts && canView && } } + sideIcon={grade_symbol} > From 6347116b97174b60eaef317f6e2aadd35fb51725 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 00:33:56 +0000 Subject: [PATCH 372/445] Automatic changelog for PR #97209 [ci skip] --- html/changelogs/AutoChangeLog-pr-97209.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97209.yml diff --git a/html/changelogs/AutoChangeLog-pr-97209.yml b/html/changelogs/AutoChangeLog-pr-97209.yml new file mode 100644 index 000000000000..fd09973be4de --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97209.yml @@ -0,0 +1,4 @@ +author: "Thunder12345" +delete-after: True +changes: + - rscadd: "The quantum console now tells bitrunners what grade they've reached on completed domains." \ No newline at end of file From 9a0e0a9242db1ad712fd8a76671753d4aa5a2ef5 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 02:35:53 +0000 Subject: [PATCH 373/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-1100.yml | 5 ----- html/changelogs/AutoChangeLog-pr-1286.yml | 4 ---- html/changelogs/archive/2026-08.yml | 7 +++++++ 3 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-1100.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1286.yml create mode 100644 html/changelogs/archive/2026-08.yml diff --git a/html/changelogs/AutoChangeLog-pr-1100.yml b/html/changelogs/AutoChangeLog-pr-1100.yml deleted file mode 100644 index b7f43295174a..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1100.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - code_imp: "new var on datum level for storing info on what source book it comes from" - - config: "Adds config for limiting merit based on official status" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1286.yml b/html/changelogs/AutoChangeLog-pr-1286.yml deleted file mode 100644 index 1f73ae0632d9..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1286.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Faengi" -delete-after: True -changes: - - rscadd: "Added the Uncontrollable flaw for Brujah" \ No newline at end of file diff --git a/html/changelogs/archive/2026-08.yml b/html/changelogs/archive/2026-08.yml new file mode 100644 index 000000000000..0ab9f8710cb4 --- /dev/null +++ b/html/changelogs/archive/2026-08.yml @@ -0,0 +1,7 @@ +2026-08-01: + Faengi: + - rscadd: Added the Uncontrollable flaw for Brujah + FalloutFalcon: + - code_imp: new var on datum level for storing info on what source book it comes + from + - config: Adds config for limiting merit based on official status From 29134a5733b423e7bc336921f8e503c0d0fd784f Mon Sep 17 00:00:00 2001 From: Fghj240 <43589874+Fghj240@users.noreply.github.com> Date: Fri, 31 Jul 2026 21:24:54 -0700 Subject: [PATCH 374/445] Adds an emp protection stat (#97062) ## About The Pull Request EMP effects against specifically only robotic bodyparts and cybernetic organs get weaker if their severity is weaker. In general, a lower severity leads to a lower duration or a lower intensity in the case of taking damage. If the effect is strong even at low durations (like how any duration of knockdown makes you drop items) then it was moved to a scaling probability of occuring as well. Also adds emp_protection, like flash_protection but with a slightly hackier implementation. It adds to the severity (higher severity value = weaker emp). Regular mod emp shield module has 1 emp_protection, so does adamantine armor because it's like, several inches of metal I bet, and tesla has 1 as well. Also something to note is that suit sensors only break when hit by heavy EMPs, so with any amount of emp_protection those won't break. ## Why It's Good For The Game Something something flux heart bad and EMPs are too binary, I think making more use out of severity is a good idea. ## Changelog :cl: balance: robotic limbs, organs, and cybernetic implants have their emp effects scale more closely with emp severity. balance: reactive tesla armor, adamantine armor, the mod emp shield module, and tinfoil hats slightly lower the severity of incoming EMPs. /:cl: --------- Co-authored-by: Fghj240 --- code/__DEFINES/bodyparts.dm | 8 +-- code/__DEFINES/mobs.dm | 8 +++ code/modules/clothing/clothing.dm | 4 ++ code/modules/clothing/head/tinfoilhat.dm | 1 + .../modules/clothing/suits/reactive_armour.dm | 1 + .../mob/living/carbon/human/human_defense.dm | 5 ++ code/modules/mob/living/living_defense.dm | 11 ++- code/modules/mob/living/living_defines.dm | 3 + code/modules/mod/modules/modules_general.dm | 8 +++ .../xenobiology/crossbreeding/_clothing.dm | 1 + .../surgery/bodyparts/robot_bodyparts.dm | 69 +++++++------------ .../internal/cyberimp/augments_chest.dm | 6 +- .../internal/cyberimp/augments_internal.dm | 4 +- .../organs/internal/eyes/eyes_augments.dm | 2 +- .../surgery/organs/internal/heart/_heart.dm | 6 +- .../surgery/organs/internal/liver/_liver.dm | 2 +- .../surgery/organs/internal/lungs/_lungs.dm | 2 +- .../organs/internal/stomach/_stomach.dm | 3 +- 18 files changed, 80 insertions(+), 64 deletions(-) diff --git a/code/__DEFINES/bodyparts.dm b/code/__DEFINES/bodyparts.dm index a9b152578115..dcad804ed436 100644 --- a/code/__DEFINES/bodyparts.dm +++ b/code/__DEFINES/bodyparts.dm @@ -37,13 +37,13 @@ #define AUGGED_LIMB_EMP_PARALYZE_TIME 3 SECONDS /// When hit by an EMP, the time an augged leg will be knocked down for. -#define AUGGED_LEG_EMP_KNOCKDOWN_TIME 3 SECONDS +#define AUGGED_LEG_EMP_KNOCKDOWN_TIME 5 SECONDS /// When hit by an EMP, the time a augged chest will cause a hardstun for if its above the damage threshold. #define AUGGED_CHEST_EMP_STUN_TIME 3 SECONDS -/// When hit by an EMP, the time an augged chest will cause the mob to shake() for. -#define AUGGED_CHEST_EMP_SHAKE_TIME 5 SECONDS +/// When hit by an EMP, the time an augged chest will cause the mob to jitter for. +#define AUGGED_CHEST_EMP_SHAKE_TIME 6 SECONDS /// When hit by an EMP, the time an augged head will make vision fucky for. -#define AUGGED_HEAD_EMP_GLITCH_DURATION 6 SECONDS +#define AUGGED_HEAD_EMP_GLITCH_DURATION 3.5 SECONDS // Color priorities for bodyparts /// Abductor team recoloring priority diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 94a06bf79625..3ef1ffca3b36 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -545,6 +545,14 @@ #define EAR_PROTECTION_VACUUM 3 #define EAR_PROTECTION_FULL INFINITY +/** + * EMP protection + * These values are additive to determine your overall emp protection + */ +#define EMP_PROTECTION_NONE 0 +#define EMP_PROTECTION_MODERATE 1 +#define EMP_PROTECTION_HIGH 2 + /** * Soundbang defines * These values are used as argument to determine the strength of the soundbang_act call diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 5795d1295e94..d1bf6615c9da 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -23,6 +23,8 @@ var/toggle_message ///chat message when the visor is toggled up. var/alt_toggle_message + /// What level of emp protection item has + var/emp_protection = EMP_PROTECTION_NONE var/clothing_flags = NONE ///List of items that can be equipped in the suit storage slot while we're worn. @@ -396,6 +398,8 @@ .["sterile"] = "Increases the speed at which reagents are administered to others by [round((1/NITRILE_GLOVES_MULTIPLIER-1)*100, 1)]%." if(TRAIT_FAST_CUFFING in clothing_traits) .["secure"] = "Increases the speed at which you apply restraints." + if(emp_protection >= EMP_PROTECTION_NONE) + .["emp resistant"] = "Reduces the effects of incoming electromagnetic pulses on the wearer." /obj/item/clothing/examine_descriptor(mob/user) return "clothing" diff --git a/code/modules/clothing/head/tinfoilhat.dm b/code/modules/clothing/head/tinfoilhat.dm index 903ad5e14502..04e3bb0d4364 100644 --- a/code/modules/clothing/head/tinfoilhat.dm +++ b/code/modules/clothing/head/tinfoilhat.dm @@ -11,6 +11,7 @@ var/datum/brain_trauma/mild/phobia/conspiracies/paranoia var/warped = FALSE interaction_flags_mouse_drop = NEED_HANDS + emp_protection = EMP_PROTECTION_MODERATE /datum/armor/costume_foilhat laser = -5 diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm index 97c622d8e902..c6ebbea73f0d 100644 --- a/code/modules/clothing/suits/reactive_armour.dm +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -233,6 +233,7 @@ cooldown_message = span_danger("The tesla capacitors on the reactive tesla armor are still recharging! The armor merely emits some sparks.") emp_message = span_warning("The tesla capacitors beep ominously for a moment.") clothing_traits = list(TRAIT_TESLA_SHOCKIMMUNE) + emp_protection = EMP_PROTECTION_MODERATE /// How strong are the zaps we give off? var/zap_power = 2.5e4 /// How far to the zaps we give off go? diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index a61dad7a3070..08889d1055f9 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -758,3 +758,8 @@ . = ..() for (var/obj/item/clothing/clothing in get_equipped_items()) . += clothing.flash_protect + +/mob/living/carbon/human/get_emp_protection() + . = ..() + for(var/obj/item/clothing/each_clothing in get_equipped_items()) + . += each_clothing.emp_protection diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 69b030f6d98f..e6014d7bd961 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -31,15 +31,19 @@ /mob/living/proc/getarmor(def_zone, type) return 0 -//this returns the mob's protection against eye damage (number between -1 and 2) from bright lights +/// This returns the mob's protection against eye damage (number between -1 and 2) from bright lights /mob/living/proc/get_eye_protection() return 0 -///A easy to use proc to apply both organ damage and temporary deafness at once, so you don't have to get the ears everytime. +/// This returns a number is subtracted from the severity of incoming emps against this mob. +/mob/living/proc/get_emp_protection() + return emp_protection + +/// An easy to use proc to apply both organ damage and temporary deafness at once, so you don't have to get the ears everytime. /mob/living/proc/sound_damage(damage, deafen) return -//this returns the mob's protection against ear damage (0:no protection; 1: some ear protection; 2: has no ears) +/// This returns the mob's protection against ear damage (0:no protection; 1: some ear protection; 2: has no ears) /mob/living/proc/get_ear_protection(ignore_deafness = FALSE) if(!ignore_deafness && HAS_TRAIT(src, TRAIT_DEAF)) return INFINITY //For all my homies that can not hear in the world @@ -615,6 +619,7 @@ . = ..() if(. & EMP_PROTECT_CONTENTS) return + severity += get_emp_protection() for(var/obj/inside in contents) inside.emp_act(severity) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 9a09b7de0a3e..13f52e43240e 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -266,3 +266,6 @@ /// Reference to the unconscious appearance image that appears in place of the mob to other knocked out mobs VAR_FINAL/image/unconscious_appearance + + /// Reduces the effects of EMPs, does NOT negate them even at very high numbers + var/emp_protection = EMP_PROTECTION_NONE diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm index 6a6862e918bf..748cb5825509 100644 --- a/code/modules/mod/modules/modules_general.dm +++ b/code/modules/mod/modules/modules_general.dm @@ -382,14 +382,22 @@ incompatible_modules = list(/obj/item/mod/module/emp_shield) required_slots = list(ITEM_SLOT_BACK|ITEM_SLOT_BELT) custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 5, /datum/material/plasma = SMALL_MATERIAL_AMOUNT * 5) + /// How much emp protection this module grants to its wearer when fully extended. + var/protection_factor = EMP_PROTECTION_MODERATE /obj/item/mod/module/emp_shield/on_install() . = ..() mod.AddElement(/datum/element/empprotection, EMP_PROTECT_ALL) + var/list/all_parts = mod.get_parts() + for(var/obj/item/clothing/mod_part as anything in all_parts) + mod_part.emp_protection += protection_factor/all_parts.len /obj/item/mod/module/emp_shield/on_uninstall(deleting = FALSE) . = ..() mod.RemoveElement(/datum/element/empprotection, EMP_PROTECT_ALL) + var/list/all_parts = mod.get_parts() + for(var/obj/item/clothing/mod_part as anything in all_parts) + mod_part.emp_protection -= protection_factor/all_parts.len /obj/item/mod/module/emp_shield/advanced name = "MOD advanced EMP shield module" diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm index fbcef4a6d817..f2654e913d61 100644 --- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm +++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm @@ -129,6 +129,7 @@ Slimecrossing Armor flags_inv = NONE item_flags = IMMUTABLE_SLOW slowdown = 4 + emp_protection = EMP_PROTECTION_MODERATE var/hit_reflect_chance = 40 /obj/item/clothing/suit/armor/heavy/adamantine/Initialize(mapload) diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm index 62a7e7e5635f..1e63a819c963 100644 --- a/code/modules/surgery/bodyparts/robot_bodyparts.dm +++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm @@ -140,15 +140,15 @@ . = ..() if(!. || isnull(owner)) return - - var/knockdown_time = AUGGED_LEG_EMP_KNOCKDOWN_TIME - if (severity == EMP_HEAVY) - knockdown_time *= 2 - owner.Knockdown(knockdown_time) - if(INCAPACITATED_IGNORING(owner, INCAPABLE_RESTRAINTS|INCAPABLE_GRAB)) // So the message isn't duplicated. If they were stunned beforehand by something else, then the message not showing makes more sense anyways. - return - to_chat(owner, span_danger("As your [plaintext_zone] unexpectedly malfunctions, it causes you to fall to the ground!")) - return + if(prob(80 / severity)) + if(owner.body_position == LYING_DOWN) // So the message isn't duplicated. If they were stunned beforehand by something else, then the message not showing makes more sense anyways. + to_chat(owner, span_danger("Your [plaintext_zone] suddenly malfunctions!")) + else + to_chat(owner, span_danger("As your [plaintext_zone] suddenly malfunctions, it causes you to fall to the ground!")) + owner.Knockdown(AUGGED_LEG_EMP_KNOCKDOWN_TIME) + owner.adjust_staggered(STAGGERED_SLOWDOWN_LENGTH * 3 / severity) + owner.client?.move_delay = max(owner.client?.move_delay, world.time) + owner.client?.move_delay += 0.6 SECONDS / severity /obj/item/bodypart/leg/right/robot name = "cyborg right leg" @@ -196,15 +196,15 @@ . = ..() if(!. || isnull(owner)) return - - var/knockdown_time = AUGGED_LEG_EMP_KNOCKDOWN_TIME - if (severity == EMP_HEAVY) - knockdown_time *= 2 - owner.Knockdown(knockdown_time) - if(INCAPACITATED_IGNORING(owner, INCAPABLE_RESTRAINTS|INCAPABLE_GRAB)) // So the message isn't duplicated. If they were stunned beforehand by something else, then the message not showing makes more sense anyways. - return - to_chat(owner, span_danger("As your [plaintext_zone] unexpectedly malfunctions, it causes you to fall to the ground!")) - return + if(prob(80 / severity)) + if(owner.body_position == LYING_DOWN) // So the message isn't duplicated. If they were stunned beforehand by something else, then the message not showing makes more sense anyways. + to_chat(owner, span_danger("Your [plaintext_zone] suddenly malfunctions!")) + else + to_chat(owner, span_danger("As your [plaintext_zone] suddenly malfunctions, it causes you to fall to the ground!")) + owner.Knockdown(AUGGED_LEG_EMP_KNOCKDOWN_TIME) + owner.adjust_staggered(STAGGERED_SLOWDOWN_LENGTH * 3 / severity) + owner.client?.move_delay = max(owner.client?.move_delay, world.time) + owner.client?.move_delay += 0.6 SECONDS / severity /obj/item/bodypart/chest/robot name = "cyborg torso" @@ -258,24 +258,11 @@ . = ..() if(!. || isnull(owner)) return - - var/stun_time = 0 - var/shift_x = 3 - var/shift_y = 0 - var/shake_duration = AUGGED_CHEST_EMP_SHAKE_TIME - - if(severity == EMP_HEAVY) - stun_time = AUGGED_CHEST_EMP_STUN_TIME - - shift_x = 5 - shift_y = 2 - var/damage_percent_to_max = (get_damage() / max_damage) - if (stun_time && (damage_percent_to_max >= robotic_emp_paralyze_damage_percent_threshold)) + if(damage_percent_to_max >= robotic_emp_paralyze_damage_percent_threshold) to_chat(owner, span_danger("Your [plaintext_zone]'s logic boards temporarily become unresponsive!")) - owner.Stun(stun_time) - owner.Shake(pixelshiftx = shift_x, pixelshifty = shift_y, duration = shake_duration) - return + owner.Stun(AUGGED_CHEST_EMP_STUN_TIME / severity) + owner.adjust_jitter(AUGGED_CHEST_EMP_SHAKE_TIME / severity) /obj/item/bodypart/chest/robot/get_cell() return cell @@ -436,22 +423,14 @@ if(limb_id == BODYPART_ID_ROBOTIC) . += should_draw_greyscale ? icon_greyscale : icon_static -#define EMP_GLITCH "EMP_GLITCH" /obj/item/bodypart/head/robot/emp_effect(severity, protection) . = ..() if(!. || isnull(owner)) return - - to_chat(owner, span_danger("Your [plaintext_zone]'s optical transponders glitch out and malfunction!")) - - var/glitch_duration = AUGGED_HEAD_EMP_GLITCH_DURATION - if (severity == EMP_HEAVY) - glitch_duration *= 2 - - QDEL_IN(owner.add_client_colour(/datum/client_colour/malfunction, HEAD_TRAIT), glitch_duration) - -#undef EMP_GLITCH + to_chat(owner, span_danger("Your [plaintext_zone] hurts...")) + QDEL_IN(owner.add_client_colour(/datum/client_colour/malfunction, HEAD_TRAIT), (AUGGED_HEAD_EMP_GLITCH_DURATION / severity)) + owner.adjust_confusion(AUGGED_HEAD_EMP_GLITCH_DURATION / severity) /obj/item/bodypart/head/robot/Exited(atom/movable/gone, direction) . = ..() diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm index b684fa435f69..bff640f9f5a2 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm @@ -160,16 +160,16 @@ return if(reviving) - revive_cost += 200 + revive_cost += 200 / severity else - reviver_cooldown += 20 SECONDS + reviver_cooldown += 20 SECONDS / severity if(ishuman(owner)) var/mob/living/carbon/human/human_owner = owner if(human_owner.stat != DEAD && prob(50 / severity) && human_owner.can_heartattack()) human_owner.set_heartattack(TRUE) to_chat(human_owner, span_userdanger("You feel a horrible agony in your chest!")) - addtimer(CALLBACK(src, PROC_REF(undo_heart_attack)), 600 / severity) + addtimer(CALLBACK(src, PROC_REF(undo_heart_attack)), 50 SECONDS / severity) /obj/item/organ/cyberimp/chest/reviver/proc/undo_heart_attack() var/mob/living/carbon/human/human_owner = owner diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm index 2d04b6d1e438..953a232a2fb0 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm @@ -135,7 +135,7 @@ . = ..() if(!owner || . & EMP_PROTECT_SELF) return - var/range = severity ? 10 : 5 + var/range = 10 / severity var/atom/throw_target if(active) release_items() @@ -240,7 +240,7 @@ if((organ_flags & ORGAN_FAILING) || . & EMP_PROTECT_SELF) return organ_flags |= ORGAN_FAILING - addtimer(CALLBACK(src, PROC_REF(reboot)), 90 / severity) + addtimer(CALLBACK(src, PROC_REF(reboot)), 9 SECONDS / severity) /obj/item/organ/cyberimp/brain/anti_stun/proc/reboot() organ_flags &= ~ORGAN_FAILING diff --git a/code/modules/surgery/organs/internal/eyes/eyes_augments.dm b/code/modules/surgery/organs/internal/eyes/eyes_augments.dm index 84647ea102d1..a64bcf5b40e4 100644 --- a/code/modules/surgery/organs/internal/eyes/eyes_augments.dm +++ b/code/modules/surgery/organs/internal/eyes/eyes_augments.dm @@ -12,7 +12,7 @@ . = ..() if((. & EMP_PROTECT_SELF) || !owner) return - if(prob(10 * severity)) + if(prob(100 / severity)) return to_chat(owner, span_warning("Static obfuscates your vision!")) owner.flash_act(visual = 1) diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index ea4fdf345169..3c62deb5188c 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -231,14 +231,14 @@ var/owner_needs_us = owner?.needs_heart() if(owner_needs_us && !COOLDOWN_FINISHED(src, severe_cooldown)) //So we cant just spam emp to kill people. - owner.set_dizzy_if_lower(20 SECONDS) + owner.set_dizzy_if_lower(15 SECONDS / severity) owner.losebreath += 10 - COOLDOWN_START(src, severe_cooldown, 20 SECONDS) + COOLDOWN_START(src, severe_cooldown, 15 SECONDS) if(prob(emp_vulnerability/severity)) //Chance of permanent effects organ_flags |= ORGAN_EMP //Starts organ faliure - gonna need replacing soon. Stop() - addtimer(CALLBACK(src, PROC_REF(Restart)), 10 SECONDS) + addtimer(CALLBACK(src, PROC_REF(Restart)), 10 SECONDS / severity) if(owner_needs_us) owner.visible_message( span_danger("[owner] clutches at [owner.p_their()] chest as if [owner.p_their()] heart is stopping!"), diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm index 1c70956e7f40..2f6224b6e249 100644 --- a/code/modules/surgery/organs/internal/liver/_liver.dm +++ b/code/modules/surgery/organs/internal/liver/_liver.dm @@ -254,7 +254,7 @@ if(. & EMP_PROTECT_SELF) return if(!COOLDOWN_FINISHED(src, severe_cooldown)) //So we cant just spam emp to kill people. - owner.adjust_tox_loss(10) + owner.adjust_tox_loss(10 / severity) COOLDOWN_START(src, severe_cooldown, 10 SECONDS) if(prob(emp_vulnerability/severity)) //Chance of permanent effects organ_flags |= ORGAN_EMP //Starts organ faliure - gonna need replacing soon. diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm index 250408967440..eb1823a04515 100644 --- a/code/modules/surgery/organs/internal/lungs/_lungs.dm +++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm @@ -951,7 +951,7 @@ if(. & EMP_PROTECT_SELF) return if(!COOLDOWN_FINISHED(src, severe_cooldown)) //So we cant just spam emp to kill people. - owner.losebreath += 20 + owner.losebreath += (20 / severity) COOLDOWN_START(src, severe_cooldown, 30 SECONDS) if(prob(emp_vulnerability/severity)) //Chance of permanent effects organ_flags |= ORGAN_EMP //Starts organ faliure - gonna need replacing soon. diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm index 347a31e8b756..f2c375f9551b 100644 --- a/code/modules/surgery/organs/internal/stomach/_stomach.dm +++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm @@ -511,7 +511,8 @@ if(. & EMP_PROTECT_SELF) return if(!COOLDOWN_FINISHED(src, severe_cooldown)) //So we cant just spam emp to kill people. - owner.vomit(vomit_flags = (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM)) + if(prob(100/severity)) + owner.vomit(vomit_flags = (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM)) COOLDOWN_START(src, severe_cooldown, 10 SECONDS) if(prob(emp_vulnerability/severity)) //Chance of permanent effects organ_flags |= ORGAN_EMP //Starts organ faliure - gonna need replacing soon. From cb7d0ff6b65106c87e06d99f013baf26fa7c36f1 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 04:25:18 +0000 Subject: [PATCH 375/445] Automatic changelog for PR #97062 [ci skip] --- html/changelogs/AutoChangeLog-pr-97062.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97062.yml diff --git a/html/changelogs/AutoChangeLog-pr-97062.yml b/html/changelogs/AutoChangeLog-pr-97062.yml new file mode 100644 index 000000000000..72ad9eebe6b6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97062.yml @@ -0,0 +1,5 @@ +author: "Fghj240" +delete-after: True +changes: + - balance: "robotic limbs, organs, and cybernetic implants have their emp effects scale more closely with emp severity." + - balance: "reactive tesla armor, adamantine armor, the mod emp shield module, and tinfoil hats slightly lower the severity of incoming EMPs." \ No newline at end of file From 604a120c31a8521d3d76c5fec8db6e08030c9aa7 Mon Sep 17 00:00:00 2001 From: Roxy <75404941+TealSeer@users.noreply.github.com> Date: Sat, 1 Aug 2026 00:58:24 -0400 Subject: [PATCH 376/445] Juke dm target only recompiles if define arguments have changed (#97273) ## About The Pull Request #96809 added a check in the dm target to make sure a rebuild is forced if explicit define arguments are passed, but this means it sometimes rebuilds when there's no reason to i.e. you're running with the same arguments as last compile. This change makes the build script store the arguments in a json file so it can check during subsequent runs and only rebuild if the arguments changed. ## Why It's Good For The Game One of the benefits of a build system is avoiding unnecessary rebuilds ## Changelog N/A --- tools/build/build.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tools/build/build.ts b/tools/build/build.ts index bc0c756c67a4..948631805d43 100644 --- a/tools/build/build.ts +++ b/tools/build/build.ts @@ -44,6 +44,21 @@ function getCutterPath() { const cutter_path = getCutterPath(); +const define_params_file = 'data/last_define_params.json' + +// Have compilation defines changed since last build? +async function defineParametersChanged(defines: string[]): Promise { + const defines_string = JSON.stringify(defines); + const params_file = Bun.file(define_params_file); + if(!await params_file.exists()) { + await params_file.write(defines_string); + return true; + } + const last_params = await params_file.text(); + await params_file.write(defines_string); + return last_params !== defines_string; +} + export const DefineParameter = new Juke.Parameter({ type: 'string[]', alias: 'D', @@ -202,9 +217,9 @@ export const DmTarget = new Juke.Target({ `${DME_NAME}.dme`, NamedVersionFile, ], - outputs: ({ get }) => { - if (get(DmVersionParameter) || get(DefineParameter).length > 0) { - // Always rebuild when a dm version or explicit CLI defines are provided to ensure juke re-runs + outputs: async ({ get }) => { + if (get(DmVersionParameter) || await defineParametersChanged(get(DefineParameter))) { + // Always rebuild when a dm version is provided or CLI defines have changed from last run return []; } return [`${DME_NAME}.dmb`, `${DME_NAME}.rsc`]; From d031a362cf109cfb5aa8a5efe068eee841d5c8ec Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 06:15:35 +0000 Subject: [PATCH 377/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97062.yml | 5 ----- html/changelogs/AutoChangeLog-pr-97209.yml | 4 ---- html/changelogs/archive/2026-08.yml | 8 ++++++++ 3 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97062.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97209.yml diff --git a/html/changelogs/AutoChangeLog-pr-97062.yml b/html/changelogs/AutoChangeLog-pr-97062.yml deleted file mode 100644 index 72ad9eebe6b6..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97062.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Fghj240" -delete-after: True -changes: - - balance: "robotic limbs, organs, and cybernetic implants have their emp effects scale more closely with emp severity." - - balance: "reactive tesla armor, adamantine armor, the mod emp shield module, and tinfoil hats slightly lower the severity of incoming EMPs." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97209.yml b/html/changelogs/AutoChangeLog-pr-97209.yml deleted file mode 100644 index fd09973be4de..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97209.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Thunder12345" -delete-after: True -changes: - - rscadd: "The quantum console now tells bitrunners what grade they've reached on completed domains." \ No newline at end of file diff --git a/html/changelogs/archive/2026-08.yml b/html/changelogs/archive/2026-08.yml index d7abce9f8e0d..353b81980df9 100644 --- a/html/changelogs/archive/2026-08.yml +++ b/html/changelogs/archive/2026-08.yml @@ -1,3 +1,11 @@ 2026-08-01: + Fghj240: + - balance: robotic limbs, organs, and cybernetic implants have their emp effects + scale more closely with emp severity. + - balance: reactive tesla armor, adamantine armor, the mod emp shield module, and + tinfoil hats slightly lower the severity of incoming EMPs. QuiteLiterallyAnything: - bugfix: Fixed Catwalk's upper atmospherics room being exposed to space by default. + Thunder12345: + - rscadd: The quantum console now tells bitrunners what grade they've reached on + completed domains. From fcb9f26607f6b94485c6eb2a38e2bf091eaf438b Mon Sep 17 00:00:00 2001 From: Chen Marisa <18307183+flleeppyy@users.noreply.github.com> Date: Sat, 1 Aug 2026 06:52:04 -0700 Subject: [PATCH 378/445] Fixes stuff in the experimental cloner (obligatory bald alert) (#97265) ## About The Pull Request Fix for baldness that i noticed while going through code. I'm unsure if there's more species that can be bald (I haven't played TG in a hot minute) but this at least fixes the check. image Also added some Destroy cleanup, and fixed the cloner not telling you who you are. Message no where to be seen image And now its there. image ## Why It's Good For The Game fuckups can now be bald in the experimental cloner. ## Changelog :cl: fix: the experimental cloner now actually can make you bald (if you're a human or ethereal). fix: the experimental cloner now spits flavor and policy text (if there is any) when spawning /:cl: --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- .../experimental_cloner_computer.dm | 6 +++++- .../experimental_cloner/experimental_cloner_fuckup.dm | 10 ++++++++-- .../experimental_cloner/experimental_cloner_pod.dm | 9 +++++++-- .../experimental_cloner/experimental_cloner_scanner.dm | 4 ++++ 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/code/game/machinery/experimental_cloner/experimental_cloner_computer.dm b/code/game/machinery/experimental_cloner/experimental_cloner_computer.dm index 2fc3ea73eda2..7709dc966935 100644 --- a/code/game/machinery/experimental_cloner/experimental_cloner_computer.dm +++ b/code/game/machinery/experimental_cloner/experimental_cloner_computer.dm @@ -21,6 +21,10 @@ . = ..() find_connections = mapload +/obj/machinery/computer/experimental_cloner/Destroy(force) + QDEL_NULL(stored_record) + return ..() + /obj/machinery/computer/experimental_cloner/post_machine_initialize() . = ..() if (find_connections) @@ -110,7 +114,7 @@ data["is_cloning"] = !!output?.running data["cloning_name"] = output?.loaded_record?.name data["cloning_species"] = output?.loaded_record?.dna?.species?.name - data["cloning_progress"] = output? ((output.cloning_time - cloning_time_left) / output.cloning_time) * 100 : 0 + data["cloning_progress"] = output?.cloning_time > 0 ? ((output.cloning_time - cloning_time_left) / output.cloning_time) * 100 : 0 return data diff --git a/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm b/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm index f9f612716a4b..acaf452f748c 100644 --- a/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm +++ b/code/game/machinery/experimental_cloner/experimental_cloner_fuckup.dm @@ -45,8 +45,14 @@ victim.set_facial_hairstyle("Shaved", update = FALSE) victim.set_hairstyle("Bald", update = TRUE) -/datum/experimental_cloner_fuckup/bald/is_valid(species_type) - return !is_path_in_list(/datum/species/human, /datum/species/ethereal) +/datum/experimental_cloner_fuckup/bald/is_valid(datum/species/species_type) + if(!ispath(species_type)) + return + var/datum/species/proto_species = GLOB.species_prototypes[species_type] + var/obj/item/bodypart/head/fucked_up_head = proto_species.bodypart_overrides[BODY_ZONE_HEAD] + var/head_flags = initial(fucked_up_head.head_flags) + + return (head_flags & HEAD_HAIR || head_flags & HEAD_FACIAL_HAIR) /// Give a brain trauma or two /datum/experimental_cloner_fuckup/brain_trauma diff --git a/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm b/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm index 617d013fab34..70c89890151f 100644 --- a/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm +++ b/code/game/machinery/experimental_cloner/experimental_cloner_pod.dm @@ -31,6 +31,11 @@ GLOBAL_VAR_INIT(experimental_cloner_fuckup_chance, 50) . = ..() sound_loop = new(src, FALSE) +/obj/machinery/experimental_cloner/Destroy(force) + QDEL_NULL(loaded_record) + QDEL_NULL(sound_loop) + return ..() + /obj/machinery/experimental_cloner/power_change() . = ..() if (machine_stat & NOPOWER && running) @@ -126,10 +131,10 @@ GLOBAL_VAR_INIT(experimental_cloner_fuckup_chance, 50) chosen_one.log_message("took control of experimental clone of [result].", LOG_GAME) result.PossessByPlayer(chosen_one.ckey) - to_chat(chosen_one, span_boldnotice("You are [loaded_record.name]! You aren't quite sure where you are or how you got here, though.")) + to_chat(result, span_boldnotice("You are [loaded_record.name]! You aren't quite sure where you are or how you got here, though.")) var/policy = get_policy(ROLE_EXPERIMENTAL_CLONER) if (policy) - to_chat(chosen_one, span_notice(policy)) + to_chat(result, span_notice(policy)) UnregisterSignal(result, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING, COMSIG_LIVING_DEATH)) result.forceMove(drop_location()) diff --git a/code/game/machinery/experimental_cloner/experimental_cloner_scanner.dm b/code/game/machinery/experimental_cloner/experimental_cloner_scanner.dm index a5f8211239ac..335bd4e98d0f 100644 --- a/code/game/machinery/experimental_cloner/experimental_cloner_scanner.dm +++ b/code/game/machinery/experimental_cloner/experimental_cloner_scanner.dm @@ -31,6 +31,10 @@ . = ..() soundloop = new (src) +/obj/machinery/experimental_cloner_scanner/Destroy(force) + QDEL_NULL(soundloop) + return ..() + /// Scan the occupant, eventually producing a [/datum/experimental_cloning_record]. Returns FALSE if unsuccessful. /obj/machinery/experimental_cloner_scanner/proc/start_scan() if (machine_stat & BROKEN || machine_stat & NOPOWER || isnull(occupant)) From bd0b35712bc504faa82ed750b1fb97639783b6b8 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:52:24 +0000 Subject: [PATCH 379/445] Automatic changelog for PR #97265 [ci skip] --- html/changelogs/AutoChangeLog-pr-97265.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97265.yml diff --git a/html/changelogs/AutoChangeLog-pr-97265.yml b/html/changelogs/AutoChangeLog-pr-97265.yml new file mode 100644 index 000000000000..b29f69b17cca --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97265.yml @@ -0,0 +1,5 @@ +author: "flleeppyy" +delete-after: True +changes: + - bugfix: "the experimental cloner now actually can make you bald (if you're a human or ethereal)." + - bugfix: "the experimental cloner now spits flavor and policy text (if there is any) when spawning" \ No newline at end of file From 6021040ecf4ab56ac6d426dab01235265bf9f678 Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:51:47 +0200 Subject: [PATCH 380/445] Replaces a bunch of is_species() checks for non-roundstart species with less hardcoded alternatives. (#96945) ## About The Pull Request Dehardcoding species-related code. It may not be internal to the species datum, but it's just as problematic and important if we move away from hardcoded species features. So far this just tackles the tip of the iceberg. Roundstart races (and jelly/slimepeople) will be handled in a different PR. `ismonkey()` will get its own PR, as it alone is found in over 40 files. Then I'll look around for remaining `is_species()` proc calls that aren't part of the `is_helpers` macros. ## Why It's Good For The Game More prework for future PRs. --- code/__DEFINES/bodyparts.dm | 35 ++++++++ code/__DEFINES/construction/material.dm | 2 +- code/__DEFINES/is_helpers.dm | 8 +- code/__DEFINES/traits/declarations.dm | 20 +++++ code/__DEFINES/wounds.dm | 33 -------- code/_globalvars/traits/_traits.dm | 9 ++ code/_globalvars/traits/admin_tooling.dm | 4 + code/datums/components/fearful/fearful.dm | 2 +- code/datums/components/ghostrole_on_revive.dm | 2 +- code/datums/elements/spooky.dm | 44 +++++----- code/datums/materials/basemats.dm | 4 +- code/datums/materials/properties/optional.dm | 22 ++--- .../mood_events/generic_negative_events.dm | 8 +- code/datums/mutations/body.dm | 7 -- code/datums/quirks/negative_quirks/addict.dm | 2 +- .../datums/quirks/negative_quirks/allergic.dm | 2 +- .../machinery/computer/camera_advanced.dm | 2 +- .../dna_infuser/organ_sets/fly_organs.dm | 1 + .../objects/effects/decals/cleanable/mess.dm | 10 +-- .../items/devices/scanners/health_analyzer.dm | 2 +- .../stacks/golem_food/golem_food_buff.dm | 5 +- .../stacks/golem_food/golem_status_effects.dm | 82 +++++++++++++------ code/game/objects/structures/spawner.dm | 16 ++-- .../abductor/equipment/gear/abductor_items.dm | 4 +- .../equipment/gear/abductor_posters.dm | 2 +- .../antagonists/abductor/machinery/console.dm | 2 +- .../abductor/machinery/dispenser.dm | 2 +- .../abductor/machinery/experiment.dm | 6 +- .../antagonists/nightmare/nightmare_organs.dm | 3 +- code/modules/client/verbs/typing.dm | 2 +- .../experisci/experiment/experiments.dm | 16 +++- .../boulder_processing/_boulder_processing.dm | 65 ++++++++++++--- .../mining/boulder_processing/refinery.dm | 4 +- code/modules/mob/dead/observer/observer.dm | 4 +- .../mob/living/basic/farm_animals/cow/_cow.dm | 1 + .../lavaland/gutlunchers/gutlunchers_ai.dm | 5 +- .../mob/living/basic/lavaland/mining.dm | 2 +- .../living/basic/ruin_defender/skeleton.dm | 2 +- .../mob/living/basic/space_fauna/ghost.dm | 1 + .../basic/space_fauna/revenant/_revenant.dm | 10 ++- code/modules/mob/living/brain/brain_item.dm | 18 +++- code/modules/mob/living/carbon/carbon.dm | 15 ++-- .../carbon/human/species_types/ghost.dm | 1 + .../carbon/human/species_types/podpeople.dm | 1 + .../carbon/human/species_types/vampire.dm | 2 + code/modules/mob/living/carbon/life.dm | 2 +- code/modules/mob/living/life.dm | 4 +- .../hostile/megafauna/colossus.dm | 2 +- code/modules/photography/photos/photo.dm | 2 +- .../reagents/chemistry/holder/mob_life.dm | 7 +- .../chemistry/reagents/food_reagents.dm | 14 ++-- .../religion/dreams/banish_nightmare.dm | 4 +- .../religion/dreams/deaconize_dreamer.dm | 2 +- .../spells/spell_types/self/ghostliness.dm | 6 +- code/modules/surgery/bodyparts/_bodyparts.dm | 2 + .../bodyparts/species_parts/misc_bodyparts.dm | 13 +-- .../operations/operation_plastic_surgery.dm | 2 +- .../surgery/organs/internal/liver/_liver.dm | 2 +- .../organs/internal/liver/liver_golem.dm | 2 +- .../organs/internal/liver/liver_plasmaman.dm | 2 +- .../organs/internal/liver/liver_skeleton.dm | 2 +- .../surgery/organs/internal/tongue/_tongue.dm | 1 + 62 files changed, 344 insertions(+), 215 deletions(-) diff --git a/code/__DEFINES/bodyparts.dm b/code/__DEFINES/bodyparts.dm index dcad804ed436..68277dad8464 100644 --- a/code/__DEFINES/bodyparts.dm +++ b/code/__DEFINES/bodyparts.dm @@ -163,6 +163,41 @@ DEFINE_BITFIELD(surgery_state, list( /// Surgery states flipped on automatically if the bodypart lacks vessels #define VESSELLESS_SURGERY_STATES (SURGERY_VESSELS_CLAMPED|SURGERY_ORGANS_CUT) +// ~biology defines +// What kind of biology a limb has, and what wounds it can suffer +/// Has absolutely fucking nothing, no wounds +#define BIO_INORGANIC NONE +/// Has bone - allows the victim to suffer T2-T3 bone blunt wounds +#define BIO_BONE (1<<0) +/// Has flesh - allows the victim to suffer fleshy slash pierce and burn wounds +#define BIO_FLESH (1<<1) +/// Has metal - allows the victim to suffer robotic blunt and burn wounds +#define BIO_METAL (1<<2) +/// Has wood - should probably be able to catch on fire, or something +#define BIO_WOOD (1<<3) +/// Is wired internally - allows the victim to suffer electrical wounds (robotic T1-T3 slash/pierce) +#define BIO_WIRED (1<<4) +/// Has bloodflow - can suffer bleeding wounds and can bleed +#define BIO_BLOODED (1<<5) +/// Is connected by a joint - can suffer T1 bone blunt wounds (dislocation) +#define BIO_JOINTED (1<<6) +/// Skin is covered in thick chitin and is resistant to cutting +#define BIO_CHITIN (1<<7) +/// the bodypart is made of rocks. +#define BIO_STONE (1<<8) +/// Limb is comprised entirely of slime jelly - can be cannibalized by any jelly person with the ability to eat their own limbs to replenish their own slime (blood) +#define BIO_JELLY (1<<9) +/// Robotic - can suffer all metal/wired wounds, such as: UNIMPLEMENTED PLEASE UPDATE ONCE SYNTH WOUNDS 9/5/2023 ~Niko +#define BIO_ROBOTIC (BIO_METAL|BIO_WIRED) +/// Has flesh and bone - See BIO_BONE and BIO_FLESH +#define BIO_FLESH_BONE (BIO_BONE|BIO_FLESH) +/// Standard humanoid - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt, except dislocations. Think human heads/chests +#define BIO_STANDARD_UNJOINTED (BIO_FLESH_BONE|BIO_BLOODED) +/// Standard humanoid limbs - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt. Can also bleed, and be dislocated. Think human arms and legs +#define BIO_STANDARD_JOINTED (BIO_STANDARD_UNJOINTED|BIO_JOINTED) +/// Xenomorph limbs (xenos are immune to wounds anyhow) +#define BIO_STANDARD_ALIEN (BIO_CHITIN|BIO_BONE|BIO_BLOODED|BIO_JOINTED) + /// Biological state that has some kind of skin that can be cut. #define BIOSTATE_HAS_SKIN (BIO_FLESH|BIO_METAL|BIO_CHITIN) /// Checks if a bodypart lacks both flesh and metal, meaning it has no skin to cut. diff --git a/code/__DEFINES/construction/material.dm b/code/__DEFINES/construction/material.dm index 413de505765d..61d37f0bc7a9 100644 --- a/code/__DEFINES/construction/material.dm +++ b/code/__DEFINES/construction/material.dm @@ -75,7 +75,7 @@ GLOBAL_LIST_INIT(material_flags_to_string, alist( #define MATERIAL_FLAMMABILITY "flammability" #define MATERIAL_RADIOACTIVITY "radioactivity" #define MATERIAL_FIRESTACKER "firestacker" -#define MATERIAL_VAMPIRES_BANE "vampires_bane" +#define MATERIAL_UNHOLY_BANE "unholy_bane" #define MATERIAL_TELEPORTING "teleporting" #define MATERIAL_PENETRATING "penetrating" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 9e397e697ac2..21bd14beee60 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -126,7 +126,12 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define isdummy(A) (istype(A, /mob/living/carbon/human/dummy)) -//Human sub-species +/** + * Human sub-species checks. + * NOTE: our long term objective is to move away from hardcoded species checks in favor of traits, components, organs and bodyparts! + * It's more reusable and modulable code overall. If you think you're better off using traits etc. in place of an is_species check, + * then do just that. + */ #define isabductor(A) (is_species(A, /datum/species/abductor)) #define isspirit(A) (is_species(A, /datum/species/spirit)) #define isgolem(A) (is_species(A, /datum/species/golem)) @@ -148,7 +153,6 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define isandroid(A) (is_species(A, /datum/species/android)) #define isnightmare(A) (is_species(A, /datum/species/shadow/nightmare)) - //More carbon mobs #define isalien(A) (istype(A, /mob/living/carbon/alien)) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index e79646192b15..1fd81be11a86 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1683,6 +1683,26 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Mobs with this trait will always have be a priority target for mining mobs #define TRAIT_MINING_AGGRO "mining_aggro" +/// Don't show the thinking indicator and therefore the typing indicator when we are about to type something... +#define TRAIT_HIDE_THINKING_INDICATOR "hide_thinking_indicator" + +// species-specific traits, used in place of deprecable is_species() checks. Feel free to use them for other things as well +#define TRAIT_NIGHTMARISH "nightmarish" +/// Allow the mob to utilize most but not all abductor equipment. Complemented by TRAIT_ABDUCTOR_KNOWLEDGE for the antagonists. +#define TRAIT_ABDUCTOR_KNOWLEDGE "abductor_knowledge" +/// Allow the mob to be instantly scanned at a distance with the abductor scanner gizmo. FOr abductors and cows. +#define TRAIT_ABDUCTOR_QUICK_SCAN "abductor_quick_scan" +/// The creature is weak to silver (or any material with the unholy bane property). Namely for vampires. +#define TRAIT_UNHOLY_BANEABLE "unholy_baneable" +/// Allows the mob to use the spooky instruments that turn people into spooky scary skeletons, while being immune to them +#define TRAIT_SPOOKY_INSTRUMENT_PLAYER "spooky_instrument_player" +/// Allows flypeople to re-ingest what has come out of their mouth. +#define TRAIT_VOMIT_SLURPER "vomit_slurper" +/// It looks, sounds and smells like a ghost. +#define TRAIT_GHOSTLY_MOB "ghostly_mob" +/// It requires an advanced health analyzer to be health-scanned. +#define TRAIT_REQUIRED_ADV_HEALTH_SCANNER "require_adv_health_scanner" + /// Allows the wearer to cast spells that require robes and hats #define TRAIT_CASTING_CLOTHING "casting_clothing" diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm index 36f4a65da1f1..c598c36582e7 100644 --- a/code/__DEFINES/wounds.dm +++ b/code/__DEFINES/wounds.dm @@ -70,39 +70,6 @@ GLOBAL_LIST_INIT(wound_severities_chronological, list( /// Wounds using this competition mode will remove any wounds of a lower severity than itself in a random wound roll. Used for ensuring the worse case scenario of a given injury_roll. #define WOUND_COMPETITION_OVERPOWER_LESSERS "wound_dominate" -// ~biology defines -// What kind of biology a limb has, and what wounds it can suffer -/// Has absolutely fucking nothing, no wounds -#define BIO_INORGANIC NONE -/// Has bone - allows the victim to suffer T2-T3 bone blunt wounds -#define BIO_BONE (1<<0) -/// Has flesh - allows the victim to suffer fleshy slash pierce and burn wounds -#define BIO_FLESH (1<<1) -/// Has metal - allows the victim to suffer robotic blunt and burn wounds -#define BIO_METAL (1<<2) -/// Has wood - should probably be able to catch on fire, or something -#define BIO_WOOD (1<<3) -/// Is wired internally - allows the victim to suffer electrical wounds (robotic T1-T3 slash/pierce) -#define BIO_WIRED (1<<4) -/// Has bloodflow - can suffer bleeding wounds and can bleed -#define BIO_BLOODED (1<<5) -/// Is connected by a joint - can suffer T1 bone blunt wounds (dislocation) -#define BIO_JOINTED (1<<6) -/// Skin is covered in thick chitin and is resistant to cutting -#define BIO_CHITIN (1<<7) -/// Limb is comprised entirely of slime jelly - can be cannibalized by any jelly person with the ability to eat their own limbs to replenish their own slime (blood) -#define BIO_JELLY (1<<8) -/// Robotic - can suffer all metal/wired wounds, such as: UNIMPLEMENTED PLEASE UPDATE ONCE SYNTH WOUNDS 9/5/2023 ~Niko -#define BIO_ROBOTIC (BIO_METAL|BIO_WIRED) -/// Has flesh and bone - See BIO_BONE and BIO_FLESH -#define BIO_FLESH_BONE (BIO_BONE|BIO_FLESH) -/// Standard humanoid - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt, except dislocations. Think human heads/chests -#define BIO_STANDARD_UNJOINTED (BIO_FLESH_BONE|BIO_BLOODED) -/// Standard humanoid limbs - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt. Can also bleed, and be dislocated. Think human arms and legs -#define BIO_STANDARD_JOINTED (BIO_STANDARD_UNJOINTED|BIO_JOINTED) -/// Xenomorph limbs (xenos are immune to wounds anyhow) -#define BIO_STANDARD_ALIEN (BIO_CHITIN|BIO_BONE|BIO_BLOODED|BIO_JOINTED) - // "Where" a specific biostate is within a given limb // Interior is hard shit, the last line, shit like bones // Exterior is soft shit, targeted by slashes and pierces (usually), protects exterior diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index ff9913e11df5..c33cc5b1c6c9 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -164,6 +164,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( ), /mob = list( "TRAIT_ABDUCTOR_HUD" = TRAIT_ABDUCTOR_HUD, + "TRAIT_ABDUCTOR_KNOWLEDGE" = TRAIT_ABDUCTOR_KNOWLEDGE, + "TRAIT_ABDUCTOR_QUICK_SCAN" = TRAIT_ABDUCTOR_QUICK_SCAN, "TRAIT_ABDUCTOR_SCIENTIST_TRAINING" = TRAIT_ABDUCTOR_SCIENTIST_TRAINING, "TRAIT_ABDUCTOR_TRAINING" = TRAIT_ABDUCTOR_TRAINING, "TRAIT_ACT_AS_CULTIST" = TRAIT_ACT_AS_CULTIST, @@ -322,6 +324,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_GAMERGOD" = TRAIT_GAMERGOD, "TRAIT_GARLIC_BREATH" = TRAIT_GARLIC_BREATH, "TRAIT_GENELESS" = TRAIT_GENELESS, + "TRAIT_GHOSTLY_MOB" = TRAIT_GHOSTLY_MOB, "TRAIT_GHOSTROLE_ON_REVIVE" = TRAIT_GHOSTROLE_ON_REVIVE, "TRAIT_GIANT" = TRAIT_GIANT, "TRAIT_GODMODE" = TRAIT_GODMODE, @@ -347,6 +350,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_HEAVY_SLEEPER" = TRAIT_HEAVY_SLEEPER, "TRAIT_HERETIC_SUMMON" = TRAIT_HERETIC_SUMMON, "TRAIT_HIDE_EXTERNAL_ORGANS" = TRAIT_HIDE_EXTERNAL_ORGANS, + "TRAIT_HIDE_THINKING_INDICATOR" = TRAIT_HIDE_THINKING_INDICATOR, "TRAIT_HIGH_VALUE_RANSOM" = TRAIT_HIGH_VALUE_RANSOM, "TRAIT_HOLY" = TRAIT_HOLY, "TRAIT_HOPELESSLY_ADDICTED" = TRAIT_HOPELESSLY_ADDICTED, @@ -422,6 +426,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NEVER_WOUNDED" = TRAIT_NEVER_WOUNDED, "TRAIT_NICE_SHOT" = TRAIT_NICE_SHOT, "TRAIT_NIGHT_VISION" = TRAIT_NIGHT_VISION, + "TRAIT_NIGHTMARISH" = TRAIT_NIGHTMARISH, "TRAIT_NOBLOOD" = TRAIT_NOBLOOD, "TRAIT_NOBREATH" = TRAIT_NOBREATH, "TRAIT_NOCRITDAMAGE" = TRAIT_NOCRITDAMAGE, @@ -519,6 +524,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_REGEN_SHIELD" = TRAIT_REGEN_SHIELD, "TRAIT_RELAYING_ATTACKER" = TRAIT_RELAYING_ATTACKER, "TRAIT_REMOTE_TASTING" = TRAIT_REMOTE_TASTING, + "TRAIT_REQUIRED_ADV_HEALTH_SCANNER" = TRAIT_REQUIRED_ADV_HEALTH_SCANNER, "TRAIT_RESEARCH_SCANNER" = TRAIT_RESEARCH_SCANNER, "TRAIT_RESISTCOLD" = TRAIT_RESISTCOLD, "TRAIT_RESISTHEAT" = TRAIT_RESISTHEAT, @@ -572,6 +578,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_SPIDER_CONSUMED" = TRAIT_SPIDER_CONSUMED, "TRAIT_SPIRITUAL" = TRAIT_SPIRITUAL, "TRAIT_SPLATTERCASTER" = TRAIT_SPLATTERCASTER, + "TRAIT_SPOOKY_INSTRUMENT_PLAYER" = TRAIT_SPOOKY_INSTRUMENT_PLAYER, "TRAIT_SPRAY_PAINTABLE" = TRAIT_SPRAY_PAINTABLE, "TRAIT_STABLEHEART" = TRAIT_STABLEHEART, "TRAIT_STABLELIVER" = TRAIT_STABLELIVER, @@ -617,6 +624,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_UNBREAKABLE" = TRAIT_UNBREAKABLE, "TRAIT_UNCONVERTABLE" = TRAIT_UNCONVERTABLE, "TRAIT_UNDENSE" = TRAIT_UNDENSE, + "TRAIT_UNHOLY_BANEABLE" = TRAIT_UNHOLY_BANEABLE, "TRAIT_UNHUSKABLE" = TRAIT_UNHUSKABLE, "TRAIT_UNINTELLIGIBLE_SPEECH" = TRAIT_UNINTELLIGIBLE_SPEECH, "TRAIT_UNKNOWN_APPEARANCE" = TRAIT_UNKNOWN_APPEARANCE, @@ -634,6 +642,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_VIRUSIMMUNE" = TRAIT_VIRUSIMMUNE, "TRAIT_VIRUS_RESISTANCE" = TRAIT_VIRUS_RESISTANCE, "TRAIT_VOICE_MATCHES_ID" = TRAIT_VOICE_MATCHES_ID, + "TRAIT_VOMIT_SLURPER" = TRAIT_VOMIT_SLURPER, "TRAIT_VORACIOUS" = TRAIT_VORACIOUS, "TRAIT_WAS_EVOLVED" = TRAIT_WAS_EVOLVED, "TRAIT_WATER_ADAPTATION" = TRAIT_WATER_ADAPTATION, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index ba5d03e2197e..79ef99a9949a 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -36,6 +36,8 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( ), /mob = list( "TRAIT_ABDUCTOR_HUD" = TRAIT_ABDUCTOR_HUD, + "TRAIT_ABDUCTOR_KNOWLEDGE" = TRAIT_ABDUCTOR_KNOWLEDGE, + "TRAIT_ABDUCTOR_QUICK_SCAN" = TRAIT_ABDUCTOR_QUICK_SCAN, "TRAIT_ABDUCTOR_SCIENTIST_TRAINING" = TRAIT_ABDUCTOR_SCIENTIST_TRAINING, "TRAIT_ABDUCTOR_TRAINING" = TRAIT_ABDUCTOR_TRAINING, "TRAIT_ACT_AS_CULTIST" = TRAIT_ACT_AS_CULTIST, @@ -296,6 +298,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_SOOTHED_HEADACHE" = TRAIT_SOOTHED_HEADACHE, "TRAIT_SPACEWALK" = TRAIT_SPACEWALK, "TRAIT_SPIRITUAL" = TRAIT_SPIRITUAL, + "TRAIT_SPOOKY_INSTRUMENT_PLAYER" = TRAIT_SPOOKY_INSTRUMENT_PLAYER, "TRAIT_STABLEHEART" = TRAIT_STABLEHEART, "TRAIT_STABLELIVER" = TRAIT_STABLELIVER, "TRAIT_STRENGTH" = TRAIT_STRENGTH, @@ -325,6 +328,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_UI_BLOCKED" = TRAIT_UI_BLOCKED, "TRAIT_UNCONVERTABLE" = TRAIT_UNCONVERTABLE, "TRAIT_UNDENSE" = TRAIT_UNDENSE, + "TRAIT_UNHOLY_BANEABLE" = TRAIT_UNHOLY_BANEABLE, "TRAIT_UNHUSKABLE" = TRAIT_UNHUSKABLE, "TRAIT_UNINTELLIGIBLE_SPEECH" = TRAIT_UNINTELLIGIBLE_SPEECH, "TRAIT_UNKNOWN_APPEARANCE" = TRAIT_UNKNOWN_APPEARANCE, diff --git a/code/datums/components/fearful/fearful.dm b/code/datums/components/fearful/fearful.dm index 9620c976c339..750e036cde5f 100644 --- a/code/datums/components/fearful/fearful.dm +++ b/code/datums/components/fearful/fearful.dm @@ -148,7 +148,7 @@ if(hugger == parent) return - if(isnightmare(hugger)) + if(HAS_TRAIT(hugger, TRAIT_NIGHTMARISH)) var/lit_tiles = 0 var/unlit_tiles = 0 diff --git a/code/datums/components/ghostrole_on_revive.dm b/code/datums/components/ghostrole_on_revive.dm index 83541bcafdc7..ddc63524cba0 100644 --- a/code/datums/components/ghostrole_on_revive.dm +++ b/code/datums/components/ghostrole_on_revive.dm @@ -174,7 +174,7 @@ UnregisterSignal(parent_mob, COMSIG_LIVING_GHOSTROLE_INFO) // Block formaldehyde from being metabolized, Coroner QoL -/datum/component/ghostrole_on_revive/proc/block_formaldehyde_metabolism(mob/living/source, datum/reagent/chem) +/datum/component/ghostrole_on_revive/proc/block_formaldehyde_metabolism(mob/living/source, datum/reagent/chem, seconds_per_tick, metabolization_ratio) SIGNAL_HANDLER if(istype(chem, /datum/reagent/toxin/formaldehyde)) diff --git a/code/datums/elements/spooky.dm b/code/datums/elements/spooky.dm index c88c7b540cd5..c58ed1631487 100644 --- a/code/datums/elements/spooky.dm +++ b/code/datums/elements/spooky.dm @@ -24,44 +24,42 @@ /datum/element/spooky/proc/spectral_attack(datum/source, mob/living/carbon/target, mob/user) SIGNAL_HANDLER - if(ishuman(user) && !isskeleton(user)) //this weapon wasn't meant for mortals. - var/mob/living/carbon/human/human_user = user - if(rattle_bones(human_user, stam_dam_mult = stam_damage_mult * 2)) - to_chat(human_user, span_userdanger("Your ears weren't meant for this spectral sound.")) - INVOKE_ASYNC(src, PROC_REF(spectral_change), human_user, user, source) + if(!HAS_TRAIT(user, TRAIT_SPOOKY_INSTRUMENT_PLAYER)) //this weapon wasn't meant for mortals. + if(rattle_bones(user, stam_dam_mult = stam_damage_mult * 2)) + to_chat(user, span_userdanger("Your ears weren't meant for this spectral sound.")) + if(ishuman(user)) + INVOKE_ASYNC(src, PROC_REF(spectral_change), user, user, source) return to_chat(target, span_userdanger("DOOT[allergy_string]
- make sure not to consume any of these!")) -/datum/quirk/item_quirk/allergic/proc/block_metab(mob/living/carbon/source, datum/reagent/chem, seconds_per_tick) +/datum/quirk/item_quirk/allergic/proc/block_metab(mob/living/carbon/source, datum/reagent/chem, seconds_per_tick, metabolization_ratio) SIGNAL_HANDLER if(!is_type_in_list(chem, allergies)) diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm index 18139f148078..01278ffbf96b 100644 --- a/code/game/machinery/computer/camera_advanced.dm +++ b/code/game/machinery/computer/camera_advanced.dm @@ -148,7 +148,7 @@ return can_interact(user) /obj/machinery/computer/camera_advanced/abductor/can_use(mob/user) - if(!isabductor(user)) + if(!HAS_MIND_TRAIT(user, TRAIT_ABDUCTOR_KNOWLEDGE)) return FALSE return ..() diff --git a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm index 1af4ad7f2ee3..a6b0bb4a3019 100644 --- a/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm +++ b/code/game/machinery/dna_infuser/organ_sets/fly_organs.dm @@ -57,6 +57,7 @@ toxic_foodtypes = NONE // these fucks eat vomit, i am sure they can handle drinking bleach or whatever too modifies_speech = TRUE languages_native = list(/datum/language/buzzwords) + organ_traits = list(TRAIT_VOMIT_SLURPER) var/static/list/speech_replacements = list( new /regex("z+", "g") = "zzz", new /regex("Z+", "g") = "ZZZ", diff --git a/code/game/objects/effects/decals/cleanable/mess.dm b/code/game/objects/effects/decals/cleanable/mess.dm index 6d91ed525888..2bb044396f71 100644 --- a/code/game/objects/effects/decals/cleanable/mess.dm +++ b/code/game/objects/effects/decals/cleanable/mess.dm @@ -197,15 +197,11 @@ /obj/effect/decal/cleanable/vomit/attack_hand(mob/user, list/modifiers) . = ..() - if(. || !ishuman(user)) - return - var/mob/living/carbon/human/as_human = user - var/obj/item/organ/tongue/user_tongue = user.get_organ_slot(ORGAN_SLOT_TONGUE) - if(!istype(user_tongue, /obj/item/organ/tongue/fly)) + if(. || !HAS_TRAIT(user, TRAIT_VOMIT_SLURPER)) return playsound(get_turf(src), 'sound/items/drink.ogg', 50, TRUE) //slurp - as_human.visible_message(span_alert("[as_human] extends a small proboscis into the vomit pool, sucking it with a slurping sound.")) - lazy_init_reagents()?.trans_to(as_human, reagents.total_volume, transferred_by = user, methods = INGEST) + user.visible_message(span_alert("[user] extends a small proboscis into the vomit pool, sucking it with a slurping sound.")) + lazy_init_reagents()?.trans_to(user, reagents.total_volume, transferred_by = user, methods = INGEST) qdel(src) /obj/effect/decal/cleanable/vomit/toxic // this has a more toned-down color palette, which may be why it's used as the default in so many spots diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index eda5892686a0..1bc9bc02d0df 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -90,7 +90,7 @@ last_scan_text = floor_text return - if(ispodperson(M) && scanpower < SCANPOWER_ADVANCED) + if(HAS_TRAIT(M, TRAIT_REQUIRED_ADV_HEALTH_SCANNER) && scanpower < SCANPOWER_ADVANCED) to_chat(user, span_info("[M]'s biological structure is too complex for the health analyzer.")) return diff --git a/code/game/objects/items/stacks/golem_food/golem_food_buff.dm b/code/game/objects/items/stacks/golem_food/golem_food_buff.dm index 4960a6dfb131..d1ebb858ddbf 100644 --- a/code/game/objects/items/stacks/golem_food/golem_food_buff.dm +++ b/code/game/objects/items/stacks/golem_food/golem_food_buff.dm @@ -14,7 +14,7 @@ if (!exclusive) return TRUE var/datum/status_effect/golem/existing = consumer.has_status_effect(/datum/status_effect/golem) - return !existing || istype(existing, status_effect) + return !existing || !existing.exclusive || istype(existing, status_effect) /// Called when someone actually eats this /datum/golem_food_buff/proc/on_consumption(mob/living/carbon/consumer, atom/movable/consumed, multiplier = 1) @@ -83,8 +83,9 @@ /datum/golem_food_buff/lightbulb nutrition = 0 exclusive = FALSE - status_effect = /datum/status_effect/golem_lightbulb + status_effect = /datum/status_effect/golem/lightbulb added_info = "Not nutritious, but gives you a healthy glow if eaten." + exclusive = FALSE /datum/golem_food_buff/gibtonite exclusive = FALSE diff --git a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm index 308d9b71ebac..87b50ce84eaf 100644 --- a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm +++ b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm @@ -20,6 +20,12 @@ var/alert_desc = "Something went wrong and this tooltip is not displaying correctly." /// If we are not a golem what color does the filter glow? var/filter_color = LIGHT_COLOR_DEFAULT + /// If TRUE, then this status effect is exclusive to other golem status effects and cannot be stacked with them. + var/exclusive = TRUE + /// For mobs with bodyparts, used to calculate the overall duration based on how many BIO_STONE bodyparts the mob has. + var/duration_mult_per_bodypart = 0.15 // 0.1 + (0.15 * 6 bodyparts) = 1 + /// The minimum duration if the mob has no BIO_STONE bodypart + var/minimum_duration_mult = 0.1 /atom/movable/screen/alert/status_effect/golem_status name = "Metamorphic %SOMETHING%" @@ -65,60 +71,83 @@ QDEL_NULL(mineral_overlay) return ..() +///If we find 3 or more golem parts, then we can say that the status effect overlays are noticeable enough, so we won't need the outline filter. +#define MAX_GOLEM_PARTS_FOUND_FOR_FILTER 2 + /datum/status_effect/golem/on_apply() . = ..() - if (!ishuman(owner)) - return FALSE - if (owner.has_status_effect(/datum/status_effect/golem)) + if (owner.has_status_effect(type)) //the initial id of the type will be checked. Make sure to override it if not exclusive return FALSE if (applied_fluff) to_chat(owner, span_notice(applied_fluff)) - if (!overlay_state_prefix || !iscarbon(owner)) - return TRUE - if(isgolem(owner)) - var/mob/living/carbon/golem_owner = owner - for (var/obj/item/bodypart/part in golem_owner.get_bodyparts()) + if (overlay_state_prefix) + var/golem_parts_found = 0 + for (var/obj/item/bodypart/part as anything in owner.get_bodyparts()) // these overlays won't look good on anything but golem limbs if (part.limb_id != SPECIES_GOLEM) continue var/datum/bodypart_overlay/simple/golem_overlay/overlay = new() overlay.add_to_bodypart(overlay_state_prefix, part) active_overlays += overlay - golem_owner.update_body_parts() - else + golem_parts_found++ + if(golem_parts_found) + var/mob/living/carbon/golem_owner = owner + golem_owner.update_body_parts() + if(golem_parts_found > MAX_GOLEM_PARTS_FOUND_FOR_FILTER) + return TRUE + + if(filter_color) owner.add_filter("[id]_filter", 2, outline_filter("color" = filter_color, "size" = 1.25)) var/the_filter = owner.get_filter("[id]_filter") animate(the_filter, alpha = 0) // start at 0 alpha animate(the_filter, alpha = 150, time = 7.5 SECONDS, loop = -1, easing = SINE_EASING) // fade in and out animate(alpha = 50, time = 7.5 SECONDS, loop = -1, easing = SINE_EASING) + return TRUE +#undef MAX_GOLEM_PARTS_FOUND_FOR_FILTER + /datum/status_effect/golem/on_creation(mob/living/new_owner, multiplier = 1) ///instead of straight out multiplying the duration, we use exponents to flatten the duration so it doesn't become exceedingly long for golems - var/exponent = 0.2 - if(!isgolem(new_owner)) - duration *= 0.1 - exponent = 0.5 //non-golem benefit more from higher multipliers since it normally only lasts for 30 seconds for them. + var/exponent = 0.5 + var/duration_mult = minimum_duration_mult + if(iscarbon(new_owner)) + //increase the duration for each stone bodypart, while reducing the benefit of higher multipliers in relation to the inscreased duration + duration_mult = 0.1 + for(var/obj/item/bodypart/part as anything in new_owner.get_bodyparts()) + if(part.biological_state & BIO_STONE) + duration_mult += duration_mult_per_bodypart // should be 1 at 6 stone-y bodyparts + exponent = max(0.2, exponent - 0.05) // 0.2 at 6 bodyparts + else if(new_owner.mob_biotypes & MOB_MINERAL) + exponent = 0.2 + duration_mult = 1 if(multiplier > 1) - duration *= multiplier ** exponent + duration *= (duration_mult * multiplier) ** exponent else // if the multiplier is lower than 1, don't bother using powers. - duration *= multiplier + duration *= duration_mult * multiplier var/buff_duration = duration . = ..() if (!.) return . var/atom/movable/screen/alert/status_effect/golem_status/status_alert = linked_alert status_alert?.update_details(buff_time = buff_duration) + RegisterSignal(new_owner, SIGNAL_REMOVETRAIT(TRAIT_ROCK_METAMORPHIC), PROC_REF(on_metamorphic_lost)) + +/datum/status_effect/golem/proc/on_metamorphic_lost() + SIGNAL_HANDLER + qdel(src) /datum/status_effect/golem/on_remove() - to_chat(owner, span_warning("The effect of the [mineral_name] fades.")) + if(mineral_name) + to_chat(owner, span_warning("The effect of the [mineral_name] fades.")) QDEL_LIST(active_overlays) owner.remove_filter("[id]_filter") return ..() /datum/status_effect/golem/get_examine_text() - return span_notice("[owner.p_Their()] body has been augmented with veins of [mineral_name].") + if(mineral_name) + return span_notice("[owner.p_Their()] body has been augmented with veins of [mineral_name].") /// Body part overlays applied by golem status effects /datum/bodypart_overlay/simple/golem_overlay @@ -468,23 +497,22 @@ #define LIGHTBULB_FILTER "filter_lightbulb_glow" -/// Lights up the golem, NOT using the golem subtype because it is not exclusive with other status effects -/datum/status_effect/golem_lightbulb +/datum/status_effect/golem/lightbulb id = "golem_lightbulb" status_type = STATUS_EFFECT_REFRESH duration = 2 MINUTES alert_type = null + overlay_state_prefix = null + filter_color = null + exclusive = FALSE + duration_mult_per_bodypart = 0.11 // 0.34 + (0.11 * 6 bodyparts) = 1 + minimum_duration_mult = 0.34 var/glow_range = 3 var/glow_power = 1 var/glow_color = LIGHT_COLOR_DEFAULT var/obj/effect/dummy/lighting_obj/moblight/lightbulb -/datum/status_effect/golem_lightbulb/on_creation(mob/living/new_owner, ...) - if(!isgolem(new_owner)) - duration *= 0.3 - return ..() - -/datum/status_effect/golem_lightbulb/on_apply() +/datum/status_effect/golem/lightbulb/on_apply() . = ..() if (!.) return @@ -492,7 +520,7 @@ lightbulb = owner.mob_light(glow_range, glow_power, glow_color) owner.add_filter(LIGHTBULB_FILTER, 2, list("type" = "outline", "color" = glow_color, "alpha" = 60, "size" = 1)) -/datum/status_effect/golem_lightbulb/on_remove() +/datum/status_effect/golem/lightbulb/on_remove() QDEL_NULL(lightbulb) owner.remove_filter(LIGHTBULB_FILTER) to_chat(owner, span_warning("Your glow fades.")) diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm index 78275ea70ef1..eda088547374 100644 --- a/code/game/objects/structures/spawner.dm +++ b/code/game/objects/structures/spawner.dm @@ -203,19 +203,15 @@ /obj/structure/spawner/nether/examine(mob/user) . = ..() - if(isskeleton(user) || iszombie(user)) - . += "A direct link to another dimension full of creatures very happy to see you. [span_nicegreen("You can see your house from here!")]" - else - . += "A direct link to another dimension full of creatures not very happy to see you. [span_warning("Entering the link would be a very bad idea.")]" + . += "A direct link to another dimension full of creatures not very happy to see you. [span_warning("Entering the link would be a very bad idea.")]" /obj/structure/spawner/nether/attack_hand(mob/user, list/modifiers) . = ..() - if(isskeleton(user) || iszombie(user)) - to_chat(user, span_notice("You don't feel like going home yet...")) - else - user.visible_message(span_warning("[user] is violently pulled into the link!"), \ - span_userdanger("Touching the portal, you are quickly pulled through into a world of unimaginable horror!")) - contents.Add(user) + user.visible_message( + span_warning("[user] is violently pulled into the link!"), + span_userdanger("Touching the portal, you are quickly pulled through into a world of unimaginable horror!") + ) + contents.Add(user) /obj/structure/spawner/nether/process(seconds_per_tick) for(var/mob/living/living_mob in contents) diff --git a/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm b/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm index 030fb27c60d3..3974fe35d600 100644 --- a/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm +++ b/code/modules/antagonists/abductor/equipment/gear/abductor_items.dm @@ -79,7 +79,7 @@ if(marked == target) to_chat(user, span_warning("This specimen is already marked!")) return - if(isabductor(target) || iscow(target)) + if(HAS_TRAIT(target, TRAIT_ABDUCTOR_QUICK_SCAN)) marked_target_weakref = WEAKREF(target) to_chat(user, span_notice("You mark [target] for future retrieval.")) else @@ -241,7 +241,7 @@ fail_message = span_abductor("Firing error, please contact Command.") /obj/item/firing_pin/abductor/pin_auth(mob/living/user) - . = isabductor(user) + return HAS_MIND_TRAIT(user, TRAIT_ABDUCTOR_KNOWLEDGE) /obj/item/gun/energy/alien name = "alien pistol" diff --git a/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm b/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm index 33224d543809..7360e32e7b3a 100644 --- a/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm +++ b/code/modules/antagonists/abductor/equipment/gear/abductor_posters.dm @@ -12,7 +12,7 @@ poster_item_icon_state = "rolled_abductor" /obj/structure/sign/poster/abductor/tear_poster(mob/user) - if(!isabductor(user)) + if(!HAS_MIND_TRAIT(user, TRAIT_ABDUCTOR_KNOWLEDGE)) balloon_alert(user, "it won't budge!") return return ..() diff --git a/code/modules/antagonists/abductor/machinery/console.dm b/code/modules/antagonists/abductor/machinery/console.dm index 88e8da2be45d..7f9fb9ca15f2 100644 --- a/code/modules/antagonists/abductor/machinery/console.dm +++ b/code/modules/antagonists/abductor/machinery/console.dm @@ -72,7 +72,7 @@ TeleporterSend() /obj/machinery/abductor/console/ui_status(mob/user, datum/ui_state/state) - if(!isabductor(user) && !isobserver(user)) + if(!HAS_MIND_TRAIT(user, TRAIT_ABDUCTOR_KNOWLEDGE) && !isobserver(user)) return UI_CLOSE return ..() diff --git a/code/modules/antagonists/abductor/machinery/dispenser.dm b/code/modules/antagonists/abductor/machinery/dispenser.dm index b4775b08ba82..a8e66ace494e 100644 --- a/code/modules/antagonists/abductor/machinery/dispenser.dm +++ b/code/modules/antagonists/abductor/machinery/dispenser.dm @@ -23,7 +23,7 @@ amounts[i] = rand(1,5) /obj/machinery/abductor/gland_dispenser/ui_status(mob/user, datum/ui_state/state) - if(!isabductor(user) && !isobserver(user)) + if(!HAS_MIND_TRAIT(user, TRAIT_ABDUCTOR_KNOWLEDGE) && !isobserver(user)) return UI_CLOSE return ..() diff --git a/code/modules/antagonists/abductor/machinery/experiment.dm b/code/modules/antagonists/abductor/machinery/experiment.dm index 9bc5f4a1819d..2cc52bed914c 100644 --- a/code/modules/antagonists/abductor/machinery/experiment.dm +++ b/code/modules/antagonists/abductor/machinery/experiment.dm @@ -24,7 +24,7 @@ return ..() /obj/machinery/abductor/experiment/mouse_drop_receive(mob/target, mob/user, params) - if(!ishuman(target) || isabductor(target)) + if(!ishuman(target) || HAS_MIND_TRAIT(target, TRAIT_ABDUCTOR_KNOWLEDGE)) return close_machine(target) @@ -33,8 +33,8 @@ ..() /obj/machinery/abductor/experiment/close_machine(mob/target, density_to_set = TRUE) - for(var/A in loc) - if(isabductor(A)) + for(var/mob/living/mob in loc) + if(HAS_MIND_TRAIT(mob, TRAIT_ABDUCTOR_KNOWLEDGE)) return if(state_open && !panel_open) ..(target) diff --git a/code/modules/antagonists/nightmare/nightmare_organs.dm b/code/modules/antagonists/nightmare/nightmare_organs.dm index 03ecc0eeb30d..4acf712ccd33 100644 --- a/code/modules/antagonists/nightmare/nightmare_organs.dm +++ b/code/modules/antagonists/nightmare/nightmare_organs.dm @@ -9,6 +9,7 @@ icon = 'icons/obj/medical/organs/organs.dmi' icon_state = "brain-x-d" shade_color = "black, somehow" + organ_traits = list(TRAIT_NIGHTMARISH) ///Our associated shadow jaunt spell, for all nightmares var/datum/action/cooldown/spell/jaunt/shadow_walk/our_jaunt @@ -161,7 +162,7 @@ respawn_progress = 0 /obj/item/organ/heart/nightmare/get_availability(datum/species/owner_species, mob/living/owner_mob) - if(isnightmare(owner_mob)) + if(HAS_TRAIT(owner_mob, TRAIT_NIGHTMARISH)) return TRUE return ..() diff --git a/code/modules/client/verbs/typing.dm b/code/modules/client/verbs/typing.dm index bef6d85a0482..7965a9b73a8f 100644 --- a/code/modules/client/verbs/typing.dm +++ b/code/modules/client/verbs/typing.dm @@ -33,7 +33,7 @@ if(!typing_indicators) return FALSE /// Special exemptions - if(isabductor(mob)) + if(HAS_MIND_TRAIT(mob, TRAIT_HIDE_THINKING_INDICATOR)) return FALSE ADD_TRAIT(mob, TRAIT_THINKING_IN_CHARACTER, CURRENTLY_TYPING_TRAIT) mob.create_thinking_indicator() diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index 271322b1a496..140ea10c941d 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -450,9 +450,7 @@ . = ..() if (!.) return - if (isandroid(check)) - return TRUE - if (length(check.organs) < 6 || length(check.get_missing_limbs()) > 1) + if (length(check.get_missing_limbs()) > 1) return FALSE var/static/list/augmented_organ_slots = list( @@ -463,11 +461,23 @@ ORGAN_SLOT_LIVER, ORGAN_SLOT_STOMACH, ) + + // some species don't need a stomach, lungs, heart or liver. This is the case for androids, which are fully augged humies with a few extra quirks. + var/organs_left_to_check = length(augmented_organ_slots) + for(var/skip_organ_trait in list(TRAIT_NOHUNGER, TRAIT_NOBREATH, TRAIT_NOBLOOD, TRAIT_LIVERLESS_METABOLISM)) + if(HAS_TRAIT(check, skip_organ_trait)) + organs_left_to_check-- + for (var/obj/item/organ/organ as anything in check.organs) if (!(organ.slot in augmented_organ_slots)) continue if (!IS_ROBOTIC_ORGAN(organ)) return FALSE + organs_left_to_check-- + + if(organs_left_to_check > 0) //we're missing some organs like ears, eyes, liver etc. + return FALSE + for (var/obj/item/bodypart/bodypart as anything in check.get_bodyparts()) if (!IS_ROBOTIC_LIMB(bodypart)) return FALSE diff --git a/code/modules/mining/boulder_processing/_boulder_processing.dm b/code/modules/mining/boulder_processing/_boulder_processing.dm index 324c616ae84e..f6ddcd820bee 100644 --- a/code/modules/mining/boulder_processing/_boulder_processing.dm +++ b/code/modules/mining/boulder_processing/_boulder_processing.dm @@ -124,8 +124,8 @@ return TRUE if(istype(mover, /obj/item/boulder)) return can_process_boulder(mover) - if(isgolem(mover)) - return can_process_golem(mover) + if(isliving(mover)) + return can_process_living_mob(mover) return ..() /** @@ -183,7 +183,7 @@ * * * [rockman][mob/living/carbon/human] - the golem we are trying to main */ -/obj/machinery/bouldertech/proc/can_process_golem(mob/living/carbon/human/rockman) +/obj/machinery/bouldertech/proc/can_process_living_mob(mob/living/rockman) PRIVATE_PROC(TRUE) SHOULD_BE_PURE(TRUE) @@ -195,11 +195,26 @@ if(!COOLDOWN_FINISHED(src, accept_cooldown)) return FALSE - //not processable - if(!istype(rockman) || QDELETED(rockman) || rockman.body_position != LYING_DOWN) + //not a mineral mob + if(!istype(rockman) || QDELETED(rockman)) return FALSE - return TRUE + var/list/bodyparts = rockman.get_bodyparts() + if(length(bodyparts)) //Has bodypart, check if any has the BIO_STONE state (we don't check MOB_MINERAL for carbon mobs cuz we don't want to gib plasmamen, just grind golem parts) + for(var/obj/item/bodypart/part as anything in bodyparts) + if(!(part.biological_state & BIO_STONE)) + return FALSE + else if(!(rockman.mob_biotypes & MOB_MINERAL)) + return FALSE + + //Only return true if they are lying down or are incapacitated. + if(rockman.mobility_flags & MOBILITY_LIEDOWN) + if(rockman.body_position == LYING_DOWN) + return TRUE + else if(rockman.incapacitated) + return TRUE + + return FALSE /** * Accepts a golem to be processed, mainly for memes @@ -207,28 +222,52 @@ * * * [rockman][mob/living/carbon/human] - the golem we are trying to main */ -/obj/machinery/bouldertech/proc/accept_golem(mob/living/carbon/human/rockman) +/obj/machinery/bouldertech/proc/accept_mob(mob/living/rockman) PRIVATE_PROC(TRUE) - if(!can_process_golem(rockman)) + if(!can_process_living_mob(rockman)) return if(!use_energy(active_power_usage * 1.5, force = FALSE)) say("Not enough energy!") return - maim_golem(rockman) + maim_mob(rockman) playsound(src, usage_sound, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) COOLDOWN_START(src, accept_cooldown, 3 SECONDS) /// What effects actually happens to a golem when it is "processed" -/obj/machinery/bouldertech/proc/maim_golem(mob/living/carbon/human/rockman) +/obj/machinery/bouldertech/proc/maim_mob(mob/living/rockman) PROTECTED_PROC(TRUE) Shake(duration = 1 SECONDS) + var/list/bodyparts = rockman.get_bodyparts() + var/gibbing = TRUE + if(length(bodyparts)) //Has bodypart, check if any has the BIO_STONE state and don't just gib them if you can. + var/list/deleted_part_names + gibbing = FALSE + for(var/obj/item/bodypart/part as anything in bodyparts) + if(!(part.biological_state & BIO_STONE)) + continue + if(part.body_zone == BODY_ZONE_CHEST) //Ok, so, without a chest, we're pretty much going to be gibbed anyway. + gibbing = TRUE + break + else + deleted_part_names += part.name + qdel(part) //This calls force_removal() with dismembered set to TRUE + if(!gibbing && length(deleted_part_names)) + var/parts_text = english_list(deleted_part_names) + rockman.investigate_log("had [rockman.p_their()] [parts_text] which all had BIO_STONE biological state) destroyed by a [src]", INVESTIGATE_CARGO) + rockman.visible_message(span_warning("[rockman] is processed by [src]!"), span_userdanger("Your [parts_text] are shred into bits by [src]!")) + + if(gibbing) + gib_mob(rockman) + +///Called at the end of main_mob() if they were a bodyparts-less mob or their chest was made of stone... +/obj/machinery/bouldertech/proc/gib_mob(mob/living/rockman) rockman.visible_message(span_warning("[rockman] is processed by [src]!"), span_userdanger("You get processed into bits by [src]!")) - rockman.investigate_log("was gibbed by [src] for being a golem", INVESTIGATE_DEATHS) + rockman.investigate_log("was gibbed by [src] for having the MOB_MINERAL mob biotype", INVESTIGATE_DEATHS) rockman.gib(DROP_ALL_REMAINS) /obj/machinery/bouldertech/proc/on_entered(datum/source, atom/movable/atom_movable) @@ -238,8 +277,8 @@ INVOKE_ASYNC(src, PROC_REF(accept_boulder), atom_movable) return - if(isgolem(atom_movable)) - INVOKE_ASYNC(src, PROC_REF(accept_golem), atom_movable) + if(isliving(atom_movable)) + INVOKE_ASYNC(src, PROC_REF(accept_mob), atom_movable) return /** diff --git a/code/modules/mining/boulder_processing/refinery.dm b/code/modules/mining/boulder_processing/refinery.dm index f65669d3b401..cc91fbb58e59 100644 --- a/code/modules/mining/boulder_processing/refinery.dm +++ b/code/modules/mining/boulder_processing/refinery.dm @@ -161,7 +161,7 @@ /obj/machinery/bouldertech/refinery/smelter/on_set_panel_open(old_value) update_light_value() -/obj/machinery/bouldertech/refinery/smelter/maim_golem(mob/living/carbon/human/rockman) +/obj/machinery/bouldertech/refinery/smelter/gib_mob(mob/living/rockman) rockman.visible_message(span_warning("[rockman] is processed by [src]!"), span_userdanger("You get melted into rock by [src]!")) - rockman.investigate_log("was melted by [src] for being a golem", INVESTIGATE_DEATHS) + rockman.investigate_log("was melted by [src] for having the MOB_MINERAL mob biotype", INVESTIGATE_DEATHS) rockman.dust() diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 83a857159eca..04bd5498f081 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -148,9 +148,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) toggle_ghost_hud_flag(GHOST_VISION | GHOST_DATA_HUDS) SSpoints_of_interest.make_point_of_interest(src) - ADD_TRAIT(src, TRAIT_HEAR_THROUGH_DARKNESS, INNATE_TRAIT) - ADD_TRAIT(src, TRAIT_GOOD_HEARING, INNATE_TRAIT) - ADD_TRAIT(src, TRAIT_DETECT_STORM, INNATE_TRAIT) + add_traits(list(TRAIT_HEAR_THROUGH_DARKNESS, TRAIT_GOOD_HEARING, TRAIT_DETECT_STORM, TRAIT_GHOSTLY_MOB), INNATE_TRAIT) /mob/dead/observer/get_photo_description(obj/item/camera/camera) if(!invisibility || camera.see_ghosts) diff --git a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm index 7fcbb5eb84ed..0fc66e6d5d34 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm @@ -57,6 +57,7 @@ post_tipped_callback = CALLBACK(src, PROC_REF(after_cow_tipped))) AddElement(/datum/element/pet_bonus, "moo") AddElement(/datum/element/swabable, CELL_LINE_TABLE_COW, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + ADD_TRAIT(src, TRAIT_ABDUCTOR_QUICK_SCAN, INNATE_TRAIT) //cow ufo abduction ahaha setup_udder() setup_eating() . = ..() diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm index 96aed06f1467..0453162f4cca 100644 --- a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm @@ -68,14 +68,15 @@ if(succeeded) controller.clear_blackboard_key(BB_CHECK_HUNGRY) -///Find nearby ashwalkers. we love lizards. +///Find nearby ashwalkers (which we share the FACTION_ASHWALKERS faction with), we love lizards. /datum/bt_node/ai_behavior/befriend_ashwalkers time_between_perform = 5 SECONDS /datum/bt_node/ai_behavior/befriend_ashwalkers/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn for(var/mob/living/potential_friend in oview(9, living_pawn)) - if(!isashwalker(potential_friend) || living_pawn.has_ally(REF(potential_friend))) + /// The client check is necessary so we don't befriend any npc mining mob that isn't hostile to ashwalkers (until attacked) + if(!living_pawn.client || !living_pawn.faction_check_atom(potential_friend) || living_pawn.has_ally(REF(potential_friend))) continue living_pawn.befriend(potential_friend) to_chat(potential_friend, span_nicegreen("[living_pawn] looks at you with endearing eyes!")) diff --git a/code/modules/mob/living/basic/lavaland/mining.dm b/code/modules/mob/living/basic/lavaland/mining.dm index 2f0baa25c2b5..7a40095b7462 100644 --- a/code/modules/mob/living/basic/lavaland/mining.dm +++ b/code/modules/mob/living/basic/lavaland/mining.dm @@ -59,6 +59,6 @@ /mob/living/basic/mining/proc/on_attacked(datum/source, atom/attacker, attack_flags) SIGNAL_HANDLER - if(!isashwalker(attacker) || !has_faction(FACTION_ASHWALKER)) + if(!has_faction(FACTION_ASHWALKER) || !astype(attacker, /mob/living)?.has_faction(FACTION_ASHWALKER)) return remove_faction(FACTION_ASHWALKER) diff --git a/code/modules/mob/living/basic/ruin_defender/skeleton.dm b/code/modules/mob/living/basic/ruin_defender/skeleton.dm index 7d42078291fb..5dab118f8cfe 100644 --- a/code/modules/mob/living/basic/ruin_defender/skeleton.dm +++ b/code/modules/mob/living/basic/ruin_defender/skeleton.dm @@ -50,7 +50,7 @@ AddElement(/datum/element/death_drops, loot) AddElement(/datum/element/basic_eating, heal_amt = 50, drinking = TRUE, food_types = good_drinks) AddElement(/datum/element/basic_eating, heal_amt = 0, damage_amount = 25, damage_type = BURN, drinking = TRUE, food_types = bad_drinks) - ADD_TRAIT(src, TRAIT_SNOWSTORM_IMMUNE, INNATE_TRAIT) + add_traits(list(TRAIT_SPOOKY_INSTRUMENT_PLAYER, TRAIT_SNOWSTORM_IMMUNE), INNATE_TRAIT) var/list/foods_list = good_drinks + bad_drinks ai_controller?.set_blackboard_key(BB_BASIC_FOODS, typecacheof(foods_list)) diff --git a/code/modules/mob/living/basic/space_fauna/ghost.dm b/code/modules/mob/living/basic/space_fauna/ghost.dm index 904857438f3f..4b8f3f438cdb 100644 --- a/code/modules/mob/living/basic/space_fauna/ghost.dm +++ b/code/modules/mob/living/basic/space_fauna/ghost.dm @@ -49,6 +49,7 @@ AddElement(/datum/element/death_drops, /obj/item/ectoplasm) AddElement(/datum/element/simple_flying) AddElement(/datum/element/ai_retaliate) + ADD_TRAIT(src, TRAIT_GHOSTLY_MOB, INNATE_TRAIT) give_identity() diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm index cd36bc7b2674..5b37b0f25021 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm @@ -102,7 +102,15 @@ /mob/living/basic/revenant/Initialize(mapload) . = ..() AddElement(/datum/element/simple_flying) - add_traits(list(TRAIT_COMBAT_MODE_LOCK, TRAIT_SPACEWALK, TRAIT_SIXTHSENSE, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_SEE_BLESSED_TILES, TRAIT_IGNORE_ELEVATION), INNATE_TRAIT) + add_traits(list( + TRAIT_COMBAT_MODE_LOCK, + TRAIT_SPACEWALK, + TRAIT_SIXTHSENSE, + TRAIT_FREE_HYPERSPACE_MOVEMENT, + TRAIT_SEE_BLESSED_TILES, + TRAIT_IGNORE_ELEVATION, + TRAIT_GHOSTLY_MOB, + ), INNATE_TRAIT) grant_actions_by_list(abilities) diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index b9d47f759e52..703c63605ce8 100644 --- a/code/modules/mob/living/brain/brain_item.dm +++ b/code/modules/mob/living/brain/brain_item.dm @@ -586,9 +586,25 @@ desc = "A piece of juicy meat found in an ayy lmao's head." icon_state = "brain-x" brain_size = 1.3 - variant_traits_added = list(TRAIT_REMOTE_TASTING) + variant_traits_added = list(TRAIT_REMOTE_TASTING, TRAIT_ABDUCTOR_KNOWLEDGE) shade_color = "grey" +/obj/item/organ/brain/abductor/on_mob_insert(mob/living/carbon/brain_owner, special = FALSE, movement_flags) + . = ..() + RegisterSignal(brain_owner, COMSIG_MOB_REAGENT_TICK, PROC_REF(handle_peanut_butter)) + +/obj/item/organ/brain/abductor/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) + . = ..() + UnregisterSignal(organ_owner, COMSIG_MOB_REAGENT_TICK) + +/obj/item/organ/brain/abductor/proc/handle_peanut_butter(mob/living/carbon/organ_owner, datum/reagent/reagent, seconds_per_tick, metabolization_ratio) + SIGNAL_HANDLER + if(!istype(reagent, /datum/reagent/consumable/peanut_butter)) + return + organ_owner.add_mood_event("ET_pieces", /datum/mood_event/et_pieces, reagent.name) + organ_owner.set_drugginess_if_lower(15 SECONDS * metabolization_ratio * seconds_per_tick) + + ////////////////////////////////////TRAUMAS//////////////////////////////////////// /obj/item/organ/brain/proc/has_trauma_type(brain_trauma_type = /datum/brain_trauma, resilience = TRAUMA_RESILIENCE_ABSOLUTE) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index b89912111ef3..0dfec0816048 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -328,14 +328,13 @@ ) add_mood_event("vomit", /datum/mood_event/vomitself) distance = 0 - else - if(message) - visible_message( - span_danger("[src] throws up!"), - span_userdanger("You throw up!"), - ) - if(!isflyperson(src)) - add_mood_event("vomit", /datum/mood_event/vomit) + else if(message) + visible_message( + span_danger("[src] throws up!"), + span_userdanger("You throw up!"), + ) + if(!HAS_TRAIT(src, TRAIT_VOMIT_SLURPER)) + add_mood_event("vomit", /datum/mood_event/vomit) if(stun) var/stun_time = 8 SECONDS diff --git a/code/modules/mob/living/carbon/human/species_types/ghost.dm b/code/modules/mob/living/carbon/human/species_types/ghost.dm index d1da78e38700..3456066ae329 100644 --- a/code/modules/mob/living/carbon/human/species_types/ghost.dm +++ b/code/modules/mob/living/carbon/human/species_types/ghost.dm @@ -13,6 +13,7 @@ TRAIT_UNHUSKABLE, TRAIT_NO_FLOATING_ANIM, TRAIT_MOVE_FLYING, + TRAIT_GHOSTLY_MOB, ) inherent_biotypes = MOB_SPIRIT | MOB_UNDEAD no_equip_flags = ITEM_SLOT_FEET diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm index 93f83df35ab5..1c316f735a9f 100644 --- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm @@ -6,6 +6,7 @@ inherent_traits = list( TRAIT_MUTANT_COLORS, TRAIT_PLANT_SAFE, + TRAIT_REQUIRED_ADV_HEALTH_SCANNER, ) mutant_organs = list( /obj/item/organ/pod_hair = "None", diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 87c8bcb27a8a..9ade8591552c 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -12,6 +12,7 @@ TRAIT_BLOOD_CLANS, TRAIT_USES_SKINTONES, TRAIT_NO_MIRROR_REFLECTION, + TRAIT_UNHOLY_BANEABLE, //still baned by silver even if they get a different heart ) inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID changesource_flags = MIRROR_BADMIN | MIRROR_PRIDE | WABBAJACK | ERT_SPAWN @@ -263,6 +264,7 @@ name = "vampire heart" icon_state = "heart_vampire" desc = "Some guy stabbed his brother 6,000 years ago so now you have this." + organ_traits = list(TRAIT_UNHOLY_BANEABLE) #undef VAMPIRES_PER_HOUSE #undef VAMP_DRAIN_AMOUNT diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 404086be71cf..25d1fe579164 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -451,7 +451,7 @@ /mob/living/carbon/proc/handle_blood(seconds_per_tick) return -/mob/living/carbon/reagent_tick(datum/reagent/chem, seconds_per_tick) +/mob/living/carbon/reagent_tick(datum/reagent/chem, seconds_per_tick, metabolization_ratio) . = ..() if(. & COMSIG_MOB_STOP_REAGENT_TICK) return diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index bf39f4aa87ee..e331f67d5e9c 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -139,9 +139,9 @@ adjust_brute_loss(min(GRAVITY_DAMAGE_SCALING * grav_strength, GRAVITY_DAMAGE_MAXIMUM) * seconds_per_tick) /// Proc used for custom metabolization of reagents, if any -/mob/living/proc/reagent_tick(datum/reagent/chem, seconds_per_tick) +/mob/living/proc/reagent_tick(datum/reagent/chem, seconds_per_tick, metabolization_ratio) SHOULD_CALL_PARENT(TRUE) - return SEND_SIGNAL(src, COMSIG_MOB_REAGENT_TICK, chem, seconds_per_tick) + return SEND_SIGNAL(src, COMSIG_MOB_REAGENT_TICK, chem, seconds_per_tick, metabolization_ratio) /// Proc used for custom reagent exposure effects, if any /mob/living/proc/reagent_expose(datum/reagent/chem, methods = TOUCH, reac_volume, show_message = TRUE, touch_protection = 0) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 893f3e5602d8..0ba2196dba7b 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -155,7 +155,7 @@ /mob/living/simple_animal/hostile/megafauna/colossus/proc/enrage(mob/living/victim) if(!ishuman(victim)) return FALSE - if(isgolem(victim) && victim.has_status_effect(/datum/status_effect/golem/gold)) + if(victim.has_status_effect(/datum/status_effect/golem/gold)) return TRUE return istype(GET_ACTIVE_MARTIAL_ART(victim), /datum/martial_art/the_sleeping_carp) diff --git a/code/modules/photography/photos/photo.dm b/code/modules/photography/photos/photo.dm index 13feead034e7..b9f46447b408 100644 --- a/code/modules/photography/photos/photo.dm +++ b/code/modules/photography/photos/photo.dm @@ -55,7 +55,7 @@ if(!seen) P.mobs_seen -= seen_ref continue - if(!isobserver(seen) && !isspirit(seen)) + if(!HAS_TRAIT(seen, TRAIT_GHOSTLY_MOB)) continue set_custom_materials(list(/datum/material/hauntium =SHEET_MATERIAL_AMOUNT)) break diff --git a/code/modules/reagents/chemistry/holder/mob_life.dm b/code/modules/reagents/chemistry/holder/mob_life.dm index b90d825c6aa6..e8c617d359e2 100644 --- a/code/modules/reagents/chemistry/holder/mob_life.dm +++ b/code/modules/reagents/chemistry/holder/mob_life.dm @@ -102,7 +102,10 @@ if(!owner || !reagent || (dead && !(reagent.chemical_flags & REAGENT_DEAD_PROCESS))) return FALSE - var/tick_return = owner.reagent_tick(reagent, seconds_per_tick) + var/metabolized_volume = reagent.compute_metabolization(owner, seconds_per_tick) + var/metabolization_ratio = REM * metabolized_volume + + var/tick_return = owner.reagent_tick(reagent, seconds_per_tick, metabolization_ratio) if(tick_return & COMSIG_MOB_STOP_REAGENT_TICK) return FALSE @@ -110,8 +113,6 @@ return FALSE var/need_mob_update = FALSE - var/metabolized_volume = reagent.compute_metabolization(owner, seconds_per_tick) - var/metabolization_ratio = REM * metabolized_volume if(reagents_metabolized) reagents_metabolized[reagent.type] = metabolization_ratio if(can_overdose && !HAS_TRAIT(owner, TRAIT_OVERDOSEIMMUNE)) diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 7484372eafb5..7fc1e49badac 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -575,7 +575,7 @@ /datum/reagent/consumable/garlic/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, metabolization_ratio) . = ..() - if(isvampire(affected_mob)) //incapacitating but not lethal. Unfortunately, vampires cannot vomit. + if(HAS_TRAIT(affected_mob, TRAIT_UNHOLY_BANEABLE)) //incapacitating but not lethal. Unfortunately, vampires cannot vomit. if(SPT_PROB(min((current_cycle-1)/2, 12.5), seconds_per_tick)) if(HAS_TRAIT(affected_mob, TRAIT_ANOSMIA)) to_chat(affected_mob, span_danger("You feel that something is wrong, your strength is leaving you! You can barely think...")) @@ -583,12 +583,12 @@ to_chat(affected_mob, span_danger("You can't get the scent of garlic out of your nose! You can barely think...")) affected_mob.Paralyze(10) affected_mob.set_jitter_if_lower(20 SECONDS) - else - var/obj/item/organ/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) - if(liver && HAS_TRAIT(liver, TRAIT_CULINARY_METABOLISM)) - if(SPT_PROB(10, seconds_per_tick)) //stays in the system much longer than sprinkles/banana juice, so heals slower to partially compensate - if(affected_mob.heal_bodypart_damage(brute = 3.34 * metabolization_ratio, burn = 3.34 * metabolization_ratio, updating_health = FALSE)) - return UPDATE_MOB_HEALTH + return + var/obj/item/organ/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER) + if(liver && HAS_TRAIT(liver, TRAIT_CULINARY_METABOLISM)) + if(SPT_PROB(10, seconds_per_tick)) //stays in the system much longer than sprinkles/banana juice, so heals slower to partially compensate + if(affected_mob.heal_bodypart_damage(brute = 3.34 * metabolization_ratio, burn = 3.34 * metabolization_ratio, updating_health = FALSE)) + return UPDATE_MOB_HEALTH /datum/reagent/consumable/tearjuice name = "Tear Juice" diff --git a/code/modules/religion/dreams/banish_nightmare.dm b/code/modules/religion/dreams/banish_nightmare.dm index 4cc8561d1f46..21f1f0359676 100644 --- a/code/modules/religion/dreams/banish_nightmare.dm +++ b/code/modules/religion/dreams/banish_nightmare.dm @@ -17,7 +17,7 @@ /datum/religion_rites/banish_nightmare/perform_rite(mob/living/user, atom/religious_tool) var/has_nightmare = FALSE for(var/mob/living/carbon/human/nightmare in get_turf(religious_tool)) - if(isnightmare(nightmare)) + if(HAS_TRAIT(nightmare, TRAIT_NIGHTMARISH)) has_nightmare = TRUE break @@ -37,7 +37,7 @@ var/favor = 0 var/give_heart = FALSE for(var/mob/living/carbon/human/nightmare in get_turf(religious_tool)) - if(!isnightmare(nightmare)) + if(!HAS_TRAIT(nightmare, TRAIT_NIGHTMARISH)) continue if(istype(nightmare.get_organ_slot(ORGAN_SLOT_HEART), /obj/item/organ/heart/nightmare)) diff --git a/code/modules/religion/dreams/deaconize_dreamer.dm b/code/modules/religion/dreams/deaconize_dreamer.dm index 04bdcfc656a6..aedb881e07df 100644 --- a/code/modules/religion/dreams/deaconize_dreamer.dm +++ b/code/modules/religion/dreams/deaconize_dreamer.dm @@ -4,7 +4,7 @@ rite_flags = parent_type::rite_flags & ~RITE_ONE_TIME_USE /datum/religion_rites/deaconize/dreamers/invoke_effect(mob/living/carbon/human/user, atom/movable/religious_tool) - if(isnightmare(potential_deacon)) + if(HAS_TRAIT(potential_deacon, TRAIT_NIGHTMARISH)) to_chat(user, span_warning("[potential_deacon] is a nightmare, an affront to [GLOB.deity] and all they stand for!")) return FALSE return ..() diff --git a/code/modules/spells/spell_types/self/ghostliness.dm b/code/modules/spells/spell_types/self/ghostliness.dm index 878114fa23f6..8db3464d2625 100644 --- a/code/modules/spells/spell_types/self/ghostliness.dm +++ b/code/modules/spells/spell_types/self/ghostliness.dm @@ -30,17 +30,17 @@ /datum/action/cooldown/spell/ghostliness/cast(mob/living/carbon/human/cast_on) . = ..() - if(isspirit(cast_on)) + if(HAS_TRAIT(cast_on, TRAIT_GHOSTLY_MOB)) to_chat(cast_on, span_green("You begin to focus on loosening the bonds holding you to the material plane.")) else to_chat(cast_on, span_green("You begin to focus on your very being, drawing it out of its corporeal vessel...")) if(!do_after(cast_on, 5 SECONDS)) - if(isspirit(cast_on)) + if(HAS_TRAIT(cast_on, TRAIT_GHOSTLY_MOB)) to_chat(cast_on, span_warning("Your focus is broken, and you feel your material bindings snap tight once more.")) else to_chat(cast_on, span_warning("Your focus is broken, and your soul snaps back into place.")) return - if(isspirit(cast_on)) + if(HAS_TRAIT(cast_on, TRAIT_GHOSTLY_MOB)) to_chat(cast_on, span_green("You successfully loosen your bonds to the material plane, and can now slip partially out of it.")) else to_chat(cast_on, span_danger("As the last trailing filament of your essence ceases intersection with your body, \ diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 45d5b026832a..698d8fd43172 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -1836,6 +1836,8 @@ /// Returns the generic description of our BIO_INTERNAL feature(s), prioritizing certain ones over others. Returns error on failure. /obj/item/bodypart/proc/get_internal_description() + if(biological_state & BIO_STONE) + return "bedrock" if (biological_state & BIO_BONE) return "bone" if (biological_state & BIO_METAL) diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index efa39c3591da..f1d1332e4464 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -61,6 +61,7 @@ is_dimorphic = FALSE should_draw_greyscale = FALSE wing_types = null + bodypart_traits = list(TRAIT_ABDUCTOR_QUICK_SCAN) /obj/item/bodypart/chest/abductor/get_butt_sprite() return icon('icons/mob/butts.dmi', BUTT_SPRITE_GREY) @@ -565,7 +566,7 @@ icon = 'icons/mob/human/species/golems.dmi' icon_static = 'icons/mob/human/species/golems.dmi' icon_state = "golem_head" - biological_state = BIO_BONE + biological_state = BIO_BONE|BIO_STONE bodytype = BODYTYPE_GOLEM | BODYTYPE_ORGANIC bodyshape = BODYSHAPE_GOLEM limb_id = SPECIES_GOLEM @@ -608,7 +609,7 @@ icon = 'icons/mob/human/species/golems.dmi' icon_static = 'icons/mob/human/species/golems.dmi' icon_state = "golem_chest" - biological_state = BIO_BONE + biological_state = BIO_BONE|BIO_STONE acceptable_bodytype = BODYTYPE_GOLEM bodytype = BODYTYPE_GOLEM | BODYTYPE_ORGANIC bodyshape = BODYSHAPE_GOLEM @@ -634,7 +635,7 @@ icon = 'icons/mob/human/species/golems.dmi' icon_static = 'icons/mob/human/species/golems.dmi' icon_state = "golem_l_arm" - biological_state = (BIO_BONE|BIO_JOINTED) + biological_state = (BIO_BONE|BIO_JOINTED|BIO_STONE) bodytype = BODYTYPE_GOLEM | BODYTYPE_ORGANIC bodyshape = BODYSHAPE_GOLEM limb_id = SPECIES_GOLEM @@ -671,7 +672,7 @@ icon = 'icons/mob/human/species/golems.dmi' icon_static = 'icons/mob/human/species/golems.dmi' icon_state = "golem_r_arm" - biological_state = (BIO_BONE|BIO_JOINTED) + biological_state = (BIO_BONE|BIO_JOINTED|BIO_STONE) bodytype = BODYTYPE_GOLEM | BODYTYPE_ORGANIC bodyshape = BODYSHAPE_GOLEM limb_id = SPECIES_GOLEM @@ -708,7 +709,7 @@ icon = 'icons/mob/human/species/golems.dmi' icon_static = 'icons/mob/human/species/golems.dmi' icon_state = "golem_l_leg" - biological_state = (BIO_BONE|BIO_JOINTED) + biological_state = (BIO_BONE|BIO_JOINTED|BIO_STONE) bodytype = BODYTYPE_GOLEM | BODYTYPE_ORGANIC bodyshape = BODYSHAPE_GOLEM limb_id = SPECIES_GOLEM @@ -725,7 +726,7 @@ icon = 'icons/mob/human/species/golems.dmi' icon_static = 'icons/mob/human/species/golems.dmi' icon_state = "golem_r_leg" - biological_state = (BIO_BONE|BIO_JOINTED) + biological_state = (BIO_BONE|BIO_JOINTED|BIO_STONE) bodytype = BODYTYPE_GOLEM | BODYTYPE_ORGANIC bodyshape = BODYSHAPE_GOLEM limb_id = SPECIES_GOLEM diff --git a/code/modules/surgery/operations/operation_plastic_surgery.dm b/code/modules/surgery/operations/operation_plastic_surgery.dm index 63b130ff6c31..99dd9cad6a14 100644 --- a/code/modules/surgery/operations/operation_plastic_surgery.dm +++ b/code/modules/surgery/operations/operation_plastic_surgery.dm @@ -29,7 +29,7 @@ return TRUE //skip name selection if fixing disfigurement var/list/names = list() - if(isabductor(surgeon)) + if(HAS_MIND_TRAIT(surgeon, TRAIT_ABDUCTOR_KNOWLEDGE)) for(var/j in 1 to 9) names += "Subject [limb.owner.gender == MALE ? "i" : "o"]-[pick("a", "b", "c", "d", "e")]-[rand(10000, 99999)]" diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm index 2f6224b6e249..bb1462e262ae 100644 --- a/code/modules/surgery/organs/internal/liver/_liver.dm +++ b/code/modules/surgery/organs/internal/liver/_liver.dm @@ -83,7 +83,7 @@ * * NOTE: If you return COMSIG_MOB_STOP_REAGENT_TICK, that reagent will not be removed like normal! You must handle it manually. **/ -/obj/item/organ/liver/proc/handle_chemical(mob/living/carbon/organ_owner, datum/reagent/chem, seconds_per_tick) +/obj/item/organ/liver/proc/handle_chemical(mob/living/carbon/organ_owner, datum/reagent/chem, seconds_per_tick, metabolization_rate) SIGNAL_HANDLER /obj/item/organ/liver/examine(mob/user) diff --git a/code/modules/surgery/organs/internal/liver/liver_golem.dm b/code/modules/surgery/organs/internal/liver/liver_golem.dm index c05c5df26aaf..05193c209849 100644 --- a/code/modules/surgery/organs/internal/liver/liver_golem.dm +++ b/code/modules/surgery/organs/internal/liver/liver_golem.dm @@ -12,7 +12,7 @@ organ_flags = ORGAN_MINERAL color = COLOR_GOLEM_GRAY -/obj/item/organ/liver/golem/handle_chemical(mob/living/carbon/organ_owner, datum/reagent/chem, seconds_per_tick) +/obj/item/organ/liver/golem/handle_chemical(mob/living/carbon/organ_owner, datum/reagent/chem, seconds_per_tick, metabolization_rate) . = ..() // parent returned COMSIG_MOB_STOP_REAGENT_TICK or we are failing if((. & COMSIG_MOB_STOP_REAGENT_TICK) || (organ_flags & ORGAN_FAILING)) diff --git a/code/modules/surgery/organs/internal/liver/liver_plasmaman.dm b/code/modules/surgery/organs/internal/liver/liver_plasmaman.dm index 18eaadbfa763..a4e2da4fca64 100644 --- a/code/modules/surgery/organs/internal/liver/liver_plasmaman.dm +++ b/code/modules/surgery/organs/internal/liver/liver_plasmaman.dm @@ -10,7 +10,7 @@ organ_traits = list(TRAIT_PLASMA_LOVER_METABOLISM) milk_burn_healing = 0 -/obj/item/organ/liver/bone/plasmaman/handle_chemical(mob/living/carbon/organ_owner, datum/reagent/chem, seconds_per_tick) +/obj/item/organ/liver/bone/plasmaman/handle_chemical(mob/living/carbon/organ_owner, datum/reagent/chem, seconds_per_tick, metabolization_rate) . = ..() // parent returned COMSIG_MOB_STOP_REAGENT_TICK or we are failing if((. & COMSIG_MOB_STOP_REAGENT_TICK) || (organ_flags & ORGAN_FAILING)) diff --git a/code/modules/surgery/organs/internal/liver/liver_skeleton.dm b/code/modules/surgery/organs/internal/liver/liver_skeleton.dm index c01919cdc378..74303414beef 100644 --- a/code/modules/surgery/organs/internal/liver/liver_skeleton.dm +++ b/code/modules/surgery/organs/internal/liver/liver_skeleton.dm @@ -13,7 +13,7 @@ ///Var for burn healing via milk var/milk_burn_healing = 2.5 -/obj/item/organ/liver/bone/handle_chemical(mob/living/carbon/organ_owner, datum/reagent/chem, seconds_per_tick) +/obj/item/organ/liver/bone/handle_chemical(mob/living/carbon/organ_owner, datum/reagent/chem, seconds_per_tick, metabolization_rate) . = ..() // parent returned COMSIG_MOB_STOP_REAGENT_TICK or we are failing if((. & COMSIG_MOB_STOP_REAGENT_TICK) || (organ_flags & ORGAN_FAILING)) diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm index 3f9376b1d878..fe42147f0d43 100644 --- a/code/modules/surgery/organs/internal/tongue/_tongue.dm +++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm @@ -363,6 +363,7 @@ desc = "A mysterious structure that allows for instant communication between users. Pretty impressive until you need to eat something." icon_state = "tongueayylmao" say_mod = "gibbers" + organ_traits = list(TRAIT_HIDE_THINKING_INDICATOR) sense_of_taste = FALSE modifies_speech = TRUE var/mothership From 0cc888f4466475acc6731d9ef83b7ac8a016be2a Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:52:07 +0000 Subject: [PATCH 381/445] Automatic changelog for PR #96945 [ci skip] --- html/changelogs/AutoChangeLog-pr-96945.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96945.yml diff --git a/html/changelogs/AutoChangeLog-pr-96945.yml b/html/changelogs/AutoChangeLog-pr-96945.yml new file mode 100644 index 000000000000..ab9de131dce4 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96945.yml @@ -0,0 +1,5 @@ +author: "Ghommie" +delete-after: True +changes: + - balance: "The boulder processing machinery can maim any (incapacitated/dead) mob with the mineral biotype, not just golems." + - refactor: "some quirky interactions with specific (non-roundstart) species have been de-hardcoded. An example: peanut butter gets those with abductor brains high rather than just those with the abductor species. Report any oddity with that (the refactor, not the peanut butter...)." \ No newline at end of file From 299d8a6433e38f575f48a70597df3e2584c9fc63 Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:53:16 +0200 Subject: [PATCH 382/445] More is_species() conversion to less hardcoded checks. (#96956) ## About The Pull Request This time, I take care of a bunch of is_species() checks for roundstart races, as well as pod and jellypeople. Surprisingly, this was WAY less work than the other PR, but that's also because many of the species checks for jellypeople and ethereal cannot be touched at the moment because they're tied to features like jellypeople abilities and ethereal glowing skin color mechanics. This PR also makes cats and not just catpeople follow laser pointers as well. ## Why It's Good For The Game If we are moving away from features being directly tied to a species, then we need to get rid of a bunch of species checks as well. --- .../signals/signals_mob/signals_mob_carbon.dm | 2 + code/__DEFINES/traits/declarations.dm | 10 ++++- code/_globalvars/traits/_traits.dm | 5 ++- code/_globalvars/traits/admin_tooling.dm | 3 +- code/datums/components/food/edible.dm | 2 +- code/datums/components/tackle.dm | 4 +- .../mood_events/generic_negative_events.dm | 4 +- .../objects/items/devices/laserpointer.dm | 20 ++++----- code/game/objects/items/pet_carrier.dm | 3 +- code/modules/clothing/clothing.dm | 2 +- code/modules/hydroponics/beekeeping/beebox.dm | 23 +--------- .../chaplain/chaplain_vorpal_scythe.dm | 2 +- code/modules/mob/living/basic/pets/cat/cat.dm | 2 +- code/modules/mob/living/brain/brain_item.dm | 4 ++ .../carbon/human/species_types/ethereal.dm | 8 ++++ .../carbon/human/species_types/felinid.dm | 2 - .../human/species_types/lizardpeople.dm | 1 + .../carbon/human/species_types/mothmen.dm | 4 ++ code/modules/mob/living/living.dm | 15 ++++++- .../reagents/drinks/alcohol_reagents.dm | 4 +- .../reagents/drinks/drink_reagents.dm | 2 +- .../chemistry/reagents/food_reagents.dm | 2 +- .../chemistry/reagents/other_reagents.dm | 4 +- .../reagents/pyrotechnic_reagents.dm | 2 +- .../xenobiology/crossbreeding/regenerative.dm | 2 +- code/modules/surgery/bodyparts/_bodyparts.dm | 2 + .../species_parts/ethereal_bodyparts.dm | 42 ------------------- .../surgery/organs/internal/liver/_liver.dm | 6 +++ .../organs/internal/stomach/_stomach.dm | 7 ++++ .../internal/stomach/stomach_ethereal.dm | 2 +- 30 files changed, 90 insertions(+), 101 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index 79c3e4673e70..65203c4db982 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -62,6 +62,8 @@ /// From /obj/item/bodypart/proc/update_limb(): (dropping_limb, is_creating) #define COMSIG_BODYPART_UPDATED "bodypart_updated" +/// Also from /obj/item/bodypart/proc/update_limb(), sent to the mob: (dropping_limb, is_creating) +#define COMSIG_CARBON_BODYPART_UPDATED "carbon_bodypart_updated" /// From /datum/component/butchering/create_replacement_limb(): (replacement limb) #define COMSIG_BODYPART_BUTCHERED "bodypart_butchered" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 1fd81be11a86..ad14246096a1 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1048,8 +1048,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_VEGETARIAN "trait_vegetarian" #define TRAIT_KEEN_NOSE "keen_nose" -/// This mob always lands on their feet when they fall, for better or for worse. -#define TRAIT_CATLIKE_GRACE "catlike_grace" +/// The mob has a variety of catlike behaviors, like landing on their feet when falling or chasing pinpointers. +#define TRAIT_CATLIKE_INSTINCT "catlike_grace" ///Won't show up on cameras when they snap a photo. #define TRAIT_INVISIBLE_TO_CAMERA "invisible_to_camera" @@ -1679,6 +1679,12 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Makes the owner immune from the pacification from synthpax #define TRAIT_SYNTHPAX_IMMUNE "synthpax_immune" +/// Allow the owner to eat cloth +#define TRAIT_CLOTH_EATER "cloth_eater" +/// Has the metabolization quirks of a lizardperson +#define TRAIT_LIZARD_METABOLISM "lizard_metabolism" +// Has the metabolization quirks of an ethereal +#define TRAIT_ETHEREAL_METABOLISM "ethereal_metabolism" /// Mobs with this trait will always have be a priority target for mining mobs #define TRAIT_MINING_AGGRO "mining_aggro" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index c33cc5b1c6c9..f570a85dd182 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -234,7 +234,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CAN_STRIP" = TRAIT_CAN_STRIP, "TRAIT_CAN_THROW_ITEMS" = TRAIT_CAN_THROW_ITEMS, "TRAIT_CAN_USE_NUKE" = TRAIT_CAN_USE_NUKE, - "TRAIT_CATLIKE_GRACE" = TRAIT_CATLIKE_GRACE, + "TRAIT_CATLIKE_INSTINCT" = TRAIT_CATLIKE_INSTINCT, "TRAIT_CHANGELING_HIVEMIND" = TRAIT_CHANGELING_HIVEMIND, "TRAIT_CHASM_DESTROYED" = TRAIT_CHASM_DESTROYED, "TRAIT_CHEF_KISS" = TRAIT_CHEF_KISS, @@ -243,6 +243,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CHUUNIBYOU" = TRAIT_CHUUNIBYOU, "TRAIT_CLEANBOT_WHISPERER" = TRAIT_CLEANBOT_WHISPERER, "TRAIT_CLIFF_WALKER" = TRAIT_CLIFF_WALKER, + "TRAIT_CLOTH_EATER" = TRAIT_CLOTH_EATER, "TRAIT_CLOWN_ENJOYER" = TRAIT_CLOWN_ENJOYER, "TRAIT_CLUMSY" = TRAIT_CLUMSY, "TRAIT_COAGULATING" = TRAIT_COAGULATING, @@ -285,6 +286,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_ELITE_CHALLENGER" = TRAIT_ELITE_CHALLENGER, "TRAIT_EMOTEMUTE" = TRAIT_EMOTEMUTE, "TRAIT_ENTRAILS_READER" = TRAIT_ENTRAILS_READER, + "TRAIT_ETHEREAL_METABOLISM" = TRAIT_ETHEREAL_METABOLISM, "TRAIT_EVIL" = TRAIT_EVIL, "TRAIT_EXAMINE_DEEPER_FISH" = TRAIT_EXAMINE_DEEPER_FISH, "TRAIT_EXAMINE_FISH" = TRAIT_EXAMINE_FISH, @@ -387,6 +389,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_LIMBATTACHMENT" = TRAIT_LIMBATTACHMENT, "TRAIT_LITERATE" = TRAIT_LITERATE, "TRAIT_LIVERLESS_METABOLISM" = TRAIT_LIVERLESS_METABOLISM, + "TRAIT_LIZARD_METABOLISM" = TRAIT_LIZARD_METABOLISM, "TRAIT_LOUD_BINARY" = TRAIT_LOUD_BINARY, "TRAIT_LUMINESCENT_EYES" = TRAIT_LUMINESCENT_EYES, "TRAIT_MADNESS_IMMUNE" = TRAIT_MADNESS_IMMUNE, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index 79ef99a9949a..da610a4f22b5 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -75,12 +75,13 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_CAN_STRIP" = TRAIT_CAN_STRIP, "TRAIT_CAN_THROW_ITEMS" = TRAIT_CAN_THROW_ITEMS, "TRAIT_CAN_USE_NUKE" = TRAIT_CAN_USE_NUKE, - "TRAIT_CATLIKE_GRACE" = TRAIT_CATLIKE_GRACE, + "TRAIT_CATLIKE_INSTINCT" = TRAIT_CATLIKE_INSTINCT, "TRAIT_CHASM_DESTROYED" = TRAIT_CHASM_DESTROYED, "TRAIT_CHUNKYFINGERS" = TRAIT_CHUNKYFINGERS, "TRAIT_CHUNKYFINGERS_IGNORE_BATON" = TRAIT_CHUNKYFINGERS_IGNORE_BATON, "TRAIT_CLEANBOT_WHISPERER" = TRAIT_CLEANBOT_WHISPERER, "TRAIT_CLIFF_WALKER" = TRAIT_CLIFF_WALKER, + "TRAIT_CLOTH_EATER" = TRAIT_CLOTH_EATER, "TRAIT_CLOWN_ENJOYER" = TRAIT_CLOWN_ENJOYER, "TRAIT_CLUMSY" = TRAIT_CLUMSY, "TRAIT_COLORBLIND" = TRAIT_COLORBLIND, diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm index c1aa1c3a6cb4..c12c7fbce1e7 100644 --- a/code/datums/components/food/edible.dm +++ b/code/datums/components/food/edible.dm @@ -661,7 +661,7 @@ Behavior that's still missing from this component that original food items had t food_quality += quality if(HAS_TRAIT(parent, TRAIT_FOOD_SILVER)) // it's not real food - if(!isjellyperson(eater)) //if you aren't a jellyperson, it makes you sick no matter how nice it looks + if(!(eater.mob_biotypes & MOB_SLIME)) //if you aren't a jellyperson, it makes you sick no matter how nice it looks return TOXIC_FOOD_QUALITY_THRESHOLD food_quality += LIKED_FOOD_QUALITY_CHANGE diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index 9bdc48520c4d..ca461d139385 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -107,7 +107,7 @@ RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(checkObstacle)) playsound(user, 'sound/items/weapons/thudswoosh.ogg', 40, TRUE, -1) - var/leap_word = isfelinid(user) || HAS_TRAIT(user, TRAIT_TACKLING_TAILED_POUNCE) ? "pounce" : "leap" //If cat, "pounce" instead of "leap". + var/leap_word = HAS_TRAIT(user, TRAIT_CATLIKE_INSTINCT) || HAS_TRAIT(user, TRAIT_TACKLING_TAILED_POUNCE) ? "pounce" : "leap" //If cat, "pounce" instead of "leap". if(can_see(user, clicked_atom, 7)) user.visible_message(span_warning("[user] [leap_word]s at [clicked_atom]!"), span_danger("You [leap_word] at [clicked_atom]!")) else @@ -153,7 +153,7 @@ return var/mob/living/carbon/target = hit - var/tackle_word = isfelinid(user) ? "pounce" : "tackle" //If cat, "pounce" instead of "tackle". + var/tackle_word = HAS_TRAIT(user, TRAIT_CATLIKE_INSTINCT) ? "pounce" : "tackle" //If cat, "pounce" instead of "tackle". var/roll = rollTackle(target) tackling = FALSE diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 2df55649fd77..f952e207ad49 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -106,7 +106,7 @@ timeout = 2 MINUTES /datum/mood_event/table/add_effects() - if(isfelinid(owner)) //Holy snowflake batman! + if(HAS_TRAIT(owner, TRAIT_CATLIKE_INSTINCT)) //Holy snowflake batman! var/mob/living/carbon/human/feline = owner feline.wag_tail(3 SECONDS) description = "They want to play on the table!" @@ -526,7 +526,7 @@ /datum/mood_event/bapped/add_effects() // Felinids apparently hate being hit over the head with cardboard - if(isfelinid(owner)) + if(HAS_TRAIT(owner, TRAIT_CATLIKE_INSTINCT)) mood_change = -2 /datum/mood_event/encountered_evil diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index 664b47db23d6..d01481eaaeeb 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -275,20 +275,20 @@ else outmsg = span_warning("You miss the lens of [target_camera] with [src]!") - //catpeople: make any felinid near the target to face the target, chance for felinids to pounce at the light, stepping to the target - for(var/mob/living/carbon/human/target_felinid in view(1, targloc)) - if(!isfelinid(target_felinid) || target_felinid.stat == DEAD || target_felinid.is_blind() || target_felinid.incapacitated) + //Make any mob with catlike instincts near the target face the target, with a chance of pouncing (moving) toward it. + for(var/mob/living/target_cat in view(1, targloc)) + if(!HAS_TRAIT(target_cat, TRAIT_CATLIKE_INSTINCT) || target_cat.is_blind() || target_cat.incapacitated) continue - if(target_felinid.body_position == STANDING_UP) - target_felinid.setDir(get_dir(target_felinid, targloc)) // kitty always looks at the light + target_cat.setDir(get_dir(target_cat, targloc)) // kitty always looks at the light + if(target_cat.body_position == STANDING_UP) if(prob(effectchance * diode.rating)) - target_felinid.visible_message(span_warning("[target_felinid] makes a grab for the light!"), span_userdanger("LIGHT!")) - target_felinid.Move(targloc, get_dir(target_felinid, targloc)) - log_combat(user, target_felinid, "moved with a laser pointer", src) + target_cat.visible_message(span_warning("[target_cat] makes a grab for the light!"), span_userdanger("LIGHT!")) + target_cat.Move(targloc, get_dir(target_cat, targloc)) + log_combat(user, target_cat, "moved with a laser pointer", src) else - target_felinid.visible_message(span_notice("[target_felinid] looks briefly distracted by the light."), span_warning("You're briefly tempted by the shiny light...")) + target_cat.visible_message(span_notice("[target_cat] looks briefly distracted by the light."), span_warning("You're briefly tempted by the shiny light...")) else - target_felinid.visible_message(span_notice("[target_felinid] stares at the light."), span_warning("You stare at the light...")) + target_cat.visible_message(span_notice("[target_cat] stares at the light."), span_warning("You stare at the light...")) //The pointer is shining, change its sprite to show icon_state = "pointer_[pointer_icon_state]" diff --git a/code/game/objects/items/pet_carrier.dm b/code/game/objects/items/pet_carrier.dm index dc93e9364390..2525f730189b 100644 --- a/code/game/objects/items/pet_carrier.dm +++ b/code/game/objects/items/pet_carrier.dm @@ -111,8 +111,7 @@ var/mob/living/target = interacting_with if(target.mob_size > max_occupant_weight) if(ishuman(target)) - var/mob/living/carbon/human/H = target - if(isfelinid(H)) + if(HAS_TRAIT(target, TRAIT_CATLIKE_INSTINCT)) to_chat(user, span_warning("You'd need a lot of catnip and treats, plus maybe a laser pointer, for that to work.")) else to_chat(user, span_warning("Humans, generally, do not fit into pet carriers.")) diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index d1bf6615c9da..aab2bfee2955 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -106,7 +106,7 @@ qdel(src) /obj/item/clothing/attack(mob/living/target, mob/living/user, list/modifiers, list/attack_modifiers) - if(user.combat_mode || !ismoth(target) || ispickedupmob(src)) + if(user.combat_mode || !HAS_TRAIT(user, TRAIT_CLOTH_EATER) || ispickedupmob(src)) return ..() if((clothing_flags & INEDIBLE_CLOTHING) || (resistance_flags & INDESTRUCTIBLE)) return ..() diff --git a/code/modules/hydroponics/beekeeping/beebox.dm b/code/modules/hydroponics/beekeeping/beebox.dm index de5b5aa5ab0e..f66fd88ab451 100644 --- a/code/modules/hydroponics/beekeeping/beebox.dm +++ b/code/modules/hydroponics/beekeeping/beebox.dm @@ -5,24 +5,6 @@ #define BEE_RESOURCE_HONEYCOMB_COST 100 //The amount of bee_resources for a new honeycomb to be produced, percentage cost 1-100 #define BEE_RESOURCE_NEW_BEE_COST 50 //The amount of bee_resources for a new bee to be produced, percentage cost 1-100 - - -/mob/proc/bee_friendly() - return 0 - - - -/mob/living/carbon/human/bee_friendly() - if(ispodperson(src)) //bees pollinate plants, duh. - return 1 - if (wear_suit && head && isclothing(wear_suit) && isclothing(head)) - var/obj/item/clothing/suit = wear_suit - var/obj/item/clothing/hat = head - if (suit.clothing_flags & hat.clothing_flags & THICKMATERIAL) - return 1 - return 0 - - /obj/structure/beebox name = "apiary" desc = "Dr. Miles Manners is just your average wasp-themed super hero by day, but by night he becomes DR. BEES!" @@ -37,12 +19,10 @@ var/list/honey_frames = list() var/bee_resources = 0 - /obj/structure/beebox/Initialize(mapload) . = ..() START_PROCESSING(SSobj, src) - /obj/structure/beebox/Destroy() STOP_PROCESSING(SSobj, src) queen_bee = null @@ -51,7 +31,6 @@ QDEL_LIST(honeycombs) return ..() - //Premade apiaries can spawn with a random reagent /obj/structure/beebox/premade var/random_reagent = FALSE @@ -192,7 +171,7 @@ return NONE -/obj/structure/beebox/interact(mob/user) +/obj/structure/beebox/interact(mob/living/user) . = ..() if(!user.bee_friendly()) //Time to get stung! diff --git a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm index 4f806ed6243b..b60156192662 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm @@ -141,7 +141,7 @@ If the scythe isn't empowered when you sheath it, you take a heap of damage and death_knell_speed_mod *= 1.5 //Staminacritting someone who's jittering (from, say, a stun baton) won't give you enough time to slice their head off, but staminacritting someone who isn't jittering will if(empowerment == SCYTHE_EMPOWERED) //That said, if heads are already rolling, why stop here? death_knell_speed_mod *= 0.5 - if(ispodperson(potential_reaping) || ismonkey(potential_reaping)) //And if they're a podperson or monkey, they can just die. + if((potential_reaping.mob_biotypes & MOB_PLANT) || ismonkey(potential_reaping)) //And if they're a podperson/mushperson or monkey, they can just die. death_knell_speed_mod *= 0.5 log_combat(user, potential_reaping, "prepared to use [src] to decapitate") diff --git a/code/modules/mob/living/basic/pets/cat/cat.dm b/code/modules/mob/living/basic/pets/cat/cat.dm index abf7932978d7..5f82c564aee7 100644 --- a/code/modules/mob/living/basic/pets/cat/cat.dm +++ b/code/modules/mob/living/basic/pets/cat/cat.dm @@ -93,7 +93,7 @@ AddElement(/datum/element/can_be_held) add_cell_sample() add_verb(src, /mob/living/proc/toggle_resting) - add_traits(list(TRAIT_CATLIKE_GRACE, TRAIT_VENTCRAWLER_ALWAYS, TRAIT_WOUND_LICKER, TRAIT_COLORBLIND), INNATE_TRAIT) + add_traits(list(TRAIT_CATLIKE_INSTINCT, TRAIT_VENTCRAWLER_ALWAYS, TRAIT_WOUND_LICKER, TRAIT_COLORBLIND), INNATE_TRAIT) ai_controller.set_blackboard_key(BB_HUNTABLE_PREY, typecacheof(huntable_items)) if(can_breed) add_breeding_component() diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index 703c63605ce8..7a9da94e8c5e 100644 --- a/code/modules/mob/living/brain/brain_item.dm +++ b/code/modules/mob/living/brain/brain_item.dm @@ -559,6 +559,10 @@ /obj/item/organ/brain/felinid //A bit smaller than average brain_size = 0.8 + organ_traits = list( + TRAIT_CATLIKE_INSTINCT, + TRAIT_WATER_HATER, + ) // Sometimes, felinids go a bit haywire and bite people. Based entirely on mania and hunger. /obj/item/organ/brain/felinid/get_attacking_limb(mob/living/carbon/human/target) diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm index 2de3d7fc7991..5adf84f02b29 100644 --- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm +++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm @@ -61,6 +61,7 @@ RegisterSignal(new_ethereal, COMSIG_ATOM_SABOTEUR_ACT, PROC_REF(hit_by_saboteur)) RegisterSignal(new_ethereal, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) RegisterSignal(new_ethereal, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(refresh_light_color)) + RegisterSignal(new_ethereal, COMSIG_CARBON_BODYPART_UPDATED, PROC_REF(update_bodypart_color)) ethereal_light = new_ethereal.mob_light(light_type = /obj/effect/dummy/lighting_obj/moblight/species) refresh_light_color(new_ethereal) @@ -78,6 +79,7 @@ COMSIG_ATOM_SABOTEUR_ACT, COMSIG_LIGHT_EATER_ACT, COMSIG_LIVING_HEALTH_UPDATE, + COMSIG_CARBON_BODYPART_UPDATED, )) QDEL_NULL(ethereal_light) return ..() @@ -123,6 +125,12 @@ ethereal.set_haircolor(dead_color, override = TRUE, update = FALSE) ethereal.update_body() +/datum/species/ethereal/proc/update_bodypart_color(datum/source, obj/item/bodypart/part, dropping_limb, is_creating) + SIGNAL_HANDLER + if(part.limb_id != SPECIES_ETHEREAL) + return + part.species_color = current_color + /datum/species/ethereal/proc/on_emp_act(mob/living/carbon/human/source, severity, protection) SIGNAL_HANDLER if(protection & EMP_PROTECT_SELF) diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm index 124196d02af5..a1733698711d 100644 --- a/code/modules/mob/living/carbon/human/species_types/felinid.dm +++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm @@ -11,10 +11,8 @@ /obj/item/organ/tail/cat = "Cat", ) inherent_traits = list( - TRAIT_CATLIKE_GRACE, TRAIT_HATED_BY_DOGS, TRAIT_USES_SKINTONES, - TRAIT_WATER_HATER, ) changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT species_language_holder = /datum/language_holder/felinid diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index fe55ba44a046..2d51b1676a52 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -20,6 +20,7 @@ mutanttongue = /obj/item/organ/tongue/lizard mutanteyes = /obj/item/organ/eyes/lizard mutantbrain = /obj/item/organ/brain/lizard + mutantliver = /obj/item/organ/liver/lizard coldmod = 1.5 heatmod = 0.67 payday_modifier = 1.0 diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index 9660b2d2e4c2..5a712a5a1368 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -10,9 +10,13 @@ /obj/item/organ/wings/moth = "Plain", /obj/item/organ/antennae = "Plain", ) + inherent_traits = list( + TRAIT_CLOTH_EATER, //So that moths can still eat cloth even if their stomach is augmented + ) meat = /obj/item/food/meat/slab/human/mutant/moth mutanttongue = /obj/item/organ/tongue/moth mutanteyes = /obj/item/organ/eyes/moth + mutantstomach = /obj/item/organ/stomach/moth changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | ERT_SPAWN | RACE_SWAP | SLIME_EXTRACT species_cookie = /obj/item/food/muffin/moffin species_language_holder = /datum/language_holder/moth diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index a4d45dec889f..8c21c4a164bb 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -93,7 +93,7 @@ if(levels <= 1 && can_help_themselves) var/obj/item/organ/wings/gliders = get_organ_by_type(/obj/item/organ/wings) if(HAS_TRAIT(src, TRAIT_FREERUNNING) || gliders?.can_soften_fall()) // the power of parkour or wings allows falling short distances unscathed - var/graceful_landing = HAS_TRAIT(src, TRAIT_CATLIKE_GRACE) + var/graceful_landing = HAS_TRAIT(src, TRAIT_CATLIKE_INSTINCT) if(graceful_landing) add_movespeed_modifier(/datum/movespeed_modifier/landed_on_feet) @@ -112,7 +112,7 @@ // Smaller mobs with catlike grace can ignore damage (EG: cats) var/small_surface_area = mob_size <= MOB_SIZE_SMALL var/skip_knockdown = FALSE - if(HAS_TRAIT(src, TRAIT_CATLIKE_GRACE) && (small_surface_area || usable_legs >= 2) && body_position == STANDING_UP && can_help_themselves) + if(HAS_TRAIT(src, TRAIT_CATLIKE_INSTINCT) && (small_surface_area || usable_legs >= 2) && body_position == STANDING_UP && can_help_themselves) . |= ZIMPACT_NO_MESSAGE|ZIMPACT_NO_SPIN skip_knockdown = TRUE if(small_surface_area) @@ -3014,6 +3014,17 @@ GLOBAL_LIST_EMPTY(fire_appearances) return span_notice("You'd estimate [p_their()] fitness level at about [our_fitness_level]. [comparative_fitness <= 0.33 ? "Pathetic." : ""]") +/// Check if bees are not hostile to us +/mob/living/proc/bee_friendly() + if(mob_biotypes & MOB_PLANT) + return TRUE + var/obj/item/clothing/suit = get_item_by_slot(ITEM_SLOT_OCLOTHING) + var/obj/item/clothing/hat = get_item_by_slot(ITEM_SLOT_HEAD) + if(!istype(suit) || !istype(hat)) + return FALSE + if(suit.clothing_flags & hat.clothing_flags & THICKMATERIAL) + return TRUE + ///Performs the aftereffects of blocking a projectile. /mob/living/proc/block_projectile_effects() var/static/list/icon/blocking_overlay diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm index 6129aa822386..14443afe34d3 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm @@ -2545,7 +2545,7 @@ /datum/reagent/consumable/ethanol/protein_blend/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, metabolization_ratio) . = ..() drinker.adjust_nutrition(2 * metabolization_ratio * seconds_per_tick) - if(!islizard(drinker)) + if(!HAS_TRAIT(drinker, TRAIT_LIZARD_METABOLISM)) drinker.adjust_disgust(5 * metabolization_ratio * seconds_per_tick) else drinker.adjust_disgust(2 * metabolization_ratio * seconds_per_tick) @@ -2572,7 +2572,7 @@ /datum/reagent/consumable/ethanol/triumphal_arch/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, metabolization_ratio) . = ..() - if(islizard(drinker)) + if(HAS_TRAIT(drinker, TRAIT_LIZARD_METABOLISM)) drinker.add_mood_event("triumph", /datum/mood_event/memories_of_home, name) /datum/reagent/consumable/ethanol/the_juice diff --git a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm index c5aed896bf0c..4745c6fdb578 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm @@ -1219,7 +1219,7 @@ /datum/reagent/consumable/mushroom_tea/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, metabolization_ratio) . = ..() - if(islizard(affected_mob)) + if(HAS_TRAIT(affected_mob, TRAIT_LIZARD_METABOLISM)) if(affected_mob.adjust_oxy_loss(-0.25 * metabolization_ratio * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype)) return UPDATE_MOB_HEALTH diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 7fc1e49badac..efce8b609029 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -1038,7 +1038,7 @@ /datum/reagent/consumable/liquidelectricity/enriched/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, metabolization_ratio) . = ..() - if(isethereal(affected_mob)) + if(HAS_TRAIT(affected_mob, TRAIT_ETHEREAL_METABOLISM)) affected_mob.adjust_blood_volume(1 * seconds_per_tick) else if(SPT_PROB(10, seconds_per_tick)) //lmao at the newbs who eat energy bars affected_mob.electrocute_act(rand(5,10), "Liquid Electricity in their body", 1, SHOCK_NOGLOVES) //the shock is coming from inside the house diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 0e4a9618b386..f6ea167bfaaf 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -802,7 +802,7 @@ if(!ishuman(affected_mob)) return ..() var/mob/living/carbon/affected_human = affected_mob - if(isjellyperson(affected_human)) + if(affected_human.mob_biotypes & MOB_SLIME) var/datum/species/species_type = pick(subtypesof(race)) affected_human.set_species(species_type) holder.del_reagent(type) @@ -3353,7 +3353,7 @@ /datum/reagent/brimdust/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, metabolization_ratio) . = ..() - if(affected_mob.adjust_fire_loss((ispodperson(affected_mob) ? -1 : 1 * seconds_per_tick), updating_health = FALSE)) + if(affected_mob.adjust_fire_loss((affected_mob.mob_biotypes & MOB_PLANT) ? -1 : 1 * seconds_per_tick, updating_health = FALSE)) return UPDATE_MOB_HEALTH /datum/reagent/brimdust/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user) diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index 9066ff6a28b1..3619637a3070 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -476,7 +476,7 @@ randomized_spawns = REAGENT_SPAWN_ALL_RANDOM_SPAWNS /datum/reagent/teslium/energized_jelly/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, metabolization_ratio) - if(!isjellyperson(affected_mob)) //everyone but jellypeople get shocked as normal. + if(!(affected_mob.mob_biotypes & MOB_SLIME)) //everyone but jellypeople get shocked as normal. return ..() affected_mob.AdjustAllImmobility(-20 * metabolization_ratio * seconds_per_tick) if(affected_mob.adjust_stamina_loss(-5 * metabolization_ratio * seconds_per_tick, updating_stamina = FALSE)) diff --git a/code/modules/research/xenobiology/crossbreeding/regenerative.dm b/code/modules/research/xenobiology/crossbreeding/regenerative.dm index 4cc1784b8d00..967780f35c59 100644 --- a/code/modules/research/xenobiology/crossbreeding/regenerative.dm +++ b/code/modules/research/xenobiology/crossbreeding/regenerative.dm @@ -207,7 +207,7 @@ Regenerative extracts: target.visible_message(span_warning("The [target] suddenly changes color!")) var/mob/living/basic/slime/target_slime = target target_slime.set_slime_type() - if(isjellyperson(target)) + if(target.mob_biotypes & MOB_SLIME) target.reagents.add_reagent(/datum/reagent/mutationtoxin/jelly,5) /obj/item/slimecross/regenerative/pink diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 698d8fd43172..653d6b679843 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -1146,6 +1146,8 @@ SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_BODYPART_UPDATED, dropping_limb, is_creating) + if(owner) + SEND_SIGNAL(owner, COMSIG_CARBON_BODYPART_UPDATED, src, dropping_limb, is_creating) if(IS_ORGANIC_LIMB(src)) // Try to add a cached blood type data, we must do it in here because for some reason DNA gets initialized AFTER the mob's limbs are created. diff --git a/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm index e5fe9829bdb3..954eb52ae326 100644 --- a/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/ethereal_bodyparts.dm @@ -9,13 +9,6 @@ brute_modifier = 1.25 //ethereal are weak to brute damage head_flags = HEAD_HAIR|HEAD_FACIAL_HAIR|HEAD_EYESPRITES|HEAD_EYEHOLES|HEAD_DEBRAIN -/obj/item/bodypart/head/ethereal/update_limb(dropping_limb, is_creating) - . = ..() - if(isethereal(owner)) - var/mob/living/carbon/human/potato_oc = owner - var/datum/species/ethereal/eth_holder = potato_oc.dna.species - species_color = eth_holder.current_color - /obj/item/bodypart/chest/ethereal icon_greyscale = 'icons/mob/human/species/ethereal/bodyparts.dmi' limb_id = SPECIES_ETHEREAL @@ -25,13 +18,6 @@ wing_types = null bodypart_traits = list(TRAIT_NO_UNDERWEAR) -/obj/item/bodypart/chest/ethereal/update_limb(dropping_limb, is_creating) - . = ..() - if(isethereal(owner)) - var/mob/living/carbon/human/potato_oc = owner - var/datum/species/ethereal/eth_holder = potato_oc.dna.species - species_color = eth_holder.current_color - /obj/item/bodypart/arm/left/ethereal icon_greyscale = 'icons/mob/human/species/ethereal/bodyparts.dmi' limb_id = SPECIES_ETHEREAL @@ -45,13 +31,6 @@ unarmed_miss_sound = 'sound/items/weapons/etherealmiss.ogg' brute_modifier = 1.25 //ethereal are weak to brute damage -/obj/item/bodypart/arm/left/ethereal/update_limb(dropping_limb, is_creating) - . = ..() - if(isethereal(owner)) - var/mob/living/carbon/human/potato_oc = owner - var/datum/species/ethereal/eth_holder = potato_oc.dna.species - species_color = eth_holder.current_color - /obj/item/bodypart/arm/right/ethereal icon_greyscale = 'icons/mob/human/species/ethereal/bodyparts.dmi' limb_id = SPECIES_ETHEREAL @@ -65,13 +44,6 @@ unarmed_miss_sound = 'sound/items/weapons/etherealmiss.ogg' brute_modifier = 1.25 //ethereal are weak to brute damage -/obj/item/bodypart/arm/right/ethereal/update_limb(dropping_limb, is_creating) - . = ..() - if(isethereal(owner)) - var/mob/living/carbon/human/potato_oc = owner - var/datum/species/ethereal/eth_holder = potato_oc.dna.species - species_color = eth_holder.current_color - /obj/item/bodypart/leg/left/ethereal icon_greyscale = 'icons/mob/human/species/ethereal/bodyparts.dmi' limb_id = SPECIES_ETHEREAL @@ -81,13 +53,6 @@ unarmed_miss_sound = 'sound/items/weapons/etherealmiss.ogg' brute_modifier = 1.25 //ethereal are weak to brute damage -/obj/item/bodypart/leg/left/ethereal/update_limb(dropping_limb, is_creating) - . = ..() - if(isethereal(owner)) - var/mob/living/carbon/human/potato_oc = owner - var/datum/species/ethereal/eth_holder = potato_oc.dna.species - species_color = eth_holder.current_color - /obj/item/bodypart/leg/right/ethereal icon_greyscale = 'icons/mob/human/species/ethereal/bodyparts.dmi' limb_id = SPECIES_ETHEREAL @@ -97,13 +62,6 @@ unarmed_miss_sound = 'sound/items/weapons/etherealmiss.ogg' brute_modifier = 1.25 //ethereal are weak to brute damage -/obj/item/bodypart/leg/right/ethereal/update_limb(dropping_limb, is_creating) - . = ..() - if(isethereal(owner)) - var/mob/living/carbon/human/potato_oc = owner - var/datum/species/ethereal/eth_holder = potato_oc.dna.species - species_color = eth_holder.current_color - /obj/item/bodypart/head/ethereal/lustrous limb_id = SPECIES_ETHEREAL_LUSTROUS head_flags = NONE diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm index bb1462e262ae..b11615043ae4 100644 --- a/code/modules/surgery/organs/internal/liver/_liver.dm +++ b/code/modules/surgery/organs/internal/liver/_liver.dm @@ -393,6 +393,12 @@ owner.reagents.convert_reagent(reagent.type, convert_into, ethanol_conversion) +/obj/item/organ/liver/lizard + name = "saurian liver" + desc = "A liver that has mutated at some point in the past to allow the quasi-carnivore, proto-lizardfolk to metabolize plant nutrients from Tizira's xenoflora. \ + Researchers believe this to be what ultimately allowed the lizardpeople to thrive as a species." + organ_traits = list(TRAIT_LIZARD_METABOLISM) + #undef LIVER_DEFAULT_TOX_TOLERANCE #undef LIVER_DEFAULT_TOX_RESISTANCE #undef LIVER_FAILURE_STAGE_SECONDS diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm index f2c375f9551b..a7432a0bf8af 100644 --- a/code/modules/surgery/organs/internal/stomach/_stomach.dm +++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm @@ -488,6 +488,13 @@ metabolism_efficiency = 0.025 //very bad organ_traits = list(TRAIT_NOHUNGER) +/obj/item/organ/stomach/moth + name = "moth stomach" + desc = "An insectoid stomach adapted to the digestion of textile fibers from the get go. It's estimated that a young mothperson will eat 30 times their body weight in cloth \ + before their stomach can fully produce the enzymes required to digest other matter as well." + icon_state = "spinner-x" + organ_traits = list(TRAIT_CLOTH_EATER) + /obj/item/organ/stomach/bone/plasmaman name = "digestive crystal" desc = "A strange crystal that is responsible for metabolizing the unseen energy force that feeds plasmamen." diff --git a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm index 6255b5dec3d5..8030eece6968 100644 --- a/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm +++ b/code/modules/surgery/organs/internal/stomach/stomach_ethereal.dm @@ -2,7 +2,7 @@ name = "biological battery" icon_state = "stomach-p" //Welp. At least it's more unique in functionaliy. desc = "A crystal-like organ that stores the electric charge of ethereals." - organ_traits = list(TRAIT_NOHUNGER) // We have our own hunger mechanic. + organ_traits = list(TRAIT_NOHUNGER, TRAIT_ETHEREAL_METABOLISM) // We have our own hunger mechanic. /// Where the energy of the stomach is stored. var/obj/item/stock_parts/power_store/cell ///used to keep ethereals from spam draining power sources From 11bafd748f09d381bb70ab1636c950cf9d3d4208 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:53:45 +0000 Subject: [PATCH 383/445] Automatic changelog for PR #96956 [ci skip] --- html/changelogs/AutoChangeLog-pr-96956.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-96956.yml diff --git a/html/changelogs/AutoChangeLog-pr-96956.yml b/html/changelogs/AutoChangeLog-pr-96956.yml new file mode 100644 index 000000000000..58cdc953bfb1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-96956.yml @@ -0,0 +1,5 @@ +author: "Ghommie" +delete-after: True +changes: + - balance: "Bees are now friendly to any plant-like creature for that matter." + - balance: "Cats will now also follow laser pointers, not just felinids." \ No newline at end of file From 85a5cf743ab4d1335e6a1fcc2c5a99a410818614 Mon Sep 17 00:00:00 2001 From: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Date: Sat, 1 Aug 2026 10:54:35 -0700 Subject: [PATCH 384/445] Adds profiling for our pooling subsystem, alongside some code cleanup (#97156) ## About The Pull Request Pulled over from an old pr of mine. The idea is to just smash everything in the subsystem at the wall X times and see how pooling compares to normal operations. ## Why It's Good For The Game I'm pring this now because with the tracy stuff we have more interest in pooling, and this is an important part of not accidentially pooling rods to try and speed up explosion code (it's slower, I promise, this is how I found out) --- code/controllers/subsystem/wardrobe.dm | 148 ++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 14 deletions(-) diff --git a/code/controllers/subsystem/wardrobe.dm b/code/controllers/subsystem/wardrobe.dm index f394c9b30d2b..7202227190d9 100644 --- a/code/controllers/subsystem/wardrobe.dm +++ b/code/controllers/subsystem/wardrobe.dm @@ -41,6 +41,9 @@ SUBSYSTEM_DEF(wardrobe) var/stock_hit = 0 /// How many items would we make just by loading the master list once? var/one_go_master = 0 + // Hit/Miss Tracking + var/list/hit_map = list() + var/list/miss_map = list() /// Item types that should not ever be recycled, only generated (like modsuits) var/static/list/recycle_blacklist = typecacheof(list( /obj/item/mod/control/pre_equipped, @@ -98,7 +101,7 @@ SUBSYSTEM_DEF(wardrobe) order_list[type_to_stock] = (amount_to_stock - (i - 1)) // Account for types we've already created return var/atom/movable/new_member = new type_to_stock() - stash_object(new_member) + yield_object(new_member) order_list -= type_to_stock if(MC_TICK_CHECK) @@ -163,7 +166,7 @@ SUBSYSTEM_DEF(wardrobe) * * type to stock - What type exactly do you want us to remember? * */ -/datum/controller/subsystem/wardrobe/proc/canonize_type(type_to_stock) +/datum/controller/subsystem/wardrobe/proc/canonize_type(type_to_stock, amount = 1) if(!type_to_stock) return if(!ispath(type_to_stock)) @@ -179,7 +182,7 @@ SUBSYSTEM_DEF(wardrobe) master_info[WARDROBE_CACHE_CALL_INSERT] = callback_info[WARDROBE_CALLBACK_INSERT] master_info[WARDROBE_CACHE_CALL_REMOVAL] = callback_info[WARDROBE_CALLBACK_REMOVE] canon_minimum[type_to_stock] = master_info - master_info[WARDROBE_CACHE_COUNT] += 1 + master_info[WARDROBE_CACHE_COUNT] += amount one_go_master++ /datum/controller/subsystem/wardrobe/proc/add_queue_item(queued_type, amount) @@ -217,17 +220,21 @@ SUBSYSTEM_DEF(wardrobe) if(is_type_in_typecache(object, recycle_blacklist)) qdel(object) return - stash_object(object) + yield_object(object) -/// Take an existing object, and insert it into our storage -/// If we can't or won't take it, it's deleted. You do not own this object after passing it in +/// Yields an object to the wardrobe subsystem, you no longer own it after calling this proc +/datum/controller/subsystem/wardrobe/proc/yield_object(obj/item/object) + if(!stash_object(object)) + qdel(object) + +/// Take an existing object, and tries to insert it into our storage +/// Returns TRUE if we've taken it (you no longer own it), FALSE if we have not /datum/controller/subsystem/wardrobe/proc/stash_object(obj/item/object) var/object_type = object.type var/list/master_info = canon_minimum[object_type] // I will not permit objects you didn't reserve ahead of time if(!master_info) - qdel(object) - return + return FALSE var/stock_target = master_info[WARDROBE_CACHE_COUNT] * cache_intensity var/amount_held = 0 @@ -237,12 +244,11 @@ SUBSYSTEM_DEF(wardrobe) // Doublely so for things we already have too much of if(amount_held - stock_target >= overflow_lienency) - qdel(object) - return + return FALSE // Fuck off if(QDELETED(object)) stack_trace("We tried to stash a qdeleted object, what did you do") - return + return FALSE if(!stock_info) stock_info = new /list(WARDROBE_STOCK_CALL_REMOVAL) @@ -251,20 +257,26 @@ SUBSYSTEM_DEF(wardrobe) stock_info[WARDROBE_STOCK_CALL_REMOVAL] = master_info[WARDROBE_CACHE_CALL_REMOVAL] preloaded_stock[object_type] = stock_info + if(object.loc != null) + object.moveToNullspace() var/datum/callback/do_on_insert = stock_info[WARDROBE_STOCK_CALL_INSERT] if(do_on_insert) do_on_insert.object = object do_on_insert.Invoke() do_on_insert.object = null - object.moveToNullspace() stock_info[WARDROBE_STOCK_CONTENTS] += object + return TRUE /datum/controller/subsystem/wardrobe/proc/provide_type(datum/requested_type, atom/movable/location) var/atom/movable/requested_object + if(!canon_minimum[requested_type]) + return new requested_type(location) + var/list/stock_info = preloaded_stock[requested_type] if(!stock_info) stock_miss++ + miss_map[requested_type]++ requested_object = new requested_type(location) return requested_object @@ -289,6 +301,7 @@ SUBSYSTEM_DEF(wardrobe) do_on_removal.object = null stock_hit++ + hit_map[requested_type]++ add_queue_item(requested_type, 1) // Requeue the item, under the assumption we'll never see it again if(!(contents_length - 1)) preloaded_stock -= requested_type @@ -313,6 +326,113 @@ SUBSYSTEM_DEF(wardrobe) if(!length(stock_info[WARDROBE_STOCK_CONTENTS])) preloaded_stock -= unload_type +/// Runs a speed test for all our various types +/// Measures the average cost of providing vs spawning, and deletion vs stashing +/datum/controller/subsystem/wardrobe/proc/speed_test(turf/spawn_on, attempt_count = 200) + var/old_overflow_lienency = overflow_lienency + overflow_lienency = INFINITY + + // Stock everything I want IMMEDIATELY + force_stock_wardrobe(attempt_count) + + // List of lists in the form list(type, new_cost, provide_cost, qdel_cost, stash_cost) + var/list/type_info = list() + for(var/atom/movable/type_to_test as anything in preloaded_stock) + var/new_cost = 0 + var/provide_cost = 0 + var/qdel_cost = 0 + var/stash_cost = 0 + var/stash_success = FALSE + for(var/i in 1 to attempt_count) + // Stopwatches are one per proc, so lets give them their own scope + do { + STAT_START_STOPWATCH + var/atom/hh = new type_to_test(spawn_on) + STAT_STOP_STOPWATCH + new_cost += STAT_TIME + qdel(hh) + } while(FALSE) + // In case subobjects are also tracked, this avoids any mistakes + force_stock_wardrobe(attempt_count) + // Now provide() + do { + STAT_START_STOPWATCH + var/atom/hh = SSwardrobe.provide_type(type_to_test, spawn_on) + STAT_STOP_STOPWATCH + provide_cost += STAT_TIME + qdel(hh) + } while(FALSE) + force_stock_wardrobe(attempt_count) + // qdel() + do { + var/atom/hh = SSwardrobe.provide_type(type_to_test, spawn_on) + STAT_START_STOPWATCH + /// Assignment is meaningless, just to make the comparison the same for stashing + stash_success = qdel(hh) + STAT_STOP_STOPWATCH + qdel_cost += STAT_TIME + } while(FALSE) + force_stock_wardrobe(attempt_count) + // stash_object() + do { + var/atom/hh = SSwardrobe.provide_type(type_to_test, spawn_on) + STAT_START_STOPWATCH + stash_success = SSwardrobe.stash_object(hh) + STAT_STOP_STOPWATCH + if(!stash_success) + qdel(hh) + stash_cost += STAT_TIME + } while(FALSE) + force_stock_wardrobe(attempt_count) + type_info += list(list(type_to_test, new_cost, provide_cost, qdel_cost, stash_cost)) + + overflow_lienency = old_overflow_lienency + // Ok now we have our data, time to print it + var/list/wardrobe_info = list("Performance information ([attempt_count] runs)

    ") + sortTim(type_info, cmp=/proc/cmp_wardrobe_performance) + for(var/list/deets in type_info) + wardrobe_info += "
  1. [deets[1]]
      " + wardrobe_info += "
    • New: [deets[2]]ms
    • " + wardrobe_info += "
    • Provide: [deets[3]]ms
    • " + wardrobe_info += "
    • Qdel: [deets[4]]ms
    • " + wardrobe_info += "
    • Stash: [deets[5]]ms
    • " + wardrobe_info += "
  2. " + wardrobe_info += "
" + + usr << browse(wardrobe_info.Join(), "window=wardrobe_perf") + +/proc/cmp_wardrobe_performance(list/A, list/B) + var/create_delta_a = A[3] - A[2] + var/create_delta_b = B[3] - B[2] + var/del_delta_a = A[5] - A[4] + var/del_delta_b = B[5] - B[4] + if((create_delta_a + del_delta_a) / 2 > (create_delta_b + del_delta_b) / 2) + return 1 + return -1 + +/// Stocks the wardrobe to stock_to items, no more no less +/// Only usable for testing, will not work well in production as it'll likely just get run over by other code +/datum/controller/subsystem/wardrobe/proc/force_stock_wardrobe(stock_to) + for(var/datum/loaded_type as anything in canon_minimum) + var/list/stock_info = preloaded_stock[loaded_type] + var/amount_held = 0 + if(stock_info) + amount_held = length(stock_info[WARDROBE_STOCK_CONTENTS]) + + var/target_delta = amount_held - stock_to + + // If we've got anything over the line, cut it back down + if(target_delta > 0) + unload_stock(loaded_type, target_delta, force = TRUE) + continue + // If we have more then we target, just don't you feel me? + else if (target_delta == 0) + continue + + // If we don't have enough, queue enough to make up the remainder + for(var/i in 1 to abs(target_delta)) + yield_object(new loaded_type()) + /// Sets up insertion and removal callbacks by typepath /// We will always use the deepest path. So /obj/item/blade/knife superceeds the entries of /obj/item and /obj/item/blade /// Mind this @@ -343,8 +463,8 @@ SUBSYSTEM_DEF(wardrobe) continue var/datum/species/fossil_record = new to_record() for(var/obj/item/species_request as anything in fossil_record.get_types_to_preload()) - for(var/i in 1 to 5) // Store 5 of each species, since that seems on par with 1 of each outfit - canonize_type(species_request) + // Store 5 of each species, since that seems on par with 1 of each outfit + canonize_type(species_request, 5) CHECK_TICK /datum/controller/subsystem/wardrobe/proc/load_storage_contents() From a55f5f4d7417b458b60295f73e59fab0f4611d65 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 18:00:30 +0000 Subject: [PATCH 385/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-96945.yml | 5 ----- html/changelogs/AutoChangeLog-pr-96956.yml | 5 ----- html/changelogs/AutoChangeLog-pr-97265.yml | 5 ----- html/changelogs/archive/2026-08.yml | 14 ++++++++++++++ 4 files changed, 14 insertions(+), 15 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-96945.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-96956.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97265.yml diff --git a/html/changelogs/AutoChangeLog-pr-96945.yml b/html/changelogs/AutoChangeLog-pr-96945.yml deleted file mode 100644 index ab9de131dce4..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96945.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - balance: "The boulder processing machinery can maim any (incapacitated/dead) mob with the mineral biotype, not just golems." - - refactor: "some quirky interactions with specific (non-roundstart) species have been de-hardcoded. An example: peanut butter gets those with abductor brains high rather than just those with the abductor species. Report any oddity with that (the refactor, not the peanut butter...)." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96956.yml b/html/changelogs/AutoChangeLog-pr-96956.yml deleted file mode 100644 index 58cdc953bfb1..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96956.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - balance: "Bees are now friendly to any plant-like creature for that matter." - - balance: "Cats will now also follow laser pointers, not just felinids." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97265.yml b/html/changelogs/AutoChangeLog-pr-97265.yml deleted file mode 100644 index b29f69b17cca..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97265.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "flleeppyy" -delete-after: True -changes: - - bugfix: "the experimental cloner now actually can make you bald (if you're a human or ethereal)." - - bugfix: "the experimental cloner now spits flavor and policy text (if there is any) when spawning" \ No newline at end of file diff --git a/html/changelogs/archive/2026-08.yml b/html/changelogs/archive/2026-08.yml index 353b81980df9..15e1a1d8f46f 100644 --- a/html/changelogs/archive/2026-08.yml +++ b/html/changelogs/archive/2026-08.yml @@ -4,8 +4,22 @@ scale more closely with emp severity. - balance: reactive tesla armor, adamantine armor, the mod emp shield module, and tinfoil hats slightly lower the severity of incoming EMPs. + Ghommie: + - balance: Bees are now friendly to any plant-like creature for that matter. + - balance: Cats will now also follow laser pointers, not just felinids. + - balance: The boulder processing machinery can maim any (incapacitated/dead) mob + with the mineral biotype, not just golems. + - refactor: 'some quirky interactions with specific (non-roundstart) species have + been de-hardcoded. An example: peanut butter gets those with abductor brains + high rather than just those with the abductor species. Report any oddity with + that (the refactor, not the peanut butter...).' QuiteLiterallyAnything: - bugfix: Fixed Catwalk's upper atmospherics room being exposed to space by default. Thunder12345: - rscadd: The quantum console now tells bitrunners what grade they've reached on completed domains. + flleeppyy: + - bugfix: the experimental cloner now actually can make you bald (if you're a human + or ethereal). + - bugfix: the experimental cloner now spits flavor and policy text (if there is + any) when spawning From f4e350e2a0158ef5c726c5207bf3f6dc5a28dfed Mon Sep 17 00:00:00 2001 From: CabinetOnFire <279736348+CabinetOnFire@users.noreply.github.com> Date: Sat, 1 Aug 2026 20:15:40 +0200 Subject: [PATCH 386/445] Adds "do-once" decorator in BT (#97254) ## About The Pull Request Something goof needed for his mobs. Useful for effects that should only happen once. This decorator basically locks once the child has ran, optionally only locking if the child succeeded. Do-once can be reset by calling UNLOCK_DO_ONCE or running /datum/bt_node/ai_behavior/unlock_do_once ## Why It's Good For The Game useful for specific behaviors that should run only once (e.g. only play you roar animation the first time you enter combat) --- code/__DEFINES/ai/ai.dm | 2 ++ .../ai/generic_behaviors/unlock_do_once.dm | 8 ++++++++ code/datums/ai/generic_decorators/do_once.dm | 20 +++++++++++++++++++ tgstation.dme | 2 ++ 4 files changed, 32 insertions(+) create mode 100644 code/datums/ai/generic_behaviors/unlock_do_once.dm create mode 100644 code/datums/ai/generic_decorators/do_once.dm diff --git a/code/__DEFINES/ai/ai.dm b/code/__DEFINES/ai/ai.dm index e7372a62773d..a8c3371f5d21 100644 --- a/code/__DEFINES/ai/ai.dm +++ b/code/__DEFINES/ai/ai.dm @@ -1,6 +1,8 @@ #define GET_TARGETING_STRATEGY(targeting_type) SSai_controllers.targeting_strategies[targeting_type] #define GET_TARGET_PRIORITY_STRATEGY(targeting_type) SSai_controllers.target_priority_strategies[targeting_type] #define GET_TARGET_SOURCE(source_type) SSai_controllers.target_sources[source_type] +/// Clears the blackboard latch used by a [/datum/bt_node/decorator/do_once]. +#define UNLOCK_DO_ONCE(controller, lock_key) ##controller.clear_blackboard_key(lock_key) /** * Returns TRUE if the target should be rejected based on factions. diff --git a/code/datums/ai/generic_behaviors/unlock_do_once.dm b/code/datums/ai/generic_behaviors/unlock_do_once.dm new file mode 100644 index 000000000000..2adb2776f16e --- /dev/null +++ b/code/datums/ai/generic_behaviors/unlock_do_once.dm @@ -0,0 +1,8 @@ +/// Clears a do-once being locked +/datum/bt_node/ai_behavior/unlock_do_once + /// Blackboard key configured on the corresponding do_once decorator. + var/lock_key + +/datum/bt_node/ai_behavior/unlock_do_once/perform(seconds_per_tick, datum/ai_controller/controller) + UNLOCK_DO_ONCE(controller, lock_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_decorators/do_once.dm b/code/datums/ai/generic_decorators/do_once.dm new file mode 100644 index 000000000000..d2d97776b0ee --- /dev/null +++ b/code/datums/ai/generic_decorators/do_once.dm @@ -0,0 +1,20 @@ +///Only returns TRUE the first time. Optionally only locks if the child succeeds. Can be unlocked again +/datum/bt_node/decorator/do_once + /// Blackboard key storing the completion latch. + var/lock_key + /// Whether a child FAILURE should leave this decorator available for another attempt. + var/only_lock_if_succeeded = FALSE + +/datum/bt_node/decorator/do_once/check_condition(datum/ai_controller/controller) + return !controller.blackboard_key_exists(lock_key) + +/datum/bt_node/decorator/do_once/on_child_complete(datum/ai_controller/controller, result) + if(!only_lock_if_succeeded || result == BT_SUCCESS) + controller.set_blackboard_key(lock_key, TRUE) + +/datum/bt_node/decorator/do_once/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(lock_key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(lock_key)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/do_once/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(lock_key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(lock_key))) diff --git a/tgstation.dme b/tgstation.dme index fff62c6cbdc9..5bda428edc0d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1124,6 +1124,7 @@ #include "code\datums\ai\generic_behaviors\stuff_in_disposal.dm" #include "code\datums\ai\generic_behaviors\succeed.dm" #include "code\datums\ai\generic_behaviors\target_retaliate.dm" +#include "code\datums\ai\generic_behaviors\unlock_do_once.dm" #include "code\datums\ai\generic_behaviors\use_in_hand.dm" #include "code\datums\ai\generic_behaviors\use_on_object.dm" #include "code\datums\ai\generic_behaviors\virtual_pick_up_item.dm" @@ -1139,6 +1140,7 @@ #include "code\datums\ai\generic_decorators\check_cooldown.dm" #include "code\datums\ai\generic_decorators\check_rider_stat.dm" #include "code\datums\ai\generic_decorators\container_attackable.dm" +#include "code\datums\ai\generic_decorators\do_once.dm" #include "code\datums\ai\generic_decorators\is_at_distance.dm" #include "code\datums\ai\generic_decorators\is_dragging.dm" #include "code\datums\ai\generic_decorators\is_grabbing_target.dm" From 87671aec22d4dbc36e3d65de55e49475e23c3c5e Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sat, 1 Aug 2026 20:44:27 +0200 Subject: [PATCH 387/445] Refactors blessing into an element/trait combo rather than an object (#97204) ## About The Pull Request Currently turf blessing is an object that is ``locate()``d whenever we want to check if a turf is blessed or not, there's no reason not to have it be a trait and handle cult TP blocking and chaplain-exclusive visuals via an element, which is what this PR does. ``/obj/effect/blessing`` was kept for mapping purposes as we use it on a few maps I believe, but it just assigns the element to the turf and deletes itself right after. ## Why It's Good For The Game Don't hog memory with useless objects or waste time on their creation whenever one of the domains that uses blessings loads/we bless turfs in an AOE with a smoke machine if we can handle it with an element/trait combo, makes checking for blessings more intuitive (trait check vs object locate) --- _maps/RandomZLevels/heretic.dmm | 483 +++++++++--------- .../signals/signals_atom/signals_atom_main.dm | 1 + code/__DEFINES/traits/declarations.dm | 2 + code/_globalvars/traits/_traits.dm | 1 + code/datums/components/chasm.dm | 2 +- code/datums/elements/blessed_turf.dm | 36 ++ code/datums/helper_datums/teleport.dm | 2 +- code/game/objects/effects/blessing.dm | 37 -- code/game/turfs/turf.dm | 7 +- code/modules/antagonists/cult/cult_items.dm | 58 ++- code/modules/mapping/mapping_helpers.dm | 18 + .../basic/space_fauna/revenant/_revenant.dm | 2 +- .../revenant/revenant_abilities.dm | 5 +- .../carbon/human/species_types/ghost.dm | 8 +- code/modules/mob/mob_movement.dm | 2 +- .../chemistry/reagents/other_reagents.dm | 2 +- .../spell_types/jaunt/ethereal_jaunt.dm | 3 +- tgstation.dme | 2 +- .../Scripts/97204_turf_blessing.txt | 1 + 19 files changed, 350 insertions(+), 322 deletions(-) create mode 100644 code/datums/elements/blessed_turf.dm delete mode 100644 code/game/objects/effects/blessing.dm create mode 100644 tools/UpdatePaths/Scripts/97204_turf_blessing.txt diff --git a/_maps/RandomZLevels/heretic.dmm b/_maps/RandomZLevels/heretic.dmm index 86b568994eb6..c64a0abbb0e7 100644 --- a/_maps/RandomZLevels/heretic.dmm +++ b/_maps/RandomZLevels/heretic.dmm @@ -4,14 +4,14 @@ /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "af" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "ah" = ( @@ -32,7 +32,7 @@ "al" = ( /obj/item/radio/intercom/mi13/directional/north, /obj/effect/turf_decal/tile/dark/opposingcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "am" = ( @@ -110,12 +110,12 @@ "aB" = ( /obj/item/kirbyplants/organic/plant10, /obj/effect/turf_decal/tile/green, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "aC" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "aD" = ( @@ -175,7 +175,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "aR" = ( @@ -217,14 +217,14 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory) "be" = ( /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "bf" = ( @@ -262,14 +262,14 @@ /obj/structure/bed/double{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "bt" = ( /obj/effect/turf_decal/tile/bar/anticorner/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "bu" = ( @@ -282,7 +282,7 @@ /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "by" = ( @@ -317,7 +317,7 @@ /area/awaymission/caves/heretic_laboratory) "bJ" = ( /obj/machinery/light/directional/north, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "bK" = ( @@ -379,7 +379,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/blue/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "ck" = ( @@ -465,7 +465,7 @@ /area/awaymission/caves/heretic_laboratory) "cK" = ( /obj/effect/turf_decal/tile/bar/anticorner/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "cL" = ( @@ -476,7 +476,7 @@ /obj/effect/turf_decal/tile/bar/anticorner/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "cO" = ( @@ -516,7 +516,7 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "cX" = ( @@ -531,7 +531,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "dd" = ( @@ -555,7 +555,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/bar/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "dk" = ( @@ -585,12 +585,12 @@ dir = 1 }, /obj/effect/turf_decal/stripes/line, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "dr" = ( /obj/structure/table, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory_clean) "du" = ( @@ -604,7 +604,7 @@ /area/awaymission/caves/heretic_laboratory) "dw" = ( /obj/machinery/light/directional/west, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "dy" = ( @@ -633,7 +633,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "dF" = ( @@ -664,7 +664,7 @@ /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "dO" = ( @@ -707,7 +707,7 @@ /area/awaymission/beach/heretic) "dY" = ( /obj/structure/bookcase/random, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory_clean) "eg" = ( @@ -831,7 +831,7 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "eQ" = ( @@ -846,7 +846,7 @@ }, /obj/effect/turf_decal/stripes/corner, /obj/effect/turf_decal/tile/bar/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "eT" = ( @@ -859,7 +859,7 @@ "eW" = ( /obj/structure/table, /obj/item/ammo_box/magazine/m7mm, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "eY" = ( @@ -916,7 +916,7 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "fo" = ( @@ -936,7 +936,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "fu" = ( @@ -1052,7 +1052,7 @@ /obj/effect/decal/cleanable/blood/tracks{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "fP" = ( @@ -1081,7 +1081,7 @@ /obj/effect/turf_decal/tile/bar/anticorner/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "fW" = ( @@ -1143,7 +1143,7 @@ /obj/effect/turf_decal/tile/dark_red/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory) "gi" = ( @@ -1260,7 +1260,7 @@ dir = 1 }, /obj/effect/turf_decal/tile/bar/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "gK" = ( @@ -1279,7 +1279,7 @@ id = "crematoriumchapel"; dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory_clean) "gQ" = ( @@ -1296,7 +1296,7 @@ dir = 1 }, /obj/structure/door_assembly/door_assembly_highsecurity, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "gT" = ( @@ -1323,7 +1323,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "gY" = ( @@ -1566,7 +1566,7 @@ /obj/effect/turf_decal/tile/dark{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "ij" = ( @@ -1641,7 +1641,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/dark_green/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "iF" = ( @@ -1695,7 +1695,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "iO" = ( @@ -1711,7 +1711,7 @@ "iQ" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/catwalk_floor/iron_dark, /area/awaymission/caves/heretic_laboratory) "iR" = ( @@ -1734,7 +1734,7 @@ pixel_x = 17; pixel_y = 0 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "iW" = ( @@ -1765,7 +1765,7 @@ /obj/effect/turf_decal/tile/bar/anticorner/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "jc" = ( @@ -1775,7 +1775,7 @@ /obj/effect/turf_decal/tile/dark/half{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "je" = ( @@ -1877,7 +1877,7 @@ "jO" = ( /obj/machinery/vending/medical, /obj/effect/turf_decal/tile/green/anticorner/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "jP" = ( @@ -1913,7 +1913,7 @@ /obj/effect/turf_decal/tile/dark/half{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "jX" = ( @@ -2066,7 +2066,7 @@ "kO" = ( /obj/structure/table, /obj/item/fake_items/l6_saw, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "kQ" = ( @@ -2144,14 +2144,14 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "lt" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/light/directional/east, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "lu" = ( @@ -2166,7 +2166,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/bar/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "lz" = ( @@ -2209,7 +2209,7 @@ "lH" = ( /obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, /obj/effect/mapping_helpers/damaged_window, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory) "lJ" = ( @@ -2219,14 +2219,14 @@ /obj/effect/turf_decal/tile/dark{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "lK" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/blue/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "lL" = ( @@ -2241,7 +2241,7 @@ "lV" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "lW" = ( @@ -2267,7 +2267,7 @@ /obj/structure/bed/double, /obj/effect/spawner/random/bedsheet/double, /obj/effect/turf_decal/tile/bar/anticorner/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "lZ" = ( @@ -2388,7 +2388,7 @@ /area/awaymission/caves/heretic_laboratory) "mJ" = ( /obj/machinery/suit_storage_unit/open, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "mL" = ( @@ -2399,7 +2399,7 @@ /obj/effect/turf_decal/tile/bar{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "mM" = ( @@ -2492,7 +2492,7 @@ /obj/effect/turf_decal/tile/dark_red/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory) "nj" = ( @@ -2538,7 +2538,7 @@ dir = 1 }, /obj/item/radio/intercom/mi13/directional/west, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "ny" = ( @@ -2552,7 +2552,7 @@ /obj/effect/turf_decal/tile/dark_red/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "nD" = ( @@ -2627,7 +2627,7 @@ /obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "nR" = ( @@ -2730,7 +2730,7 @@ /obj/machinery/light/floor, /obj/machinery/light/directional/east, /obj/structure/bed/double, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "os" = ( @@ -2755,7 +2755,7 @@ /obj/structure/rack, /obj/item/gun/energy/disabler/smg, /obj/effect/turf_decal/tile/dark/opposingcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "oC" = ( @@ -2766,7 +2766,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "oF" = ( @@ -2788,7 +2788,7 @@ /obj/effect/turf_decal/tile/dark_green/half{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "oN" = ( @@ -2813,7 +2813,7 @@ "oQ" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "oS" = ( @@ -2961,7 +2961,7 @@ /obj/effect/turf_decal/tile/dark_red/anticorner/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "pv" = ( @@ -3034,12 +3034,12 @@ /area/awaymission/caves/heretic_laboratory) "pJ" = ( /mob/living/basic/bot/cleanbot, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory_clean) "pL" = ( /obj/machinery/light/directional/south, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "pN" = ( @@ -3079,7 +3079,7 @@ /area/awaymission/caves/heretic_laboratory_clean) "pW" = ( /obj/structure/table, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "pX" = ( @@ -3100,7 +3100,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory) "qc" = ( @@ -3110,13 +3110,13 @@ /obj/effect/turf_decal/tile/dark_green/half{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "qe" = ( /obj/structure/closet/secure_closet/personal/cabinet, /obj/effect/spawner/random/heretic_gateway, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "qg" = ( @@ -3236,7 +3236,7 @@ "qL" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "qM" = ( @@ -3280,7 +3280,7 @@ /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "qW" = ( @@ -3303,7 +3303,7 @@ /obj/effect/turf_decal/stripes/corner{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "rc" = ( @@ -3315,7 +3315,7 @@ /area/awaymission/beach/heretic) "rf" = ( /obj/machinery/light/directional/west, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory_clean) "rh" = ( @@ -3364,13 +3364,13 @@ /obj/structure/bed/double, /obj/effect/spawner/random/bedsheet/double, /obj/effect/turf_decal/tile/dark_green/anticorner/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "ry" = ( /obj/structure/table, /obj/item/book/bible, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory_clean) "rA" = ( @@ -3532,7 +3532,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/stripes/line, /obj/effect/turf_decal/tile/bar/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "sp" = ( @@ -3657,7 +3657,7 @@ /obj/effect/turf_decal/tile/dark_green/half{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "sZ" = ( @@ -3665,7 +3665,7 @@ /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "ta" = ( @@ -3710,7 +3710,7 @@ /area/awaymission/beach/heretic) "tl" = ( /obj/effect/spawner/structure/window/reinforced/plasma/plastitanium, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory) "tm" = ( @@ -3733,7 +3733,7 @@ /area/awaymission/beach/heretic) "ts" = ( /obj/machinery/light/directional/west, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "ty" = ( @@ -3800,7 +3800,7 @@ /turf/open/water/beach, /area/awaymission/beach/heretic) "tS" = ( -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "tT" = ( @@ -3834,7 +3834,7 @@ dir = 4 }, /obj/effect/turf_decal/tile/bar/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "uc" = ( @@ -3873,7 +3873,7 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "ul" = ( @@ -4003,7 +4003,7 @@ "uQ" = ( /obj/machinery/light/directional/south, /obj/effect/turf_decal/tile/dark_green/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "uR" = ( @@ -4061,7 +4061,7 @@ /area/awaymission/beach/heretic) "vf" = ( /obj/structure/closet/secure_closet/personal/cabinet, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "vj" = ( @@ -4070,7 +4070,7 @@ /turf/open/floor/iron/white, /area/awaymission/caves/heretic_laboratory) "vk" = ( -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory_clean) "vm" = ( @@ -4133,7 +4133,7 @@ pixel_y = 4 }, /obj/effect/turf_decal/tile/dark/opposingcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "vw" = ( @@ -4292,7 +4292,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "ws" = ( @@ -4329,7 +4329,7 @@ /obj/effect/turf_decal/tile/green/anticorner/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "wz" = ( @@ -4351,7 +4351,7 @@ /obj/effect/turf_decal/tile/purple/half{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "wE" = ( @@ -4386,7 +4386,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "wJ" = ( @@ -4463,7 +4463,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "wY" = ( @@ -4504,7 +4504,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "xd" = ( @@ -4527,7 +4527,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "xk" = ( @@ -4549,7 +4549,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "xn" = ( @@ -4649,7 +4649,7 @@ /obj/structure/bed/double{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "xJ" = ( @@ -4688,7 +4688,7 @@ /obj/effect/turf_decal/stripes/corner{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "xQ" = ( @@ -4703,7 +4703,7 @@ "xT" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "xW" = ( @@ -4740,7 +4740,7 @@ /obj/item/ammo_box/magazine/lahtimagazine{ anchored = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "ye" = ( @@ -4845,7 +4845,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "yJ" = ( @@ -4875,7 +4875,7 @@ dir = 8 }, /obj/item/radio/intercom/mi13/directional/west, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "yN" = ( @@ -4930,7 +4930,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "zb" = ( @@ -4995,7 +4995,7 @@ /area/awaymission/beach/heretic) "zE" = ( /obj/structure/bookcase/random, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "zF" = ( @@ -5167,7 +5167,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "AD" = ( @@ -5177,7 +5177,7 @@ "AG" = ( /obj/structure/bookcase/random, /obj/machinery/light/directional/east, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory_clean) "AI" = ( @@ -5187,7 +5187,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "AJ" = ( @@ -5231,7 +5231,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "AR" = ( @@ -5343,7 +5343,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/bar/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "By" = ( @@ -5375,7 +5375,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "BS" = ( @@ -5383,7 +5383,7 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "BU" = ( @@ -5439,7 +5439,7 @@ /area/awaymission/caves/heretic_laboratory) "Cm" = ( /mob/living/basic/heretic_summon/stalker, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Cn" = ( @@ -5482,7 +5482,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/dark_green/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Cy" = ( @@ -5528,7 +5528,7 @@ /area/awaymission/beach/heretic) "CG" = ( /obj/effect/turf_decal/tile/yellow/anticorner/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "CI" = ( @@ -5561,7 +5561,7 @@ /turf/open/floor/asphalt, /area/awaymission/beach/heretic) "CT" = ( -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "CU" = ( @@ -5583,7 +5583,7 @@ /area/awaymission/beach/heretic) "CX" = ( /obj/machinery/light/directional/east, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "CZ" = ( @@ -5596,7 +5596,7 @@ /obj/effect/turf_decal/stripes/corner{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Dc" = ( @@ -5617,7 +5617,7 @@ /area/awaymission/caves/heretic_laboratory) "Di" = ( /obj/structure/closet/secure_closet/personal/cabinet, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "Dj" = ( @@ -5653,7 +5653,7 @@ /obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Dt" = ( @@ -5694,20 +5694,20 @@ dir = 8 }, /obj/effect/turf_decal/tile/bar/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "DC" = ( /obj/structure/bookcase/random, /obj/machinery/light/directional/west, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "DG" = ( /obj/effect/turf_decal/tile/purple/half{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "DJ" = ( @@ -5748,7 +5748,7 @@ /obj/effect/decal/cleanable/blood/tracks{ dir = 6 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/catwalk_floor/iron_dark, /area/awaymission/caves/heretic_laboratory) "DX" = ( @@ -5765,7 +5765,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "DZ" = ( @@ -5787,7 +5787,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory) "Ec" = ( @@ -5831,7 +5831,7 @@ /obj/effect/turf_decal/stripes/corner{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Es" = ( @@ -5975,7 +5975,7 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Fa" = ( @@ -6032,7 +6032,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory) "Fn" = ( @@ -6040,7 +6040,7 @@ /obj/effect/turf_decal/tile/green{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Fr" = ( @@ -6088,13 +6088,13 @@ /obj/effect/turf_decal/tile/dark_green/anticorner/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "FC" = ( /obj/machinery/light/directional/south, /obj/effect/turf_decal/tile/bar/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "FD" = ( @@ -6102,7 +6102,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "FF" = ( @@ -6118,7 +6118,7 @@ /obj/machinery/light/directional/south, /obj/effect/turf_decal/stripes/corner, /obj/effect/turf_decal/tile/bar, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "FM" = ( @@ -6146,7 +6146,7 @@ "FR" = ( /obj/machinery/recharge_station, /obj/effect/turf_decal/tile/dark/opposingcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "FS" = ( @@ -6186,7 +6186,7 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Ga" = ( @@ -6216,7 +6216,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory) "Gg" = ( @@ -6256,7 +6256,7 @@ /area/awaymission/beach/heretic) "Gp" = ( /mob/living/basic/bot/medbot/mysterious, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "Gq" = ( @@ -6272,7 +6272,7 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Gu" = ( @@ -6303,7 +6303,7 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Gz" = ( @@ -6326,12 +6326,12 @@ "GE" = ( /obj/machinery/light/directional/east, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "GF" = ( /obj/structure/dresser, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "GH" = ( @@ -6405,12 +6405,12 @@ /obj/effect/turf_decal/tile/bar/anticorner/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "GZ" = ( /obj/structure/rack, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "Hb" = ( @@ -6420,7 +6420,7 @@ /obj/structure/table, /obj/machinery/light/directional/east, /obj/item/ammo_box/magazine/m7mm, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "Hf" = ( @@ -6487,7 +6487,7 @@ /obj/effect/turf_decal/tile/dark_red/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Hr" = ( @@ -6543,7 +6543,7 @@ /turf/open/floor/plating, /area/awaymission/beach/heretic) "HC" = ( -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/catwalk_floor/iron_dark, /area/awaymission/caves/heretic_laboratory) "HF" = ( @@ -6559,7 +6559,7 @@ /area/awaymission/beach/heretic) "HJ" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "HL" = ( @@ -6626,7 +6626,7 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory) "Ib" = ( @@ -6681,7 +6681,7 @@ /area/awaymission/beach/heretic) "Ik" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Il" = ( @@ -6699,7 +6699,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Io" = ( @@ -6760,7 +6760,7 @@ /obj/effect/turf_decal/tile/dark_green/anticorner/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "IF" = ( @@ -6774,7 +6774,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/dark_green/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "II" = ( @@ -6832,7 +6832,7 @@ /area/awaymission/caves/heretic_laboratory) "IZ" = ( /obj/effect/turf_decal/tile/green/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Ja" = ( @@ -6846,7 +6846,7 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Jf" = ( @@ -6880,7 +6880,7 @@ /obj/effect/turf_decal/tile/bar{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Jp" = ( @@ -6919,7 +6919,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "JA" = ( @@ -7000,12 +7000,12 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "JR" = ( /obj/effect/turf_decal/tile/dark_green/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "JS" = ( @@ -7042,7 +7042,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "JY" = ( @@ -7055,7 +7055,7 @@ /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory_clean) "Kb" = ( -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory_clean) "Kd" = ( @@ -7127,7 +7127,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Ku" = ( @@ -7237,7 +7237,7 @@ /area/awaymission/caves/heretic_laboratory) "Le" = ( /obj/structure/table, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "Lf" = ( @@ -7258,7 +7258,7 @@ }, /obj/item/kirbyplants/random, /obj/effect/turf_decal/stripes/corner, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Ln" = ( @@ -7279,7 +7279,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/mapping_helpers/airlock/locked, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Lv" = ( @@ -7304,7 +7304,7 @@ /obj/effect/turf_decal/tile/dark_green/half{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "LC" = ( @@ -7350,7 +7350,7 @@ pixel_y = 6 }, /obj/structure/bed/double, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "Ma" = ( @@ -7400,7 +7400,7 @@ /obj/effect/turf_decal/tile/green/anticorner/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Ml" = ( @@ -7409,7 +7409,7 @@ /obj/effect/decal/cleanable/blood/tracks{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/catwalk_floor/iron_dark, /area/awaymission/caves/heretic_laboratory) "Mn" = ( @@ -7502,7 +7502,7 @@ "ML" = ( /obj/structure/bookcase/random, /obj/machinery/light/directional/west, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory_clean) "MP" = ( @@ -7522,7 +7522,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "MT" = ( @@ -7540,7 +7540,7 @@ /obj/effect/turf_decal/tile/purple/half{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "MX" = ( @@ -7551,14 +7551,14 @@ /obj/effect/turf_decal/tile/bar{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "MY" = ( /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Na" = ( @@ -7569,7 +7569,7 @@ /obj/machinery/door/airlock/security{ name = "Armory" }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Nc" = ( @@ -7589,7 +7589,7 @@ /area/awaymission/beach/heretic) "Nj" = ( /obj/machinery/light/directional/west, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Nm" = ( @@ -7648,7 +7648,7 @@ /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "NB" = ( @@ -7675,7 +7675,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /mob/living/basic/migo, /obj/effect/turf_decal/tile/bar/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "NO" = ( @@ -7772,7 +7772,7 @@ dir = 8 }, /obj/structure/sign/poster/contraband/free_tonto/directional/west, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Oq" = ( @@ -7787,7 +7787,7 @@ /obj/effect/turf_decal/tile/dark_red/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Ow" = ( @@ -7798,7 +7798,7 @@ /obj/effect/turf_decal/tile/dark_green/half{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Oz" = ( @@ -7900,7 +7900,7 @@ /obj/effect/turf_decal/tile/dark/half{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Pa" = ( @@ -7928,13 +7928,13 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Pc" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/catwalk_floor/iron_dark, /area/awaymission/caves/heretic_laboratory) "Ph" = ( @@ -7958,7 +7958,7 @@ /area/awaymission/caves/heretic_laboratory_clean) "Pq" = ( /obj/machinery/vending/sustenance, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Pw" = ( @@ -8000,7 +8000,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /mob/living/basic/faithless, /obj/effect/turf_decal/tile/bar/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "PF" = ( @@ -8032,7 +8032,7 @@ /obj/structure/window/spawner/directional/east, /obj/structure/window/spawner/directional/west, /obj/structure/flora/bush/jungle/c/style_random, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/misc/grass/jungle, /area/awaymission/caves/heretic_laboratory) "PN" = ( @@ -8140,7 +8140,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/dark_green/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Qn" = ( @@ -8165,7 +8165,7 @@ /obj/effect/turf_decal/tile/dark_red/anticorner/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Qx" = ( @@ -8201,7 +8201,7 @@ /obj/effect/turf_decal/stripes/corner{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "QF" = ( @@ -8235,7 +8235,7 @@ /obj/effect/turf_decal/tile/purple/half{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "QM" = ( @@ -8346,7 +8346,7 @@ /area/awaymission/caves/heretic_laboratory) "Rv" = ( /obj/effect/turf_decal/tile/bar/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Rw" = ( @@ -8376,7 +8376,7 @@ /obj/effect/turf_decal/tile/bar/anticorner/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "RE" = ( @@ -8418,7 +8418,7 @@ /obj/effect/turf_decal/stripes/line{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "RR" = ( @@ -8469,7 +8469,7 @@ /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Sb" = ( @@ -8528,7 +8528,7 @@ /obj/effect/turf_decal/tile/dark_red/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /obj/structure/sign/poster/contraband/blood_geometer/directional/north, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) @@ -8551,7 +8551,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/dark_green/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Su" = ( @@ -8582,7 +8582,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "SI" = ( @@ -8649,7 +8649,7 @@ /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Td" = ( @@ -8672,7 +8672,7 @@ /obj/effect/turf_decal/stripes/corner{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Ti" = ( @@ -8724,12 +8724,12 @@ /area/awaymission/caves/heretic_laboratory) "Ts" = ( /obj/effect/turf_decal/tile/bar/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "TA" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "TB" = ( @@ -8743,7 +8743,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/bar/fourcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "TF" = ( @@ -8753,7 +8753,7 @@ /area/awaymission/caves/heretic_laboratory) "TG" = ( /obj/machinery/light/directional/east, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "TH" = ( @@ -8789,7 +8789,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "TN" = ( @@ -8826,7 +8826,7 @@ /obj/effect/turf_decal/tile/dark_red/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Ua" = ( @@ -8854,7 +8854,7 @@ /obj/effect/turf_decal/tile/dark_red/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Uj" = ( @@ -8877,13 +8877,13 @@ /area/awaymission/caves/heretic_laboratory) "Uo" = ( /obj/machinery/light/directional/east, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory_clean) "Up" = ( /obj/machinery/vending/security, /obj/effect/turf_decal/tile/dark/opposingcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Uq" = ( @@ -8904,7 +8904,7 @@ /obj/item/kirbyplants/fern, /obj/machinery/light/directional/south, /obj/effect/turf_decal/tile/green/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "UA" = ( @@ -8933,7 +8933,7 @@ /obj/structure/rack, /obj/effect/spawner/random/armory/disablers, /obj/effect/turf_decal/tile/dark/opposingcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "UF" = ( @@ -8954,7 +8954,7 @@ /obj/effect/turf_decal/tile/green/anticorner/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "UK" = ( @@ -9000,7 +9000,7 @@ /obj/effect/turf_decal/tile/dark/half{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "UX" = ( @@ -9034,7 +9034,7 @@ /obj/effect/turf_decal/tile/dark{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "Vi" = ( @@ -9053,12 +9053,12 @@ /obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Vn" = ( /obj/effect/turf_decal/tile/dark/opposingcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Vr" = ( @@ -9083,7 +9083,7 @@ /turf/open/floor/engine, /area/awaymission/caves/heretic_laboratory) "VA" = ( -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "VB" = ( @@ -9120,7 +9120,7 @@ /obj/item/fake_items/lahti_l39{ pixel_x = 16 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "VI" = ( @@ -9173,7 +9173,7 @@ /obj/effect/turf_decal/tile/dark_red/half/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "VZ" = ( @@ -9208,7 +9208,7 @@ /turf/open/floor/iron/white, /area/awaymission/caves/heretic_laboratory) "Wj" = ( -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Wk" = ( @@ -9226,7 +9226,7 @@ dir = 4 }, /obj/structure/sign/poster/contraband/clown/directional/east, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Wn" = ( @@ -9239,11 +9239,6 @@ /obj/structure/window/reinforced/plasma/spawner/directional/east, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory_clean) -"Wr" = ( -/obj/effect/blessing/invisible, -/obj/effect/blessing/invisible, -/turf/open/floor/plating, -/area/awaymission/caves/heretic_laboratory_clean) "Ws" = ( /obj/structure/fluff/beach_umbrella/science, /turf/open/misc/beach/sand, @@ -9336,7 +9331,7 @@ /area/awaymission/caves/heretic_laboratory) "WN" = ( /obj/item/radio/intercom/mi13/directional/east, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron, /area/awaymission/caves/heretic_laboratory_clean) "WO" = ( @@ -9346,7 +9341,7 @@ "WP" = ( /obj/machinery/door/airlock/hatch, /obj/structure/holosign/barrier/atmos/sturdy, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/plating, /area/awaymission/caves/heretic_laboratory_clean) "WR" = ( @@ -9381,7 +9376,7 @@ /obj/effect/turf_decal/tile/dark_green/anticorner/contrasted{ dir = 4 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Xa" = ( @@ -9433,7 +9428,7 @@ /obj/effect/turf_decal/tile/dark_green/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Xm" = ( @@ -9504,7 +9499,7 @@ /area/awaymission/caves/heretic_laboratory) "XC" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "XI" = ( @@ -9539,7 +9534,7 @@ dir = 4 }, /obj/item/radio/intercom/mi13/directional/east, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "XR" = ( @@ -9547,7 +9542,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/effect/turf_decal/stripes/line, /obj/effect/turf_decal/tile/bar/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "XS" = ( @@ -9588,7 +9583,7 @@ "Yb" = ( /obj/effect/turf_decal/stripes/line, /obj/effect/turf_decal/tile/bar/half/contrasted, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Yc" = ( @@ -9631,7 +9626,7 @@ /obj/structure/window/spawner/directional/west, /obj/structure/window/spawner/directional/south, /obj/structure/flora/bush/jungle/c/style_random, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/misc/grass/jungle, /area/awaymission/caves/heretic_laboratory) "Yn" = ( @@ -9673,7 +9668,7 @@ /obj/item/ammo_box/magazine/lahtimagazine{ anchored = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory_clean) "Yv" = ( @@ -9697,7 +9692,7 @@ /obj/effect/turf_decal/tile/bar/half/contrasted{ dir = 1 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Yy" = ( @@ -9856,7 +9851,7 @@ /obj/effect/turf_decal/tile/green/half/contrasted{ dir = 8 }, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "Zy" = ( @@ -9867,7 +9862,7 @@ "ZD" = ( /obj/machinery/vending/wardrobe/sec_wardrobe, /obj/effect/turf_decal/tile/dark/opposingcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "ZG" = ( @@ -9891,7 +9886,7 @@ dir = 8 }, /obj/item/radio/intercom/mi13/directional/west, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/small, /area/awaymission/caves/heretic_laboratory) "ZL" = ( @@ -9929,7 +9924,7 @@ pixel_y = 4 }, /obj/effect/turf_decal/tile/dark/opposingcorners, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/floor/iron/dark, /area/awaymission/caves/heretic_laboratory) "ZV" = ( @@ -9957,7 +9952,7 @@ /obj/structure/window/spawner/directional/east, /obj/structure/window/spawner/directional/west, /obj/structure/flora/bush/jungle/c/style_random, -/obj/effect/blessing/invisible, +/obj/effect/mapping_helpers/blessing/invisible, /turf/open/misc/grass/jungle, /area/awaymission/caves/heretic_laboratory) @@ -23476,7 +23471,7 @@ vk vk vk vk -Wr +vk xl xl nd diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm index 6e70a9b36b9c..06df0cbfe5af 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm @@ -84,6 +84,7 @@ #define COMSIG_ATOM_PROCESSED "atom_processed" ///called when teleporting into a possibly protected turf: (channel, turf/origin, turf/destination) #define COMSIG_ATOM_INTERCEPT_TELEPORTING "intercept_teleporting" + #define COMPONENT_INTERCEPT_TELEPORT (1<<0) ///called when an atom is added to the hearers on get_hearers_in_view(): (list/processing_list, list/hearers) #define COMSIG_ATOM_HEARER_IN_VIEW "atom_hearer_in_view" ///called when an atom starts orbiting another atom: (atom) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index ad14246096a1..be043a838eeb 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -838,6 +838,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_IMMERSE_STOPPED "immerse_stopped" /// The effects of hyperspace drift are blocked when the tile has this trait #define TRAIT_HYPERSPACE_STOPPED "hyperspace_stopped" +/// This turf is blessed and blocks jaunts/revenants/cult TPs +#define TRAIT_TURF_BLESSED "turf_blessed" ///Turf slowdown will be ignored when this trait is added to a turf. #define TRAIT_TURF_IGNORE_SLOWDOWN "turf_ignore_slowdown" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index f570a85dd182..3c724063fe57 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -857,6 +857,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_LAVA_STOPPED" = TRAIT_LAVA_STOPPED, "TRAIT_NO_TERRAFORM" = TRAIT_NO_TERRAFORM, "TRAIT_SHUTTLE_CONSTRUCTION_TURF" = TRAIT_SHUTTLE_CONSTRUCTION_TURF, + "TRAIT_TURF_BLESSED" = TRAIT_TURF_BLESSED, "TRAIT_SPINNING_WEB_TURF" = TRAIT_SPINNING_WEB_TURF, "TRAIT_TURF_IGNORE_SLIPPERY" = TRAIT_TURF_IGNORE_SLIPPERY, "TRAIT_TURF_IGNORE_SLOWDOWN" = TRAIT_TURF_IGNORE_SLOWDOWN, diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm index 28ae7a18d5be..1fce2e916a34 100644 --- a/code/datums/components/chasm.dm +++ b/code/datums/components/chasm.dm @@ -70,7 +70,7 @@ drop_stuff(movable) /datum/component/chasm/proc/block_teleport() - return TRUE + return COMPONENT_INTERCEPT_TELEPORT /datum/component/chasm/proc/on_chasm_stopped(datum/source) SIGNAL_HANDLER diff --git a/code/datums/elements/blessed_turf.dm b/code/datums/elements/blessed_turf.dm new file mode 100644 index 000000000000..bf97abd12ff8 --- /dev/null +++ b/code/datums/elements/blessed_turf.dm @@ -0,0 +1,36 @@ +#define BLESSING_APPEARANCE_KEY "blessing" +/// Attached to blessed turfs, prevents jaunting, revenant movement and cult teleportation +/datum/element/blessed_turf + +/datum/element/blessed_turf/Attach(datum/target, invisible = FALSE) + . = ..() + if (!isturf(target)) + return ELEMENT_INCOMPATIBLE + + var/turf/target_turf = target + ADD_TRAIT(target_turf, TRAIT_TURF_BLESSED, ELEMENT_TRAIT(type)) + RegisterSignal(target_turf, COMSIG_ATOM_INTERCEPT_TELEPORTING, PROC_REF(block_cult_teleport)) + + if (invisible) + return + + var/image/blessing_icon = image(icon = 'icons/effects/effects.dmi', icon_state = "blessed", layer = ABOVE_NORMAL_TURF_LAYER, loc = target_turf) + SET_PLANE_EXPLICIT(blessing_icon, GAME_PLANE, target_turf) + blessing_icon.alpha = 64 + blessing_icon.appearance_flags = RESET_ALPHA | RESET_COLOR | KEEP_APART + target_turf.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessed_aware, BLESSING_APPEARANCE_KEY, blessing_icon) + +/datum/element/blessed_turf/Detach(datum/source, ...) + . = ..() + var/turf/target_turf = source + REMOVE_TRAIT(target_turf, TRAIT_TURF_BLESSED, ELEMENT_TRAIT(type)) + UnregisterSignal(target_turf, COMSIG_ATOM_INTERCEPT_TELEPORTING) + target_turf.remove_alt_appearance(BLESSING_APPEARANCE_KEY) + +/datum/element/blessed_turf/proc/block_cult_teleport(datum/source, channel, turf/origin) + SIGNAL_HANDLER + + if(channel == TELEPORT_CHANNEL_CULT) + return COMPONENT_INTERCEPT_TELEPORT + +#undef BLESSING_APPEARANCE_KEY diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index b234481babc3..10fde7b8dae6 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -75,7 +75,7 @@ if(SEND_SIGNAL(teleatom, COMSIG_MOVABLE_TELEPORTING, destination, channel)) return FALSE - if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORTING, channel, curturf)) + if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORTING, channel, curturf) & COMPONENT_INTERCEPT_TELEPORT) return FALSE if(isobserver(teleatom)) diff --git a/code/game/objects/effects/blessing.dm b/code/game/objects/effects/blessing.dm deleted file mode 100644 index f4f9ea381400..000000000000 --- a/code/game/objects/effects/blessing.dm +++ /dev/null @@ -1,37 +0,0 @@ -/obj/effect/blessing - name = "holy blessing" - desc = "Holy energies interfere with ethereal travel at this location." - icon = 'icons/effects/effects.dmi' - icon_state = null - anchored = TRUE - density = FALSE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - /// Is this blessing visible to those with the ability to see blessed tiles? (chaplains) - var/invisible - -/obj/effect/blessing/Initialize(mapload) - . = ..() - - RegisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORTING, PROC_REF(block_cult_teleport)) - - if(invisible) - return - - var/image/blessing_icon = image(icon = 'icons/effects/effects.dmi', icon_state = "blessed", layer = ABOVE_NORMAL_TURF_LAYER, loc = src) - blessing_icon.alpha = 64 - blessing_icon.appearance_flags = RESET_ALPHA - add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessed_aware, "blessing", blessing_icon) - -/obj/effect/blessing/Destroy() - UnregisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORTING) - return ..() - -///Called from intercept teleport signal, blocks cult teleporting from being able to teleport on us. -/obj/effect/blessing/proc/block_cult_teleport(datum/source, channel, turf/origin) - SIGNAL_HANDLER - - if(channel == TELEPORT_CHANNEL_CULT) - return TRUE - -/obj/effect/blessing/invisible - invisible = TRUE diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index bb6b51dd90ca..91d7c0d0a5c6 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -518,10 +518,9 @@ GLOBAL_LIST_EMPTY(station_turfs) if(L && (L.flags_1 & INITIALIZED_1)) qdel(L) -/turf/proc/Bless() - if(locate(/obj/effect/blessing) in src) - return - new /obj/effect/blessing(src) +/turf/proc/bless_turf(invisible = FALSE) + if(!HAS_TRAIT(src, TRAIT_TURF_BLESSED)) + AddElement(/datum/element/blessed_turf, invisible) ////////////////////////////// //Distance procs diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index c22ca17e0b33..b5b7b6966a30 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -1042,33 +1042,41 @@ Striking a noncultist, however, will tear their flesh."} playsound(src, 'sound/effects/magic/exit_blood.ogg', 75, TRUE) new /obj/effect/temp_visual/dir_setting/cult/phase(user.loc, user.dir) var/turf/temp_target = get_turf_in_angle(set_angle, targets_from, 40) - for(var/turf/T in get_line(targets_from,temp_target)) - if (locate(/obj/effect/blessing, T)) - temp_target = T - playsound(T, 'sound/effects/parry.ogg', 50, TRUE) - new /obj/effect/temp_visual/at_shield(T, T) + for(var/turf/target_turf in get_line(targets_from,temp_target)) + if (HAS_TRAIT(target_turf, TRAIT_TURF_BLESSED)) + temp_target = target_turf + playsound(target_turf, 'sound/effects/parry.ogg', 50, TRUE) + new /obj/effect/temp_visual/at_shield(target_turf, target_turf) break - T.narsie_act(TRUE, TRUE) - for(var/mob/living/target in T.contents) - if(IS_CULTIST(target)) - new /obj/effect/temp_visual/cult/sparks(T) - if(ishuman(target)) - var/mob/living/carbon/human/H = target - if(H.stat != DEAD) - H.reagents.add_reagent(/datum/reagent/fuel/unholywater, 7) - if(isshade(target) || isconstruct(target)) - var/mob/living/basic/construct/healed_guy = target - if(healed_guy.health + 15 < healed_guy.maxHealth) - healed_guy.adjust_health(-15) - else - healed_guy.health = healed_guy.maxHealth + + target_turf.narsie_act(TRUE, TRUE) + for(var/mob/living/target in target_turf) + if(!IS_CULTIST(target)) + var/mob/living/victim = target + if(!victim.density) + continue + victim.Paralyze(20) + victim.adjust_brute_loss(45) + playsound(victim, 'sound/effects/hallucinations/wail.ogg', 50, TRUE) + victim.emote("scream") + continue + + new /obj/effect/temp_visual/cult/sparks(target_turf) + if(ishuman(target)) + var/mob/living/carbon/human/cultie = target + if(cultie.stat != DEAD) + cultie.reagents.add_reagent(/datum/reagent/fuel/unholywater, 7) + continue + + if(!isshade(target) && !isconstruct(target)) + continue + + var/mob/living/basic/construct/healed_guy = target + if(healed_guy.health + 15 < healed_guy.maxHealth) + healed_guy.adjust_health(-15) else - var/mob/living/L = target - if(L.density) - L.Paralyze(20) - L.adjust_brute_loss(45) - playsound(L, 'sound/effects/hallucinations/wail.ogg', 50, TRUE) - L.emote("scream") + healed_guy.health = healed_guy.maxHealth + user.Beam(temp_target, icon_state="blood_beam", time = 7, beam_type = /obj/effect/ebeam/blood) diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm index e2af58fc9642..8a39d3f04743 100644 --- a/code/modules/mapping/mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers.dm @@ -1548,3 +1548,21 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_atoms_ontop) . = ..() var/turf/our_turf = get_turf(src) ADD_TRAIT(our_turf, TRAIT_NO_TELEPORT, INNATE_TRAIT) + +/// Blesses turfs it is created on, then deletes itself +/obj/effect/mapping_helpers/blessing + name = "holy blessing" + desc = "Holy energies interfere with ethereal travel at this location." + icon = 'icons/effects/effects.dmi' + icon_state = "blessed" + /// Is this blessing visible to those with the ability to see blessed tiles? (chaplains) + var/invisible = FALSE + +/obj/effect/mapping_helpers/blessing/Initialize(mapload) + . = ..() + if (isturf(loc)) + loc.AddElement(/datum/element/blessed_turf, invisible) + return INITIALIZE_HINT_QDEL + +/obj/effect/mapping_helpers/blessing/invisible + invisible = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm index 5b37b0f25021..3e7c799ee96b 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm @@ -418,7 +418,7 @@ apply_status_effect(/datum/status_effect/incapacitating/paralyzed/revenant, 2 SECONDS) return FALSE - if(locate(/obj/effect/blessing) in step_turf) + if(HAS_TRAIT(step_turf, TRAIT_TURF_BLESSED)) to_chat(src, span_warning("Holy energies block your path!")) return FALSE diff --git a/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm index 246dceed2e6f..6f41f5c490e7 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/revenant_abilities.dm @@ -117,9 +117,10 @@ var/turf_was_defiled = FALSE // dispel - for(var/obj/effect/blessing/blessing in target_turf) + if (HAS_TRAIT(target_turf, TRAIT_TURF_BLESSED)) turf_was_defiled = TRUE - qdel(blessing) + target_turf.RemoveElement(/datum/element/blessed_turf) + for(var/obj/effect/decal/cleanable/food/salt/salt in target_turf) turf_was_defiled = TRUE qdel(salt) diff --git a/code/modules/mob/living/carbon/human/species_types/ghost.dm b/code/modules/mob/living/carbon/human/species_types/ghost.dm index 3456066ae329..a286fd72ee3a 100644 --- a/code/modules/mob/living/carbon/human/species_types/ghost.dm +++ b/code/modules/mob/living/carbon/human/species_types/ghost.dm @@ -191,7 +191,8 @@ return FALSE //technically you can trap a ghost by blessing them as theyre phasing, //but they can still be dragged out. - if(locate(/obj/effect/blessing) in get_turf(owner)) + var/turf/owner_turf = get_turf(owner) + if(HAS_TRAIT(owner_turf, TRAIT_TURF_BLESSED)) return FALSE var/obj/item/bodypart/chest/their_chest = living_owner.get_bodypart(BODY_ZONE_CHEST) if(!their_chest || !(their_chest.bodytype & BODYTYPE_GHOST)) @@ -250,8 +251,9 @@ UnregisterSignal(carbon_owner, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE) ///Called when attempting to move to a new tile while the action is active, returns to cancel moving. -/datum/action/innate/toggle_passthrough/proc/attempt_move(mob/source, new_loc, direct) +/datum/action/innate/toggle_passthrough/proc/attempt_move(mob/source, atom/new_loc, direct) SIGNAL_HANDLER - if(locate(/obj/effect/blessing) in new_loc) + + if(new_loc && HAS_TRAIT(new_loc, TRAIT_TURF_BLESSED)) to_chat(source, span_warning("Holy energies block your path!")) return COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 1554bedc7d77..133820b93144 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -243,7 +243,7 @@ GAME_VERB_HIDDEN(/client, drop_item, "drop item") if(stepTurf.turf_flags & NOJAUNT) to_chat(L, span_warning("Some strange aura is blocking the way.")) return - if(locate(/obj/effect/blessing) in stepTurf) + if(HAS_TRAIT(stepTurf, TRAIT_TURF_BLESSED)) to_chat(L, span_warning("Holy energies block your path!")) return diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index f6ea167bfaaf..2d9541ac31d0 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -409,7 +409,7 @@ if(reac_volume >= 10) for(var/obj/effect/rune/R in exposed_turf) qdel(R) - exposed_turf.Bless() + exposed_turf.bless_turf() /datum/reagent/water/hollowwater name = "Hollow Water" diff --git a/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm b/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm index 96cf2a46cdf6..88126fd6d971 100644 --- a/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm +++ b/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm @@ -220,7 +220,8 @@ . = ..() if(!.) return - if (locate(/obj/effect/blessing) in .) + var/atom/new_loc = . + if (HAS_TRAIT(new_loc, TRAIT_TURF_BLESSED)) to_chat(user, span_warning("Holy energies block your path!")) return null diff --git a/tgstation.dme b/tgstation.dme index 5bda428edc0d..86230499a000 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1685,6 +1685,7 @@ #include "code\datums\elements\beauty.dm" #include "code\datums\elements\bed_tucking.dm" #include "code\datums\elements\befriend_petting.dm" +#include "code\datums\elements\blessed_turf.dm" #include "code\datums\elements\block_area_power_fail.dm" #include "code\datums\elements\block_turf_fingerprints.dm" #include "code\datums\elements\blocks_explosives.dm" @@ -2611,7 +2612,6 @@ #include "code\game\objects\obj_defense.dm" #include "code\game\objects\objs.dm" #include "code\game\objects\structures.dm" -#include "code\game\objects\effects\blessing.dm" #include "code\game\objects\effects\bump_teleporter.dm" #include "code\game\objects\effects\countdown.dm" #include "code\game\objects\effects\cursor_catcher.dm" diff --git a/tools/UpdatePaths/Scripts/97204_turf_blessing.txt b/tools/UpdatePaths/Scripts/97204_turf_blessing.txt new file mode 100644 index 000000000000..23d3996c60e3 --- /dev/null +++ b/tools/UpdatePaths/Scripts/97204_turf_blessing.txt @@ -0,0 +1 @@ +/obj/effect/blessing/@SUBTYPES : /obj/effect/mapping_helpers/blessing/@SUBTYPES{@OLD} From 7f739ad8351153e5fd10c6e538239c49627d2dda Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 18:44:49 +0000 Subject: [PATCH 388/445] Automatic changelog for PR #97204 [ci skip] --- html/changelogs/AutoChangeLog-pr-97204.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97204.yml diff --git a/html/changelogs/AutoChangeLog-pr-97204.yml b/html/changelogs/AutoChangeLog-pr-97204.yml new file mode 100644 index 000000000000..1fe248643faa --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97204.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - refactor: "Refactored blessing into an element/trait combo rather than an object" \ No newline at end of file From 0724c7e9542f21ca9781bc4ea032486bfc4742b7 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 21:04:12 +0000 Subject: [PATCH 389/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97204.yml | 4 ---- html/changelogs/archive/2026-08.yml | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97204.yml diff --git a/html/changelogs/AutoChangeLog-pr-97204.yml b/html/changelogs/AutoChangeLog-pr-97204.yml deleted file mode 100644 index 1fe248643faa..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97204.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - refactor: "Refactored blessing into an element/trait combo rather than an object" \ No newline at end of file diff --git a/html/changelogs/archive/2026-08.yml b/html/changelogs/archive/2026-08.yml index 15e1a1d8f46f..c6dd9fd0d558 100644 --- a/html/changelogs/archive/2026-08.yml +++ b/html/changelogs/archive/2026-08.yml @@ -15,6 +15,8 @@ that (the refactor, not the peanut butter...).' QuiteLiterallyAnything: - bugfix: Fixed Catwalk's upper atmospherics room being exposed to space by default. + SmArtKar: + - refactor: Refactored blessing into an element/trait combo rather than an object Thunder12345: - rscadd: The quantum console now tells bitrunners what grade they've reached on completed domains. From cba7bc140238c2b2495d517c0cdca45d722e0681 Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Sat, 1 Aug 2026 23:11:08 +0200 Subject: [PATCH 390/445] [NO GBP] Felinids are no longer TOO dumb (#97306) --- code/modules/antagonists/nightmare/nightmare_organs.dm | 2 +- code/modules/mob/living/brain/brain_item.dm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/antagonists/nightmare/nightmare_organs.dm b/code/modules/antagonists/nightmare/nightmare_organs.dm index 4acf712ccd33..367bec10482b 100644 --- a/code/modules/antagonists/nightmare/nightmare_organs.dm +++ b/code/modules/antagonists/nightmare/nightmare_organs.dm @@ -9,7 +9,7 @@ icon = 'icons/obj/medical/organs/organs.dmi' icon_state = "brain-x-d" shade_color = "black, somehow" - organ_traits = list(TRAIT_NIGHTMARISH) + variant_traits_added = list(TRAIT_NIGHTMARISH) ///Our associated shadow jaunt spell, for all nightmares var/datum/action/cooldown/spell/jaunt/shadow_walk/our_jaunt diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm index 7a9da94e8c5e..01cce1ced0c2 100644 --- a/code/modules/mob/living/brain/brain_item.dm +++ b/code/modules/mob/living/brain/brain_item.dm @@ -559,7 +559,7 @@ /obj/item/organ/brain/felinid //A bit smaller than average brain_size = 0.8 - organ_traits = list( + variant_traits_added = list( TRAIT_CATLIKE_INSTINCT, TRAIT_WATER_HATER, ) From 9ac3c32e9e36b8641b64f3ea5a697fdff341fa14 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 21:11:33 +0000 Subject: [PATCH 391/445] Automatic changelog for PR #97306 [ci skip] --- html/changelogs/AutoChangeLog-pr-97306.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97306.yml diff --git a/html/changelogs/AutoChangeLog-pr-97306.yml b/html/changelogs/AutoChangeLog-pr-97306.yml new file mode 100644 index 000000000000..ecd3dcd0780b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97306.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Felinids (and nightmares) can once again use more complex objects, unlike monkeys." \ No newline at end of file From 37e01449c91a769313ab70a0569f75cd8f3e9d79 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 00:00:26 +0000 Subject: [PATCH 392/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97306.yml | 4 ---- html/changelogs/archive/2026-08.yml | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97306.yml diff --git a/html/changelogs/AutoChangeLog-pr-97306.yml b/html/changelogs/AutoChangeLog-pr-97306.yml deleted file mode 100644 index ecd3dcd0780b..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97306.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - bugfix: "Felinids (and nightmares) can once again use more complex objects, unlike monkeys." \ No newline at end of file diff --git a/html/changelogs/archive/2026-08.yml b/html/changelogs/archive/2026-08.yml index c6dd9fd0d558..57fc82af14b0 100644 --- a/html/changelogs/archive/2026-08.yml +++ b/html/changelogs/archive/2026-08.yml @@ -25,3 +25,7 @@ or ethereal). - bugfix: the experimental cloner now spits flavor and policy text (if there is any) when spawning +2026-08-02: + Ghommie: + - bugfix: Felinids (and nightmares) can once again use more complex objects, unlike + monkeys. From e60bd9ec278fea3babf11fa609d95ad86655f993 Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Sat, 1 Aug 2026 21:03:01 -0400 Subject: [PATCH 393/445] Slightly better bounty list UI (#97290) ## About The Pull Request I got annoyed at the spacing in between lines. image ## Why It's Good For The Game Very minor change ## Changelog :cl: spellcheck: Cargo bounty list printout now has a slightly better UI. /:cl: --- code/game/machinery/civilian_bounty/bounty_machinery.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/machinery/civilian_bounty/bounty_machinery.dm b/code/game/machinery/civilian_bounty/bounty_machinery.dm index 9b06f6feeb2b..43bf3144ec9b 100644 --- a/code/game/machinery/civilian_bounty/bounty_machinery.dm +++ b/code/game/machinery/civilian_bounty/bounty_machinery.dm @@ -400,7 +400,7 @@ paper.name = "paper - Bounties" var/list/printout_text = list() - printout_text += "

Nanotrasen Cargo Bounties


" + printout_text += "

Nanotrasen Cargo Bounties

" for(var/datum/bounty/current_bounty in GLOB.shared_crew_bounties) if(current_bounty.claimed) @@ -411,7 +411,7 @@
  • Reward: [current_bounty.get_bounty_reward()] cr.
  • Cut: [round(BOUNTY_CUT_STANDARD * current_bounty.get_bounty_reward())] cr.
  • "} - paper.add_raw_text(printout_text.Join("
    ")) + paper.add_raw_text(printout_text.Join("")) paper.update_appearance() playsound(src, 'sound/machines/printer.ogg', 100, TRUE) From 197496bca0ee32b9883b09df09aca6a970ff46a7 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 01:03:20 +0000 Subject: [PATCH 394/445] Automatic changelog for PR #97290 [ci skip] --- html/changelogs/AutoChangeLog-pr-97290.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97290.yml diff --git a/html/changelogs/AutoChangeLog-pr-97290.yml b/html/changelogs/AutoChangeLog-pr-97290.yml new file mode 100644 index 000000000000..2a2ff3eb6bc4 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97290.yml @@ -0,0 +1,4 @@ +author: "JohnFulpWillard" +delete-after: True +changes: + - spellcheck: "Cargo bounty list printout now has a slightly better UI." \ No newline at end of file From 7e6099e943a78f79056b98a0b6a522604722d0a4 Mon Sep 17 00:00:00 2001 From: chazzyjazzy <33268885+chazzyjazzy@users.noreply.github.com> Date: Mon, 3 Aug 2026 11:45:29 -0400 Subject: [PATCH 395/445] red news landmark (#1309) ## About The Pull Request landmark for the red news guy ## Why It's Good For The Game linters bleh ## Changelog :cl: code: adds a landmark for the red news reporter /:cl: --- .../modules/jobs/code/landmarks.dm | 1 + .../modules/jobs/icons/landmarks.dmi | Bin 26412 -> 26771 bytes 2 files changed, 1 insertion(+) diff --git a/modular_darkpack/modules/jobs/code/landmarks.dm b/modular_darkpack/modules/jobs/code/landmarks.dm index 0386047eff7c..7499ec6eb0b3 100644 --- a/modular_darkpack/modules/jobs/code/landmarks.dm +++ b/modular_darkpack/modules/jobs/code/landmarks.dm @@ -82,6 +82,7 @@ JOB_START_HELPER(citizen/club_worker, JOB_CLUB_WORKER) JOB_START_HELPER(citizen/janitor, JOB_STREET_JANITOR) JOB_START_HELPER(citizen/priest, JOB_PRIEST) JOB_START_HELPER(citizen/taxi, JOB_TAXI_DRIVER) +JOB_START_HELPER(citizen/red_news, JOB_RED_NEWS_REPORTER) /* Sabbat */ /obj/effect/landmark/start/darkpack/sabbat diff --git a/modular_darkpack/modules/jobs/icons/landmarks.dmi b/modular_darkpack/modules/jobs/icons/landmarks.dmi index 0b7a01cff0e1c01067613e03a3fe39becd557603..a4b22d3a310e7170cefe00d541c8cc01d8f123f4 100644 GIT binary patch delta 2586 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 delta 2198 zcmV;H2x<3|(E+T^0gxmCbCD$~e_{z*U^hmlZKAT|(vlPJ*N<)5?lg!eTkxYrJw871 z(kzzi&DNB+oB4=5Q1IrP`TF7Ge0HQNwzBy0c|L2Anl_#nLxhO(da(cw{$f6>hzD7J zn$K#64$trZP@HH$&f=CG4Fm9$1@4ZBz2b*^=!dAlT?6;|U9Us(RTg!}f8;IZkANNr z+k(p@TV$&1{9(fp*(|%>g$eTsNSzz&Wv>buAAyeD|ClD45#8;Q-N;$;LEj(RW}U)mWW`O9ZFspVaqm zGJCYG_^rn7idJY~5f0myMpC5m7O@6y!sHaL_8RaDb*&Vn@>Bk-_kqSx<+uMW_LrKx z0iLpmJpESeOAN$oSuTaE6AFjE$pW_!WvZ0Mc)7#OF1U&`S~!Zlw0&__qo({T`5HxG zkRN!8;Hwa$BujZV4u^axH~vXxVb#^J4I}-3ki0a@ZTT{@{{?(`ZMI5S$cO*{WOTEz z0&8A>zXlEJ!0a9nln;~($AxP-vJgPNsHD^azI^UMfy6KI%V-ajc76>Sl!Dm`Xz{vr zPFx9jS9K=XfG`9@h&We1kW=#m@@vqb zBrIA7gfpT2ga5z1^ACx;jN|x6$c>TAIMi;Nw;zgihs>yS!5VhBb_16cZU5K~38FuL zT3{CbH!&%TLWWDGLD~|N{7ahsA?a4oX2F6Q+^}>v&)MBPe?ZeQ8E(mS@8|Y>`+iN% zuif|2gZsST%X823e4cyXANPEo-`{5!(Et%DfqY>-{`?)mYZQv+--U%)Jc-vQhRQ2XDDm(DJ{3 z^%=%$OwR!|w|^ogI16?^POo{rRD14%_B4I)o=Df*S&d%5H)x~pFLlzf(;Xs2ob}b} zGmHvM%>gx^sEUL#Geipvy1Krg?z6q(^B-5Q(ud6rhP>SQJM>Olmt{kN#>U2f$gPD* zgT~h2JoWUxPnAdO>9!-&kcS&O>gwuPpJ9@mRfk}3KKYO3M!WfQqWdp_;{mhbun6^e zutzy7oKSUjbws}OUax`5C&ME(rwnoZIev+5A6skWBJu7xN)FeT!_P|$Ei zb8u=(#E}kQ2YZyW0$KU)FFa&_&_~M;5bE`%omDq=q`E%Cs3fif!tPqEw7{86-@2ov zw>F(2*WSJ2aoFwD@a{=14|cFexkPv8dcEGr%*>4VtiOY-u%O6br;kfJltX#eXPD&0 z)G8SEn|wJqm?kGUf4g!;I3JL`ZJUUP0B-#r|b$y0WOD$BD`H(Nas6FQQNC8iXFgGe>vy@ zNPP_ZzCKx_m1BK|NnYaifcETk(9G-%J-lVJh|6Bi^^xdstNg$A$d|UVe4_Y&_)**O zJK57C^aFta_4oH{Hs$5zn*Jku@+i}0quKFb!rH9QFlve01A_B^$;r+U&%)hB+P*!D z9Jvnqd1#25KRY9A(EITX4~ulYu-8`CO-W)gXDC@{`{}JaGXAB`j~d^ie1O^=R{b`!&xL*=5%Y~=aMk=K!}5u zI17c(*k~tLhXCe(Jg`wu>dV!)$-E-gXP6|$^lA7bFIPn1a4`IGA9T~@pDxpm-Lc6| zI3SRnW2er~+m-$4^_%iWMn<%B{c4fQ&3CD;&oEYF`YiP_0HEVt$ndJ8T9DUn&E)C&-da=Bs_4W0M)_%PzU#jaf zjMbRx0UbJgII?NWCc1a`p6KP+9?dr7`>zM+ujyG*?Tqxhf#prCGDPLn=d(V;BtL6< zA5cSmJ$XDHavv(A^v(Y+w`Xn5q-UOdO1n2+6-!G?N5)0hN@Lh%P@Sj_xlm zD*8zb!=wtPCZW#{45Ve*Y*buaOc@y&E7~9(`IVKGN={(ods%^;QGD}jt7A9k>}@<+1%#_BVSDohW-^m7j>%jX&}H-VwqO7jy|!TKAk&oC Date: Mon, 3 Aug 2026 15:45:51 +0000 Subject: [PATCH 396/445] Automatic changelog for PR #1309 [ci skip] --- html/changelogs/AutoChangeLog-pr-1309.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1309.yml diff --git a/html/changelogs/AutoChangeLog-pr-1309.yml b/html/changelogs/AutoChangeLog-pr-1309.yml new file mode 100644 index 000000000000..ed3d8c0228c3 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1309.yml @@ -0,0 +1,4 @@ +author: "chazzyjazzy" +delete-after: True +changes: + - code_imp: "adds a landmark for the red news reporter" \ No newline at end of file From 74c209296845a57535a95fa33a51f5b018b66313 Mon Sep 17 00:00:00 2001 From: Kailey Date: Tue, 4 Aug 2026 02:07:44 +1000 Subject: [PATCH 397/445] TV directionals :) (#1313) ## About The Pull Request Adds proper subtypes & directionals for the fluffy TVs THEYRE EVEN ANIMATED HOW FREAKING AWESOME IS THAT Credit to GhoulPen for the new sprites ## Why It's Good For The Game More directionals are a mapping win. dreamseeker_Px5BaFwPIW ## Changelog :cl: KCartridge add: proper TV subtypes add: directionals for TV /:cl: --- modular_darkpack/modules/decor/code/decor.dm | 15 ++++++++++++++- .../modules/decor/icons/television.dmi | Bin 6695 -> 22973 bytes 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/modular_darkpack/modules/decor/code/decor.dm b/modular_darkpack/modules/decor/code/decor.dm index d971030fbcd5..231ffa79eed8 100644 --- a/modular_darkpack/modules/decor/code/decor.dm +++ b/modular_darkpack/modules/decor/code/decor.dm @@ -786,10 +786,23 @@ /obj/structure/fluff/tv name = "\improper TV" - desc = "A slightly battered looking TV. Various infomercials play on a loop, accompanied by a jaunty tune." + desc = "A slightly battered looking TV. It's off" icon = 'modular_darkpack/modules/decor/icons/television.dmi' + icon_state = "tv_off" + density = TRUE + +/obj/structure/fluff/tv/news + desc = "A slightly battered looking TV. Looks like you're not on the news... this time." icon_state = "tv_news" +/obj/structure/fluff/tv/nature + desc = "A slightly battered looking TV. A documentary about a rabbit named 'Lepix'." + icon_state = "tv_nature" + +/obj/structure/fluff/tv/analog + desc = "A slightly battered looking TV. It might be broken." + icon_state = "tv_analog" + /obj/structure/fluff/tv/order name = "order screen" desc = "A slightly battered looking TV. It shows a menu to order from." diff --git a/modular_darkpack/modules/decor/icons/television.dmi b/modular_darkpack/modules/decor/icons/television.dmi index f244e6fbc8acea691df9fc4a7d9a97f48c227e9b..50383c21c4ab56e4c24b2fb1ce36beaf864d01a8 100644 GIT binary patch literal 22973 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
    9?x`$3G^QW3(FFDEJvF_I;t^h-?iuIp~We~t-> zU~U~nW@$V@=%FFx3nU*?EDL=7vVlCswebiS6RsrG`IDHT=m7x;XzWp9RiGs<7B+!( z>hP)FZ_$JP&43tSDD8TZ69_jUwp7P#0fck9xvPjKsV3CzL`sX<$EX0OpIGqC`!xb7dlu07=j4^$n=*9 z`g`2KcwPA0^{7$zsUgF{-VyR(spOefA6LCPY*XOTR-7ZfXQ3mbUx?AYj+`~T@O=d! z?Lgo+=g|%bDB9S(djx1syb~)s2S1z9b8Fy%3vK*@y?I|d1H%adqodL4V1_6^Rb&H`iP@IEZ)PLL&5;!faoTod^Hu5&!NWvv1zgx&4lFy>u&b!i`(?-W ff5VDUcNmc*cGuX90lC0DW{{Gc`txcTi?IIy116IA literal 6695 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 Date: Mon, 3 Aug 2026 16:08:15 +0000 Subject: [PATCH 398/445] Automatic changelog for PR #1313 [ci skip] --- html/changelogs/AutoChangeLog-pr-1313.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1313.yml diff --git a/html/changelogs/AutoChangeLog-pr-1313.yml b/html/changelogs/AutoChangeLog-pr-1313.yml new file mode 100644 index 000000000000..e6d4017f31af --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1313.yml @@ -0,0 +1,5 @@ +author: "KCartridge" +delete-after: True +changes: + - rscadd: "proper TV subtypes" + - rscadd: "directionals for TV" \ No newline at end of file From e932181729fededccc7fa97b1ec3a3b526c81354 Mon Sep 17 00:00:00 2001 From: Lucy Date: Mon, 3 Aug 2026 14:59:55 -0400 Subject: [PATCH 399/445] allow Open Seal to unlock cars (#1335) ## About The Pull Request upstream port of https://github.com/Monkestation/crimson-grid/pull/95 exactly what it says on the tin - Open Seal now works on cars too. ## Changelog :cl: add: Open Seal can now unlock cars. /:cl: --- modular_darkpack/modules/cars/code/car.dm | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modular_darkpack/modules/cars/code/car.dm b/modular_darkpack/modules/cars/code/car.dm index 0e41f33c4b5d..3a2efa0c4515 100644 --- a/modular_darkpack/modules/cars/code/car.dm +++ b/modular_darkpack/modules/cars/code/car.dm @@ -150,6 +150,11 @@ add_overlay(image(icon = src.icon, icon_state = src.icon_state, pixel_x = -32, pixel_y = -32)) icon_state = "empty" + var/static/list/loc_connections = list( + COMSIG_ATOM_MAGICALLY_UNLOCKED = PROC_REF(on_magic_unlock), + ) + AddElement(/datum/element/connect_loc, loc_connections) + /obj/darkpack_car/Destroy() STOP_PROCESSING(SScarpool, src) QDEL_NULL(engine_sound_loop) @@ -770,5 +775,14 @@ on = FALSE engine_sound_loop.stop() +/// Signal proc for [COMSIG_ATOM_MAGICALLY_UNLOCKED]. Unlock and open up when we get knock casted. +/obj/darkpack_car/proc/on_magic_unlock(datum/source, datum/spell, atom/caster) + SIGNAL_HANDLER + + if(locked) + return + playsound(src, 'modular_darkpack/modules/cars/sounds/open.ogg', 50, TRUE) + locked = FALSE + #undef DOAFTER_SOURCE_CAR #undef CAR_TANK_MAX From 7184a3b963f6f070a8d34ef5765e0882063164a4 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:00:26 +0000 Subject: [PATCH 400/445] Automatic changelog for PR #1335 [ci skip] --- html/changelogs/AutoChangeLog-pr-1335.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1335.yml diff --git a/html/changelogs/AutoChangeLog-pr-1335.yml b/html/changelogs/AutoChangeLog-pr-1335.yml new file mode 100644 index 000000000000..16c08d3f83f5 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1335.yml @@ -0,0 +1,4 @@ +author: "Absolucy" +delete-after: True +changes: + - rscadd: "Open Seal can now unlock cars." \ No newline at end of file From cfeaaab983253293a3a6c5a50194e2d570b3f412 Mon Sep 17 00:00:00 2001 From: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:36:46 -0500 Subject: [PATCH 401/445] st_stat changes that affects the mob is handled better (#1334) ## About The Pull Request Shifts them from flags it checks for in a loop to just directly calling a proc as only one stat has these flags making them kinda pointless. ## Why It's Good For The Game Ensures its easier to write code that changes mob stats based on there stats and that its easy to make sure its ALWAYS updated and doenst need a helper called after every update. ## Changelog :cl: code: Changing stats based on stamina or dexterity is handled smarter /:cl: --- code/__DEFINES/~darkpack/storyteller_stats.dm | 4 +--- .../modules/storyteller_stats/code/_st_stats.dm | 3 +++ .../code/mob_affecting_adjustments/mob_procs.dm | 6 +----- .../code/st_stats/default_attributes.dm | 17 +++++++++++++++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/code/__DEFINES/~darkpack/storyteller_stats.dm b/code/__DEFINES/~darkpack/storyteller_stats.dm index 7e384fd4aaf8..9b066f82cc47 100644 --- a/code/__DEFINES/~darkpack/storyteller_stats.dm +++ b/code/__DEFINES/~darkpack/storyteller_stats.dm @@ -1,7 +1,5 @@ // Stat Flags -#define AFFECTS_HEALTH (1<<0) -#define AFFECTS_SPEED (1<<1) -#define AFFECTS_STATS (1<<2) // If the stat affects other stats, like courage or permanent willpower. +#define AFFECTS_STATS (1<<0) // If the stat affects other stats, like courage or permanent willpower. #define STAT_FREEBIE_POINTS /datum/st_stat/freebie diff --git a/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm b/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm index b7cd90e076fa..58236463e3dc 100644 --- a/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm +++ b/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm @@ -195,3 +195,6 @@ points -= amount freebie_cost_spent += amount return TRUE + +/datum/st_stat/proc/update_mob(mob/living/our_mob, initial) + return diff --git a/modular_darkpack/modules/storyteller_stats/code/mob_affecting_adjustments/mob_procs.dm b/modular_darkpack/modules/storyteller_stats/code/mob_affecting_adjustments/mob_procs.dm index 4f7ff1e5b93e..d67ab257a4cc 100644 --- a/modular_darkpack/modules/storyteller_stats/code/mob_affecting_adjustments/mob_procs.dm +++ b/modular_darkpack/modules/storyteller_stats/code/mob_affecting_adjustments/mob_procs.dm @@ -58,11 +58,7 @@ /mob/living/proc/update_modifiers_from_stats(initial = FALSE) for(var/stat_typepath in storyteller_stats) var/datum/st_stat/stat_datum = storyteller_stats[stat_typepath] - if(stat_datum.stat_flags & AFFECTS_HEALTH) - recalculate_max_health(initial) - if(stat_datum.stat_flags & AFFECTS_SPEED) - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/dexterity, multiplicative_slowdown = -(st_get_stat(STAT_DEXTERITY) / 20)) - + stat_datum.update_mob(src, initial) /datum/preferences/proc/apply_stats_from_prefs(mob/living/carbon/human/character) character.storyteller_stats = preference_storyteller_stats.Copy() diff --git a/modular_darkpack/modules/storyteller_stats/code/st_stats/default_attributes.dm b/modular_darkpack/modules/storyteller_stats/code/st_stats/default_attributes.dm index 194cb87643c3..7069f15513aa 100644 --- a/modular_darkpack/modules/storyteller_stats/code/st_stats/default_attributes.dm +++ b/modular_darkpack/modules/storyteller_stats/code/st_stats/default_attributes.dm @@ -3,17 +3,24 @@ description = "Affects your unarmed attack damage multiplier. Increases your chances to knock down an opponent in unarmed combat." subcategory = "Physical" + /datum/st_stat/attribute/dexterity name = "Dexterity" description = "Affects your speed and melee weapon accuracy. Increases your defense against being knocked down in unarmed combat. Increases the speed of certain actions." subcategory = "Physical" - stat_flags = AFFECTS_SPEED + +/datum/st_stat/attribute/dexterity/update_mob(mob/living/our_mob, initial) + our_mob.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/dexterity, multiplicative_slowdown = -(get_score() / 20)) + /datum/st_stat/attribute/stamina name = "Stamina" description = "Affects your maximum health. Used in Quietus." subcategory = "Physical" - stat_flags = AFFECTS_HEALTH + +/datum/st_stat/attribute/stamina/update_mob(mob/living/our_mob, initial) + our_mob.recalculate_max_health(initial) + /datum/st_stat/attribute/charisma name = "Charisma" @@ -30,6 +37,12 @@ description = "A measure of how well a character makes a first impression. Used in social disciplines and makes your character more attractive." subcategory = "Social" +/* For the bimbo in the audience +/datum/st_stat/attribute/appearance/update_mob(mob/living/our_mob, initial) + update_bloodquality_from_appearance() +*/ + + /datum/st_stat/attribute/perception name = "Perception" description = "A character's ability to observe their environment. Increases your examine speed. Used in Auspex." From 18003d17d2db58a4b34ba49cf90c216038cde6bf Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 00:37:08 +0000 Subject: [PATCH 402/445] Automatic changelog for PR #1334 [ci skip] --- html/changelogs/AutoChangeLog-pr-1334.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1334.yml diff --git a/html/changelogs/AutoChangeLog-pr-1334.yml b/html/changelogs/AutoChangeLog-pr-1334.yml new file mode 100644 index 000000000000..a592072357fc --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1334.yml @@ -0,0 +1,4 @@ +author: "FalloutFalcon" +delete-after: True +changes: + - code_imp: "Changing stats based on stamina or dexterity is handled smarter" \ No newline at end of file From 23caf544e387fec734c3f819bcada76560cce0e5 Mon Sep 17 00:00:00 2001 From: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:38:06 -0500 Subject: [PATCH 403/445] Lazylist stat lists (#1312) ## About The Pull Request Free memory. ## Why It's Good For The Game Each mob has alot of stats. This means they have a lot of lists. Lists eat memory ## Changelog :cl: code: makes some stat lists properly lazylists /:cl: --------- Co-authored-by: chazzyjazzy <33268885+chazzyjazzy@users.noreply.github.com> --- .../modules/storyteller_stats/code/_st_stats.dm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm b/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm index 58236463e3dc..02f42bf7e6e9 100644 --- a/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm +++ b/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm @@ -26,10 +26,10 @@ /// If the user can spend points on that stat. var/editable = TRUE - /// A dictionary of modifiers to this attribute. - var/list/modifiers = list() - /// A dictionary of auto success scores to this attribute. - var/list/auto_successes = list() + /// LAZYLIST. A dictionary of modifiers to this attribute. + var/list/modifiers + /// LAZYLIST. A dictionary of auto success scores to this attribute. + var/list/auto_successes /// What score does this stat start out with at character creation. var/starting_score = 0 /// How many points are in this stat category that the player can use. Used in abstract classes only. From 47ac0dc9aa0be913fd2d9606bf6e07e910cfd137 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 00:38:32 +0000 Subject: [PATCH 404/445] Automatic changelog for PR #1312 [ci skip] --- html/changelogs/AutoChangeLog-pr-1312.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1312.yml diff --git a/html/changelogs/AutoChangeLog-pr-1312.yml b/html/changelogs/AutoChangeLog-pr-1312.yml new file mode 100644 index 000000000000..fc5ea387b248 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1312.yml @@ -0,0 +1,4 @@ +author: "FalloutFalcon" +delete-after: True +changes: + - code_imp: "makes some stat lists properly lazylists" \ No newline at end of file From f8fd69b478c49124275ce271fe14c1efd29d96e8 Mon Sep 17 00:00:00 2001 From: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:39:07 -0500 Subject: [PATCH 405/445] update contribution guide (#1294) ## About The Pull Request Guide says TG alot and has some outdated info, but more importantly, it says nothing for AI which we do have a strong policy for atleast internally. --------- Co-authored-by: chazzyjazzy <33268885+chazzyjazzy@users.noreply.github.com> --- .github/CONTRIBUTING.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 0b8e8a11ad58..4636f08cf765 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -30,24 +30,31 @@ First things first, we want to make it clear how you can contribute (if you've n ## Getting Started -/tg/station doesn't have a list of goals and features to add; we instead allow freedom for contributors to suggest and create their ideas for the game. That doesn't mean we aren't determined to squash bugs, which unfortunately pop up a lot due to the deep complexity of the game. Here are some useful starting guides, if you want to contribute or if you want to know what challenges you can tackle with zero knowledge about the game's code structure. +DarkPack13/SecondCity doesn't have a list of goals and features to add; we instead allow freedom for contributors to suggest and create their ideas for the game. That doesn't mean we aren't determined to squash bugs, which unfortunately pop up a lot due to the deep complexity of the game. Here are some useful starting guides, if you want to contribute or if you want to know what challenges you can tackle with zero knowledge about the game's code structure. If you want to contribute the first thing you'll need to do is [set up Git](https://hackmd.io/@tgstation/HJ8OdjNBc) so you can download the source code. After setting it up, optionally navigate your git commandline to the project folder and run the command: `git config blame.ignoreRevsFile .git-blame-ignore-revs`. -We have a [list of guides on the wiki](http://tgstation13.org/wiki/Guides#Development_and_Contribution_Guides) that will help you get started contributing to /tg/station with Git and Dream Maker. For beginners, it is recommended you work on small projects like bugfixes at first. If you need help learning to program in BYOND, check out this [repository of resources](http://www.byond.com/developer/articles/resources). +We have a [list of guides on the wiki](http://tgstation13.org/wiki/Guides#Development_and_Contribution_Guides) that will help you get started contributing to DarkPack13/SecondCity with Git and Dream Maker. For beginners, it is recommended you work on small projects like bugfixes at first. If you need help learning to program in BYOND, check out this [repository of resources](http://www.byond.com/developer/articles/resources). There is an open list of approachable issues for [your inspiration here](https://github.com/tgstation/tgstation/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22). You can of course, as always, ask for help on the Discord channels or the forums. We're just here to have fun and help out, so please don't expect professional support. +## AI Disclosure +Darkpack does not accept any AI-generated code. This to all aspects like use of any inbuilt Gen-AI features in IDEs. **All use of AI in the process of contributing to the project should be disclosed.** +#### Why? +For communication (pr bodies, reviews, etc.), AI creates a large burden on maintainers. It creates a communication barrier between the contributor and maintainer, as you now have to filter every response. We want YOUR thoughts on why you added something or why it's good for the game. +For code, while applicable to many projects, it's especially true that AI is bad at writing DM or SS13 code. It's a language with a very small set of data that's mostly SS13-derived projects. It makes frequent hallucinations and assumptions from hodgepodging together every SS13 codebase. Code written by AI instantly suffers from a bus factor of 0; if even you don't understand your code, how is anyone meant to maintain it? +In general its a big resource drain for a project of volunteers and I would ask you please respect the time of them. + ## Meet the Team -### Headcoder +### ~~Headcoder~~ -The Headcoder is responsible for controlling, adding, and removing maintainers from the project. In addition to filling the role of a normal maintainer, they have sole authority on who becomes a maintainer, as well as who remains a maintainer and who does not. +~~The Headcoder is responsible for controlling, adding, and removing maintainers from the project. In addition to filling the role of a normal maintainer, they have sole authority on who becomes a maintainer, as well as who remains a maintainer and who does not.~~ -### Maintainers +### Maintainers/Antediluvian Maintainers are quality control. If a proposed pull request doesn't meet the following specifications, they can request you to change it, or simply just close the pull request. Maintainers are required to give a reason for closing the pull request. @@ -70,9 +77,9 @@ These are not steadfast rules as maintainers are expected to use their best judg -### Issue Managers +### ~~Issue Managers~~ -Issue Managers help out the project by labelling bug reports and PRs and closing bug reports which are duplicates or are no longer applicable. +~~Issue Managers help out the project by labelling bug reports and PRs and closing bug reports which are duplicates or are no longer applicable.~~
    What You Can and Can't Do as an Issue Manager @@ -208,6 +215,6 @@ Github actions that require additional configuration are disabled on the reposit ## Using the Maintainer Role Ping in Discord -When your PR is labeled as "Stale", you are permitted to ping the `@Maintainer` role on the /tg/station Discord. Using this ping for anything else can be met with escalating timeouts and referral to /tg/station's Discord moderators for further infractions. +When your PR is labeled as "Stale", you are permitted to ping the `@Antediluvian` or `@Review Ping` role on the DarkPack13/SecondCity Discord. -Feel free to engage and obtain general feedback in the Coding General channel without the role ping before your PR goes stale to build interest and get reviews. +Feel free to engage and obtain general feedback in the #development channel without the role ping before your PR goes stale to build interest and get reviews. From e75b631ab11ed8a5684b7d10e4f85e58242f0e72 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 02:20:50 +0000 Subject: [PATCH 406/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-1309.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1312.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1313.yml | 5 ----- html/changelogs/AutoChangeLog-pr-1334.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1335.yml | 4 ---- html/changelogs/archive/2026-08.yml | 11 +++++++++++ 6 files changed, 11 insertions(+), 21 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-1309.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1312.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1313.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1334.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1335.yml diff --git a/html/changelogs/AutoChangeLog-pr-1309.yml b/html/changelogs/AutoChangeLog-pr-1309.yml deleted file mode 100644 index ed3d8c0228c3..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1309.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "chazzyjazzy" -delete-after: True -changes: - - code_imp: "adds a landmark for the red news reporter" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1312.yml b/html/changelogs/AutoChangeLog-pr-1312.yml deleted file mode 100644 index fc5ea387b248..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1312.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - code_imp: "makes some stat lists properly lazylists" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1313.yml b/html/changelogs/AutoChangeLog-pr-1313.yml deleted file mode 100644 index e6d4017f31af..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1313.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "KCartridge" -delete-after: True -changes: - - rscadd: "proper TV subtypes" - - rscadd: "directionals for TV" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1334.yml b/html/changelogs/AutoChangeLog-pr-1334.yml deleted file mode 100644 index a592072357fc..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1334.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - code_imp: "Changing stats based on stamina or dexterity is handled smarter" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1335.yml b/html/changelogs/AutoChangeLog-pr-1335.yml deleted file mode 100644 index 16c08d3f83f5..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1335.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Absolucy" -delete-after: True -changes: - - rscadd: "Open Seal can now unlock cars." \ No newline at end of file diff --git a/html/changelogs/archive/2026-08.yml b/html/changelogs/archive/2026-08.yml index 0ab9f8710cb4..3d6f44485824 100644 --- a/html/changelogs/archive/2026-08.yml +++ b/html/changelogs/archive/2026-08.yml @@ -5,3 +5,14 @@ - code_imp: new var on datum level for storing info on what source book it comes from - config: Adds config for limiting merit based on official status +2026-08-04: + Absolucy: + - rscadd: Open Seal can now unlock cars. + FalloutFalcon: + - code_imp: makes some stat lists properly lazylists + - code_imp: Changing stats based on stamina or dexterity is handled smarter + KCartridge: + - rscadd: proper TV subtypes + - rscadd: directionals for TV + chazzyjazzy: + - code_imp: adds a landmark for the red news reporter From b25bd4bed58b7e7d6eda9137112053f244a2ed26 Mon Sep 17 00:00:00 2001 From: FalloutFalcon Date: Tue, 4 Aug 2026 11:30:44 -0500 Subject: [PATCH 407/445] reworks darkpack's old ai controllers to new behavoir tree system minor behavoir reworks like corvid's theft being more intresting --- .../code/beastmaster.bt.compiled.json | 1 + .../hostile/abyss_tentacle.bt.compiled.json | 1 + .../basic_mobs/corvid/corvid.bt.compiled.json | 1 + .../corvid/corvid_hoard.bt.compiled.json | 1 + code/__DEFINES/~darkpack/ai/ai_blackboard.dm | 2 + code/__DEFINES/~darkpack/discipline.dm | 4 + code/datums/ai/dog/dog_controller.dm | 1 - .../code/modules/mob/living/living_defines.dm | 1 - .../beastmaster/code/beastmaster.bt.json | 6 + .../beastmaster/code/beastmaster_ai.dm | 56 +++--- .../beastmaster/code/beastmaster_commands.dm | 10 +- .../nonhuman/hostile/abyss_tentacle.bt.json | 85 +++++++++ .../code/nonhuman/hostile/abyss_tentacle.dm | 144 ++++++--------- .../code/nonhuman/hostile/graveyard_zombie.dm | 25 +-- .../npc/code/nonhuman/hostile/werewolf.dm | 21 +-- .../discipline/obtenebration/obtenebration.dm | 28 +-- .../code/basic_mobs/banes/suffocating.dm | 25 +-- .../basic_mobs/{bird.dm => corvid/_bird.dm} | 62 ++++++- .../code/basic_mobs/{ => corvid}/bird_ai.dm | 23 +-- .../code/basic_mobs/corvid/corvid.bt.json | 171 ++++++++++++++++++ .../basic_mobs/corvid/corvid_hoard.bt.json | 88 +++++++++ tgstation.dme | 5 +- 22 files changed, 545 insertions(+), 216 deletions(-) create mode 100644 build/behavior_trees/modular_darkpack/modules/beastmaster/code/beastmaster.bt.compiled.json create mode 100644 build/behavior_trees/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.bt.compiled.json create mode 100644 build/behavior_trees/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid.bt.compiled.json create mode 100644 build/behavior_trees/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid_hoard.bt.compiled.json create mode 100644 code/__DEFINES/~darkpack/ai/ai_blackboard.dm create mode 100644 modular_darkpack/modules/beastmaster/code/beastmaster.bt.json create mode 100644 modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.bt.json rename modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/{bird.dm => corvid/_bird.dm} (66%) rename modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/{ => corvid}/bird_ai.dm (68%) create mode 100644 modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid.bt.json create mode 100644 modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid_hoard.bt.json diff --git a/build/behavior_trees/modular_darkpack/modules/beastmaster/code/beastmaster.bt.compiled.json b/build/behavior_trees/modular_darkpack/modules/beastmaster/code/beastmaster.bt.compiled.json new file mode 100644 index 000000000000..adc4945ab6de --- /dev/null +++ b/build/behavior_trees/modular_darkpack/modules/beastmaster/code/beastmaster.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree","override_id":"pet_command"} diff --git a/build/behavior_trees/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.bt.compiled.json b/build/behavior_trees/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.bt.compiled.json new file mode 100644 index 000000000000..e43f7f8b8f18 --- /dev/null +++ b/build/behavior_trees/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/tentacle_release_target","time_between_perform":5.0}],"observer_abort":2,"key":"BB_abyss_tentacle_mode","value":"Passive"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/tentacle_crush_victim"}],"observer_abort":0,"key":"BB_abyss_tentacle_mode","value":"Aggressive"}],"observer_abort":0,"key":"BB_abyss_tentacle_grabbed"},{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/tentacle_grab_target"}],"observer_abort":3,"key":"Current Target"}],"observer_abort":0,"invert":true,"key":"BB_abyss_tentacle_mode","value":"Passive"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","vision_range":2,"hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid.bt.compiled.json b/build/behavior_trees/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid.bt.compiled.json new file mode 100644 index 000000000000..a2940becfe97 --- /dev/null +++ b/build/behavior_trees/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_contained_in_obj","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/container_attackable","children":[{"type":"/datum/bt_node/ai_behavior/break_out_of_object/from_bb","target_key":"BB_basic_mob_escape_target"}]},{"type":"/datum/bt_node/ai_behavior/resist"}]}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/corvid_hoard"},{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}]}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/random_speech/corvid"},{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"chance":0.05}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid_hoard.bt.compiled.json b/build/behavior_trees/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid_hoard.bt.compiled.json new file mode 100644 index 000000000000..b61e77c5149b --- /dev/null +++ b/build/behavior_trees/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid_hoard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_is_holding_item","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"hoard_location","required_dist":1},{"type":"/datum/bt_node/ai_behavior/drop_all_held_items"}]}],"observer_abort":1,"key":"Literally me"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"hoard_item_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/parrot","target_key":"hoard_item_target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"hoard_item_target"}]}],"observer_abort":1,"key":"hoard_item_target"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/parrot_hoard_item","target_key":"hoard_item_target","vision_range":12}],"chance":0.05}]} diff --git a/code/__DEFINES/~darkpack/ai/ai_blackboard.dm b/code/__DEFINES/~darkpack/ai/ai_blackboard.dm new file mode 100644 index 000000000000..6189c6bc5c39 --- /dev/null +++ b/code/__DEFINES/~darkpack/ai/ai_blackboard.dm @@ -0,0 +1,2 @@ +#define BB_ABYSS_TENTACLE_MODE "BB_abyss_tentacle_mode" +#define BB_ABYSS_TENTACLE_GRABBED "BB_abyss_tentacle_grabbed" diff --git a/code/__DEFINES/~darkpack/discipline.dm b/code/__DEFINES/~darkpack/discipline.dm index 3b3a6a3fd6f1..77fa833703d6 100644 --- a/code/__DEFINES/~darkpack/discipline.dm +++ b/code/__DEFINES/~darkpack/discipline.dm @@ -58,3 +58,7 @@ #define CATEGORY_DISCIPLINES "DISCIPLINES" /// Category for the keybinds where you activate individual powers #define CATEGORY_DISCIPLINE_POWERS "DISCIPLINE POWERS" + +#define ABYSS_TENTACLE_MODE_AGGRESSIVE "Aggressive" +#define ABYSS_TENTACLE_MODE_CONTROL "Control" +#define ABYSS_TENTACLE_MODE_PASSIVE "Passive" diff --git a/code/datums/ai/dog/dog_controller.dm b/code/datums/ai/dog/dog_controller.dm index 5dfb0061113f..208660f7c44e 100644 --- a/code/datums/ai/dog/dog_controller.dm +++ b/code/datums/ai/dog/dog_controller.dm @@ -5,7 +5,6 @@ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) ai_movement = /datum/ai_movement/basic_avoidance - #warn readd /datum/bt_node/subtree/find_and_hunt_target/find_dog_food, // DARKPACK EDIT ADD - NPC behavior_tree_json = "modular_darkpack/master_files/code/datums/ai/dog/dog.bt.json" // DARKPACK EDIT CHANGE - (Find dog food.) /** diff --git a/modular_darkpack/master_files/code/modules/mob/living/living_defines.dm b/modular_darkpack/master_files/code/modules/mob/living/living_defines.dm index 7acebd7c239b..416cf7041877 100644 --- a/modular_darkpack/master_files/code/modules/mob/living/living_defines.dm +++ b/modular_darkpack/master_files/code/modules/mob/living/living_defines.dm @@ -44,7 +44,6 @@ var/list/datum/component/obeys_commands/minion_command_components = list() var/tentacle_escape_attempt = 0 - var/tentacle_aggro_mode = "Aggressive" var/possessed = FALSE //dominate 5 body posession var/datum/weakref/conditioner // dominate 4 //obfuscate icon, client side diff --git a/modular_darkpack/modules/beastmaster/code/beastmaster.bt.json b/modular_darkpack/modules/beastmaster/code/beastmaster.bt.json new file mode 100644 index 000000000000..7597ab7bccc5 --- /dev/null +++ b/modular_darkpack/modules/beastmaster/code/beastmaster.bt.json @@ -0,0 +1,6 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/beastmaster_summon", + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" +} diff --git a/modular_darkpack/modules/beastmaster/code/beastmaster_ai.dm b/modular_darkpack/modules/beastmaster/code/beastmaster_ai.dm index a926d68e87bb..a0ff7a9b350e 100644 --- a/modular_darkpack/modules/beastmaster/code/beastmaster_ai.dm +++ b/modular_darkpack/modules/beastmaster/code/beastmaster_ai.dm @@ -1,6 +1,4 @@ /datum/ai_controller/basic_controller/beastmaster_summon - #warn fix - /* blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/beastmaster_enemies, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/beastmaster_enemies, @@ -8,76 +6,76 @@ BB_BEASTMASTER_ENEMIES_LIST = list(), ) ai_movement = /datum/ai_movement/basic_avoidance + + behavior_tree_json = "modular_darkpack/modules/beastmaster/code/beastmaster.bt.json" + + /* planning_subtrees = list( /datum/bt_node/subtree/basic_melee_attack_subtree, /datum/bt_node/subtree/target_retaliate, /datum/bt_node/subtree/pet_planning, ) + */ + /datum/targeting_strategy/basic/beastmaster_enemies //this is an exact copy of the parent without faction logic. -/datum/targeting_strategy/basic/beastmaster_enemies/can_attack(mob/living/source, atom/movable/target, vision_range) - var/datum/ai_controller/basic_controller/our_controller = source.ai_controller - - if(isnull(our_controller)) - return FALSE - - if(isturf(target) || isnull(target)) +/datum/targeting_strategy/basic/beastmaster_enemies/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) + if(isturf(the_target) || isnull(the_target)) return FALSE - if(isobj(target.loc)) - var/obj/container = target.loc + if(isobj(the_target.loc)) + var/obj/container = the_target.loc if(container.resistance_flags & INDESTRUCTIBLE) return FALSE - if(ismob(target)) - if(source.loc == target) + if(ismob(the_target)) + if(living_mob.loc == the_target) return FALSE - if(HAS_TRAIT(target, TRAIT_GODMODE)) + if(HAS_TRAIT(the_target, TRAIT_GODMODE)) return FALSE - if(vision_range && get_dist(source, target) > vision_range) + if(vision_range && get_dist(living_mob, the_target) > vision_range) return FALSE - if(!can_see(source, target, vision_range)) + if(!can_see(living_mob, the_target, vision_range)) return FALSE - if(source.see_invisible < target.invisibility) + if(living_mob.see_invisible < the_target.invisibility) return FALSE - if(!isturf(source.loc)) + if(!isturf(living_mob.loc)) return FALSE - if(isturf(target.loc) && source.z != target.z) + if(isturf(the_target.loc) && living_mob.z != the_target.z) return FALSE - if(!isliving(target)) + if(!isliving(the_target)) return FALSE - var/mob/living/living_target = target + var/mob/living/living_target = the_target // don't attack friends - var/list/friends = our_controller.blackboard[BB_FRIENDS_LIST] + var/list/friends = controller.blackboard[BB_FRIENDS_LIST] if(friends && (living_target in friends)) return FALSE - // check if we have a commanded target - var/mob/living/commanded_target = our_controller.blackboard[BB_CURRENT_PET_TARGET] + // check if we have a commanded the_target + var/mob/living/commanded_target = controller.blackboard[BB_CURRENT_PET_TARGET] if(commanded_target) if(living_target == commanded_target) - if(living_target.stat > our_controller.blackboard[BB_TARGET_MINIMUM_STAT]) + if(living_target.stat > controller.blackboard[BB_TARGET_MINIMUM_STAT]) return FALSE return TRUE return FALSE - // check if target is in our enemies list - var/list/enemies = our_controller.blackboard[BB_BEASTMASTER_ENEMIES_LIST] + // check if the_target is in our enemies list + var/list/enemies = controller.blackboard[BB_BEASTMASTER_ENEMIES_LIST] if(enemies && length(enemies)) if(living_target in enemies) - if(living_target.stat > our_controller.blackboard[BB_TARGET_MINIMUM_STAT]) + if(living_target.stat > controller.blackboard[BB_TARGET_MINIMUM_STAT]) return FALSE return TRUE return FALSE -*/ diff --git a/modular_darkpack/modules/beastmaster/code/beastmaster_commands.dm b/modular_darkpack/modules/beastmaster/code/beastmaster_commands.dm index 52120bf27502..5b1b99a2163d 100644 --- a/modular_darkpack/modules/beastmaster/code/beastmaster_commands.dm +++ b/modular_darkpack/modules/beastmaster/code/beastmaster_commands.dm @@ -87,8 +87,7 @@ return // attack the target - #warn fix - // controller.queue_behavior(attack_behaviour, BB_CURRENT_TARGET, targeting_strategy_key) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, attack_subtree) /datum/pet_command/attack/beastmaster/proc/on_enemy_death(mob/living/dead_enemy) SIGNAL_HANDLER @@ -119,10 +118,7 @@ parent.ai_controller.set_blackboard_key(BB_CURRENT_PET_TARGET, new_target) parent.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, new_target) parent.ai_controller.cancel_current_plan() - #warn fix - /* - parent.ai_controller.able_to_plan = TRUE - */ + else // no more enemies, clear and return to previous behavior parent.ai_controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) @@ -229,5 +225,3 @@ /datum/pet_command/befriend_target/execute_action(datum/ai_controller/controller) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - #warn fix - // return SUBTREE_RETURN_FINISH_PLANNING diff --git a/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.bt.json b/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.bt.json new file mode 100644 index 000000000000..f693eaaf49e2 --- /dev/null +++ b/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.bt.json @@ -0,0 +1,85 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/abyss_tentacle", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_ABYSS_TENTACLE_MODE", + "value": "\"Passive\"" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/tentacle_release_target", + "vars": { + "time_between_perform": "0.5 SECONDS" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "key": "BB_ABYSS_TENTACLE_GRABBED" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "key": "BB_ABYSS_TENTACLE_MODE", + "value": "\"Aggressive\"" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/tentacle_crush_victim" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "invert": true, + "key": "BB_ABYSS_TENTACLE_MODE", + "value": "\"Passive\"" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/tentacle_grab_target" + } + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "vision_range": 2, + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.dm b/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.dm index 0ea6e58f2c0c..8439961de4c3 100644 --- a/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.dm +++ b/modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.dm @@ -25,121 +25,97 @@ GLOBAL_LIST_EMPTY(global_tentacle_grabs) mobility_flags = NONE move_resist = MOVE_FORCE_EXTREMELY_STRONG - environment_smash = ENVIRONMENT_SMASH_NONE + ai_controller = /datum/ai_controller/basic_controller/abyss_tentacle + var/mob/living/carbon/human/owner - var/mob/living/grabbed_mob = null var/list/recently_released = list() - var/aggro_mode = "Aggressive" COOLDOWN_DECLARE(grab_cooldown) COOLDOWN_DECLARE(damage_cooldown) - ai_controller = /datum/ai_controller/basic_controller/abyss_tentacle /datum/ai_controller/basic_controller/abyss_tentacle blackboard = list( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/abyss_tentacle, ) ai_movement = /datum/ai_movement/complete_stop - #warn fix - /* - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/bt_node/subtree/tentacle_grab_and_crush, - ) - */ -#warn fix - /* -/datum/bt_node/subtree/tentacle_grab_and_crush + behavior_tree_json = "modular_darkpack/modules/npc/code/nonhuman/hostile/abyss_tentacle.bt.json" -/datum/bt_node/subtree/tentacle_grab_and_crush/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/abyss_tentacle/tentacle = controller.pawn - if(!istype(tentacle)) - return - if(tentacle.aggro_mode == "Passive") - if(tentacle.grabbed_mob) - tentacle.release_grabbed_mob() - return +/datum/targeting_strategy/abyss_tentacle + +/datum/targeting_strategy/abyss_tentacle/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) + var/mob/living/basic/abyss_tentacle/tentacle = astype(living_mob) + var/mob/living/target = astype(the_target) + if(!tentacle || !target) + return FALSE + //dont attack our owner, dead things, other tentacles, things being grabbed by tentacles, or things recently released + if(target == tentacle.owner) + return FALSE + if(target.stat == DEAD) + return FALSE + if(istype(target, /mob/living/basic/abyss_tentacle)) + return FALSE + if(target in GLOB.global_tentacle_grabs) + return FALSE + if(target in tentacle.recently_released) + return FALSE - if(tentacle.grabbed_mob) - if(tentacle.aggro_mode == "Aggressive") - controller.queue_behavior(/datum/ai_behavior/tentacle_crush_victim) - return SUBTREE_RETURN_FINISH_PLANNING + return ..() - var/mob/living/target = find_valid_grab_target(tentacle) - if(target) - controller.queue_behavior(/datum/ai_behavior/tentacle_grab_target, target) - return SUBTREE_RETURN_FINISH_PLANNING - return +/datum/bt_node/ai_behavior/tentacle_grab_target + time_between_perform = 2 SECONDS +/datum/bt_node/ai_behavior/tentacle_grab_target/perform(seconds_per_tick, datum/ai_controller/controller) + . = ..() + var/mob/living/basic/abyss_tentacle/tentacle = astype(controller.pawn) + var/mob/living/target = astype(controller.blackboard[BB_CURRENT_TARGET]) + if(!tentacle) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED -/datum/bt_node/subtree/tentacle_grab_and_crush/proc/find_valid_grab_target(mob/living/basic/abyss_tentacle/tentacle) - for(var/mob/living/potential_target in oview(2, tentacle)) - //dont attack our owner, dead things, other tentacles, things being grabbed by tentacles, or things recently released - if(potential_target == tentacle.owner) - continue - if(potential_target.stat == DEAD) - continue - if(istype(potential_target, /mob/living/basic/abyss_tentacle)) - continue - if(potential_target in GLOB.global_tentacle_grabs) - continue - if(potential_target in tentacle.recently_released) - continue + tentacle.grab_mob(target) - return potential_target - return null + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/tentacle_grab_target - action_cooldown = 2 SECONDS -/datum/ai_behavior/tentacle_grab_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/target = target_key - if(!istype(target)) - return FALSE - return TRUE +/datum/bt_node/ai_behavior/tentacle_crush_victim + time_between_perform = 5 SECONDS -/datum/ai_behavior/tentacle_grab_target/perform(seconds_per_tick, datum/ai_controller/controller, mob/living/target) +/datum/bt_node/ai_behavior/tentacle_crush_victim/perform(seconds_per_tick, datum/ai_controller/controller) . = ..() - var/mob/living/basic/abyss_tentacle/tentacle = controller.pawn - - if(!istype(tentacle)) + var/mob/living/basic/abyss_tentacle/tentacle = astype(controller.pawn) + var/mob/living/grabbed = astype(controller.blackboard[BB_ABYSS_TENTACLE_GRABBED]) + if(!tentacle || !grabbed) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - tentacle.grab_mob(target) + grabbed.apply_damage(40, BRUTE) + to_chat(grabbed, span_danger("The tentacle tightens its grip, crushing you!")) + playsound(tentacle, 'sound/mobs/non-humanoids/venus_trap/venus_trap_hurt.ogg', 50, FALSE) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/tentacle_crush_victim - action_cooldown = 5 SECONDS -/datum/ai_behavior/tentacle_crush_victim/perform(seconds_per_tick, datum/ai_controller/controller) - . = ..() - var/mob/living/basic/abyss_tentacle/tentacle = controller.pawn +/datum/bt_node/ai_behavior/tentacle_release_target - if(!istype(tentacle) || !tentacle.grabbed_mob) +/datum/bt_node/ai_behavior/tentacle_release_target/perform(seconds_per_tick, datum/ai_controller/controller) + . = ..() + var/mob/living/basic/abyss_tentacle/tentacle = astype(controller.pawn) + var/mob/living/grabbed = astype(controller.blackboard[BB_ABYSS_TENTACLE_GRABBED]) + if(!tentacle || !grabbed) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - tentacle.grabbed_mob.apply_damage(40, BRUTE) - to_chat(tentacle.grabbed_mob, span_danger("The tentacle tightens its grip, crushing you!")) - playsound(tentacle, 'sound/mobs/non-humanoids/venus_trap/venus_trap_hurt.ogg', 50, FALSE) - + tentacle.release_grabbed_mob() return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -*/ + /mob/living/basic/abyss_tentacle/Initialize(mapload, mob/living/summoner) . = ..() if(summoner) owner = summoner - if(owner) - var/datum/splat/vampire/vampire = get_splat_with_discipline(owner) - var/datum/discipline_power/obtenebration/arms_of_the_abyss/abyss_power = vampire?.get_discipline_power(/datum/discipline_power/obtenebration/arms_of_the_abyss) - if(abyss_power) - aggro_mode = abyss_power.aggro_mode /mob/living/basic/abyss_tentacle/Destroy(force) if(owner) @@ -147,8 +123,7 @@ GLOBAL_LIST_EMPTY(global_tentacle_grabs) var/datum/discipline_power/obtenebration/arms_of_the_abyss/abyss_power = vampire?.get_discipline_power(/datum/discipline_power/obtenebration/arms_of_the_abyss) if(abyss_power) abyss_power.active_tentacles -= src - if(grabbed_mob) - release_grabbed_mob() + release_grabbed_mob() . = ..() @@ -158,7 +133,7 @@ GLOBAL_LIST_EMPTY(global_tentacle_grabs) return if(target in GLOB.global_tentacle_grabs) return - if(grabbed_mob) + if(ai_controller?.blackboard[BB_ABYSS_TENTACLE_GRABBED]) return if(target.client) to_chat(target, span_userdanger("A shadowy tentacle grabs you!")) @@ -169,24 +144,24 @@ GLOBAL_LIST_EMPTY(global_tentacle_grabs) target.forceMove(get_turf(src)) target.set_tentacle_grab(src) - if(aggro_mode == "Control") + if(ai_controller?.blackboard[BB_ABYSS_TENTACLE_MODE] == ABYSS_TENTACLE_MODE_CONTROL) target.mobility_flags &= ~(MOBILITY_STAND | MOBILITY_MOVE) target.set_resting(TRUE, TRUE, TRUE) to_chat(target, span_userdanger("The tentacle forces you to the ground!")) - grabbed_mob = target + ai_controller?.set_blackboard_key(BB_ABYSS_TENTACLE_GRABBED, target) GLOB.global_tentacle_grabs += target RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_grabbed_mob_move)) /mob/living/basic/abyss_tentacle/proc/release_mob(mob/living/target, add_cooldown = TRUE) - if(target == grabbed_mob) - grabbed_mob = null + if(target == ai_controller?.blackboard[BB_ABYSS_TENTACLE_GRABBED]) + ai_controller?.clear_blackboard_key(BB_ABYSS_TENTACLE_GRABBED) GLOB.global_tentacle_grabs -= target target.Stun(0) target.clear_tentacle_grab() - if(aggro_mode == "Control") + if(ai_controller?.blackboard[BB_ABYSS_TENTACLE_MODE] == ABYSS_TENTACLE_MODE_CONTROL) target.mobility_flags |= (MOBILITY_STAND | MOBILITY_MOVE) target.set_resting(FALSE, TRUE, TRUE) @@ -201,6 +176,7 @@ GLOBAL_LIST_EMPTY(global_tentacle_grabs) recently_released -= target /mob/living/basic/abyss_tentacle/proc/release_grabbed_mob() + var/mob/living/grabbed_mob = ai_controller?.blackboard[BB_ABYSS_TENTACLE_GRABBED] if(grabbed_mob) release_mob(grabbed_mob, FALSE) diff --git a/modular_darkpack/modules/npc/code/nonhuman/hostile/graveyard_zombie.dm b/modular_darkpack/modules/npc/code/nonhuman/hostile/graveyard_zombie.dm index 2f2a47dc1d04..e913b194bfbe 100644 --- a/modular_darkpack/modules/npc/code/nonhuman/hostile/graveyard_zombie.dm +++ b/modular_darkpack/modules/npc/code/nonhuman/hostile/graveyard_zombie.dm @@ -76,25 +76,24 @@ icon_dead = "zombieup_dead" -#warn fix - /* // need a custom targeting strategy so they don't kill other zombies /datum/targeting_strategy/basic/zombie_darkpack -/datum/targeting_strategy/basic/zombie_darkpack/can_attack(mob/living/basic/basic_mob, atom/target, vision_range) - if(istype(target, /mob/living/basic/zombie/darkpack)) +/datum/targeting_strategy/basic/zombie_darkpack/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) + if(istype(the_target, /mob/living/basic/zombie/darkpack)) return FALSE // don't break down the fence - just try to break the gate - if(istype(target, /obj/structure/vampfence/rich)) + if(istype(the_target, /obj/structure/vampfence/rich)) return FALSE - if(istype(target, /obj/structure/vampgate)) + if(istype(the_target, /obj/structure/vampgate)) return TRUE // use the parent class for everything else return ..() +/* // planning subtree specifically for finding and targeting vampire gates /datum/bt_node/subtree/find_and_attack_vampgate var/scan_range = 9 @@ -134,20 +133,10 @@ // AI controller for darkpack zombies /datum/ai_controller/basic_controller/zombie/darkpack blackboard = list( - #warn fix - // BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/zombie_darkpack, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/zombie_darkpack, BB_TARGET_MINIMUM_STAT = DEAD, BB_VISION_RANGE = 9, ) ai_movement = /datum/ai_movement/basic_avoidance - #warn fix - /* - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/bt_node/subtree/target_retaliate, - /datum/bt_node/subtree/find_and_attack_vampgate, - /datum/bt_node/subtree/simple_find_target, - /datum/bt_node/subtree/basic_melee_attack_subtree, - ) - */ + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile.bt.json" diff --git a/modular_darkpack/modules/npc/code/nonhuman/hostile/werewolf.dm b/modular_darkpack/modules/npc/code/nonhuman/hostile/werewolf.dm index 5c3cd971e02d..8acfbc5f6d38 100644 --- a/modular_darkpack/modules/npc/code/nonhuman/hostile/werewolf.dm +++ b/modular_darkpack/modules/npc/code/nonhuman/hostile/werewolf.dm @@ -26,7 +26,7 @@ pixel_w = -8 bloodpool = 10 maxbloodpool = 10 - ai_controller = /datum/ai_controller/basic_controller/crinos_beast + ai_controller = /datum/ai_controller/basic_controller/simple/simple_hostile var/clothing_type = 0 // Set to -1 for random clothing faction = list(FACTION_HOSTILE) @@ -41,22 +41,3 @@ . = ..() AddElement(/datum/element/ai_retaliate) ADD_TRAIT(src, TRAIT_FRENETIC_AURA, INNATE_TRAIT) - -///nothing unique, just retaliation. -/datum/ai_controller/basic_controller/crinos_beast - blackboard = list( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - ) - - ai_movement = /datum/ai_movement/basic_avoidance - -#warn fix -/* - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/bt_node/subtree/escape_captivity, - /datum/bt_node/subtree/target_retaliate, - /datum/bt_node/subtree/simple_find_target, - /datum/bt_node/subtree/basic_melee_attack_subtree, - ) -*/ diff --git a/modular_darkpack/modules/powers/code/discipline/obtenebration/obtenebration.dm b/modular_darkpack/modules/powers/code/discipline/obtenebration/obtenebration.dm index 6c39144b65d5..296f37bf076c 100644 --- a/modular_darkpack/modules/powers/code/discipline/obtenebration/obtenebration.dm +++ b/modular_darkpack/modules/powers/code/discipline/obtenebration/obtenebration.dm @@ -119,7 +119,7 @@ cooldown_length = 1 TURNS var/list/active_tentacles = list() - var/aggro_mode = "Aggressive" + var/aggro_mode = ABYSS_TENTACLE_MODE_AGGRESSIVE /datum/discipline_power/obtenebration/arms_of_the_abyss/activate(atom/target) . = ..() @@ -157,7 +157,7 @@ // if we ended up making a new tentacle add it to our list and inherit set aggro_mode if(new_tentacle) - new_tentacle.aggro_mode = aggro_mode + new_tentacle.ai_controller?.set_blackboard_key(BB_ABYSS_TENTACLE_MODE, aggro_mode) active_tentacles += new_tentacle else to_chat(usr, span_warning("The area is too bright for the shadows to manifest!")) @@ -323,9 +323,9 @@ /datum/action/aggro_mode name = "Tentacle Control" desc = "Switches the aggro mode of your Arms of the Abyss" - button_icon = 'icons/hud/screen_glass.dmi' + button_icon = 'modular_darkpack/master_files/icons/hud/screen_gen.dmi' button_icon_state = "harm" - var/current_mode = "Aggressive" + var/current_mode = ABYSS_TENTACLE_MODE_AGGRESSIVE var/datum/discipline_power/obtenebration/arms_of_the_abyss/abyss_power /datum/action/aggro_mode/New(Target) @@ -347,9 +347,9 @@ return var/list/options = list( - "Aggressive" = "Aggressive (grab and damage targets)", - "Control" = "Control (grab and restrain without damage)", - "Passive" = "Passive (don't attack or grab)" + ABYSS_TENTACLE_MODE_AGGRESSIVE = "Aggressive (grab and damage targets)", + ABYSS_TENTACLE_MODE_CONTROL = "Control (grab and restrain without damage)", + ABYSS_TENTACLE_MODE_PASSIVE = "Passive (don't attack or grab)" ) var/select = tgui_input_list(tentacle_owner, "Select tentacle behaviour", "Tentacle Mode", options) @@ -363,13 +363,13 @@ var/tentacles = 0 for(var/mob/living/basic/abyss_tentacle/T in abyss_power?.active_tentacles) if(T && !QDELETED(T)) - var/was_passive = (T.aggro_mode == "Passive") - T.aggro_mode = select + var/was_passive = (T.ai_controller?.blackboard[BB_ABYSS_TENTACLE_MODE] == ABYSS_TENTACLE_MODE_PASSIVE) + T.ai_controller?.set_blackboard_key(BB_ABYSS_TENTACLE_MODE, abyss_power.aggro_mode) tentacles++ - if(select == "Passive" && T.grabbed_mob) + if(select == ABYSS_TENTACLE_MODE_PASSIVE && T.ai_controller?.blackboard[BB_ABYSS_TENTACLE_GRABBED]) T.release_grabbed_mob() - else if(was_passive && select != "Passive") + else if(was_passive && select != ABYSS_TENTACLE_MODE_PASSIVE) T.recently_released.Cut() if(tentacles) @@ -378,11 +378,11 @@ /datum/action/aggro_mode/proc/update_button_icon() switch(current_mode) - if("Aggressive") + if(ABYSS_TENTACLE_MODE_AGGRESSIVE) button_icon_state = "harm" - if("Control") + if(ABYSS_TENTACLE_MODE_CONTROL) button_icon_state = "grab" - if("Passive") + if(ABYSS_TENTACLE_MODE_PASSIVE) button_icon_state = "disarm" build_all_button_icons() diff --git a/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/banes/suffocating.dm b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/banes/suffocating.dm index 8ebce9bf6bfa..33b6b3a38b48 100644 --- a/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/banes/suffocating.dm +++ b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/banes/suffocating.dm @@ -4,31 +4,10 @@ maxHealth = 50 health = 50 - ai_controller = /datum/ai_controller/basic_controller/suffocating_bane - mob_size = MOB_SIZE_SMALL -/datum/ai_controller/basic_controller/suffocating_bane - blackboard = list( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - ) - - ai_movement = /datum/ai_movement/basic_avoidance - -#warn fix -/* - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/bt_node/subtree/escape_captivity, - /datum/bt_node/subtree/simple_find_target, - /datum/bt_node/subtree/attack_obstacle_in_path, - /datum/bt_node/subtree/basic_melee_attack_subtree, - /datum/bt_node/subtree/random_speech/suffocating_bane - ) - - -/datum/bt_node/subtree/random_speech/suffocating_bane +/* Cant be asked to setup a ai controller for 1 random speech. +/datum/bt_node/ai_behavior/random_speech/suffocating_bane speech_chance = 15 speak = list("gasps") sound = list( diff --git a/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/bird.dm b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/_bird.dm similarity index 66% rename from modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/bird.dm rename to modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/_bird.dm index ae252e62ff10..f1bbdc67270c 100644 --- a/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/bird.dm +++ b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/_bird.dm @@ -39,13 +39,16 @@ /mob/living/basic/corvid/Initialize(mapload) . = ..() add_verb(src, /mob/living/proc/toggle_resting) - var/datum/action/innate/togglecorvidflight/toggleflight = new() - toggleflight.Grant(src) - AddElement(/datum/element/ai_retaliate) + GRANT_ACTION(/datum/action/innate/togglecorvidflight) + AddElement(/datum/element/ai_retaliate) // Birds dont forget AddElement(/datum/element/pet_bonus, "caw") AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) update_appearance(UPDATE_ICON) + // For bird crime (theft). + ai_controller.set_blackboard_key(BB_HOARD_LOCATION, get_turf(src)) + AddElement(/datum/element/dextrous, can_throw = TRUE, hands_count = 1) + /mob/living/basic/corvid/update_icon_state() . = ..() if(stat == DEAD) @@ -97,6 +100,58 @@ corvid.update_icon(UPDATE_ICON) + +/// Master proc which will determine the intent of OUR attacks on an object and summon the relevant procs accordingly. +/// This is pretty much meant for players, AI will use the task-specific procs instead. +/mob/living/basic/corvid/early_melee_attack(atom/target, list/modifiers, ignore_cooldown) + . = ..() + if(.) + return + + if(iscarbon(target) && steal_from_mob(target)) + return BASIC_MOB_END_ATTACK_CHAIN_COOLDOWN + +/// Picks up an item from the ground and puts it in our claws. Returns TRUE if we picked it up, FALSE otherwise. +/mob/living/basic/corvid/put_in_hand_check(obj/item/item_to_pick_up) + if(item_to_pick_up.w_class > WEIGHT_CLASS_SMALL) + balloon_alert(src, "too big to pick up!") + return FALSE + + return ..() + +/mob/living/basic/corvid/put_in_hand(obj/item/target, hand_index, forced = FALSE, ignore_anim = TRUE, visuals_only = FALSE) + if(..()) + visible_message( + span_notice("[src] grabs [target]!"), + span_notice("You grab [target]!"), + span_hear("You hear the sounds of wings flapping furiously."), + ) + return TRUE + +/// Looks for an item that we can snatch and puts it in our claws. Returns TRUE if we picked it up, FALSE otherwise. +/mob/living/basic/corvid/proc/steal_from_mob(mob/living/carbon/victim) + if(!isnull(get_active_held_item())) + balloon_alert(src, "already holding something!") + return FALSE + + for(var/obj/item/stealable in victim.held_items) + if(stealable.w_class > WEIGHT_CLASS_SMALL) + continue + + if(!victim.temporarilyRemoveItemFromInventory(stealable)) + continue + + dropItemToGround(stealable) + visible_message( + span_notice("[src] wrestles [stealable] out of [victim]'s hand!"), + span_notice("You wrestles [stealable] out of [victim]'s hand!"), + span_hear("You hear the sounds of wings flapping furiously."), + ) + return TRUE + + return FALSE + + /datum/emote/corvid mob_type_allowed_typecache = /mob/living/basic/corvid mob_type_blacklist_typecache = list() @@ -117,6 +172,7 @@ name = "raven" desc = "Unlike a crow, it has a wedge shaped tail." verb_say = "gronks" + verb_exclaim = "gronks" speak_emote = list("gronks") /mob/living/basic/corvid/raven/Initialize(mapload) diff --git a/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/bird_ai.dm b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/bird_ai.dm similarity index 68% rename from modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/bird_ai.dm rename to modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/bird_ai.dm index 36a760ee9df9..11dfc4df0dbe 100644 --- a/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/bird_ai.dm +++ b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/bird_ai.dm @@ -1,28 +1,25 @@ /datum/ai_controller/basic_controller/corvid + behavior_tree_json = "modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid.bt.json" blackboard = list( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, + BB_HOARD_LOCATION_RANGE = 12, ) - ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance -#warn fix -/* - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/bt_node/subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/bt_node/subtree/flee_target, - /datum/bt_node/subtree/random_speech/corvid, - /datum/bt_node/subtree/find_and_hunt_target/find_shiney, - ) -/datum/bt_node/subtree/random_speech/corvid +/datum/bt_node/ai_behavior/random_speech/corvid speech_chance = 5 speak = list("Caw!") sound = list('modular_darkpack/modules/werewolf_the_apocalypse/sounds/emotes/caw.ogg') emote_hear = list("Caws.") + +/// Use spawnpoint as nest, carry loot home, then go steal more. +/datum/bt_node/subtree/corvid_hoard + behavior_tree_json = "modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid_hoard.bt.json" + +/* /datum/bt_node/subtree/find_and_hunt_target/find_shiney target_key = BB_LOW_PRIORITY_HUNTING_TARGET hunting_behavior = /datum/ai_behavior/hunt_target/find_shiney diff --git a/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid.bt.json b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid.bt.json new file mode 100644 index 000000000000..c829e0023909 --- /dev/null +++ b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid.bt.json @@ -0,0 +1,171 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/corvid", + "type": "selector", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_contained_in_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/container_attackable", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_out_of_object/from_bb", + "vars": { + "target_key": "BB_BASIC_MOB_ESCAPE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/corvid_hoard" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/corvid" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + } + ] + } + ] +} diff --git a/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid_hoard.bt.json b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid_hoard.bt.json new file mode 100644 index 000000000000..0e11f57d2f09 --- /dev/null +++ b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/corvid/corvid_hoard.bt.json @@ -0,0 +1,88 @@ +{ + "dm_type": "/datum/bt_node/subtree/parrot_hoard", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_holding_item", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_MY_PAWN" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HOARD_LOCATION", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/drop_all_held_items" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_HOARD_ITEM_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HOARD_ITEM_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/parrot", + "vars": { + "target_key": "BB_HOARD_ITEM_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_HOARD_ITEM_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/parrot_hoard_item", + "vars": { + "target_key": "BB_HOARD_ITEM_TARGET", + "vision_range": 12 + } + } + } + ] +} diff --git a/tgstation.dme b/tgstation.dme index dcc0d96689da..65674b86c418 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -481,6 +481,7 @@ #include "code\__DEFINES\~darkpack\vampire_clan.dm" #include "code\__DEFINES\~darkpack\walls.dm" #include "code\__DEFINES\~darkpack\xp_and_freebie.dm" +#include "code\__DEFINES\~darkpack\ai\ai_blackboard.dm" #include "code\__DEFINES\~darkpack\dcs\beastmaster_signals.dm" #include "code\__DEFINES\~darkpack\dcs\bouncer_signals.dm" #include "code\__DEFINES\~darkpack\dcs\fire_signals.dm" @@ -8200,12 +8201,12 @@ #include "modular_darkpack\modules\werewolf_the_apocalypse\code\silver_damage.dm" #include "modular_darkpack\modules\werewolf_the_apocalypse\code\totems.dm" #include "modular_darkpack\modules\werewolf_the_apocalypse\code\werewolf_globals.dm" -#include "modular_darkpack\modules\werewolf_the_apocalypse\code\basic_mobs\bird.dm" -#include "modular_darkpack\modules\werewolf_the_apocalypse\code\basic_mobs\bird_ai.dm" #include "modular_darkpack\modules\werewolf_the_apocalypse\code\basic_mobs\wolf.dm" #include "modular_darkpack\modules\werewolf_the_apocalypse\code\basic_mobs\banes\_banes.dm" #include "modular_darkpack\modules\werewolf_the_apocalypse\code\basic_mobs\banes\religion.dm" #include "modular_darkpack\modules\werewolf_the_apocalypse\code\basic_mobs\banes\suffocating.dm" +#include "modular_darkpack\modules\werewolf_the_apocalypse\code\basic_mobs\corvid\_bird.dm" +#include "modular_darkpack\modules\werewolf_the_apocalypse\code\basic_mobs\corvid\bird_ai.dm" #include "modular_darkpack\modules\werewolf_the_apocalypse\code\changing_breeds\corax.dm" #include "modular_darkpack\modules\werewolf_the_apocalypse\code\gifts\_gift.dm" #include "modular_darkpack\modules\werewolf_the_apocalypse\code\gifts\auspices\ahroun.dm" From 4ce29cbb6790b205d91dbc30891f49f6451125ec Mon Sep 17 00:00:00 2001 From: FalloutFalcon Date: Tue, 4 Aug 2026 11:36:28 -0500 Subject: [PATCH 408/445] runs updatepaths for tg pull --- _maps/map_files/Vampire/westfield_mall/westfield_mall.dmm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_maps/map_files/Vampire/westfield_mall/westfield_mall.dmm b/_maps/map_files/Vampire/westfield_mall/westfield_mall.dmm index 752fc6365e09..6c345779b1ca 100644 --- a/_maps/map_files/Vampire/westfield_mall/westfield_mall.dmm +++ b/_maps/map_files/Vampire/westfield_mall/westfield_mall.dmm @@ -3164,7 +3164,7 @@ "akH" = ( /obj/structure/flora/bush/flowers_br/style_random, /obj/structure/flora/bush/flowers_pp/style_random, -/obj/item/food/grown/moonflower, +/obj/item/food/grown/flower/moonflower, /turf/open/misc/grass, /area/vtm/westfield_mall/forest) "akI" = ( From 64218a7a5501a997bd6e2599b4b0061d2aaf66c1 Mon Sep 17 00:00:00 2001 From: buffyuwu Date: Tue, 4 Aug 2026 16:00:19 -0400 Subject: [PATCH 409/445] ports discipline stat descriptions from TFN (#1288) ## About The Pull Request image ## Changelog :cl: add: ported discipline stat descriptions from TFN /:cl: --------- Co-authored-by: chazzyjazzy <33268885+chazzyjazzy@users.noreply.github.com> Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> --- .../modules/powers/code/discipline/animalism.dm | 7 ++++++- .../modules/powers/code/discipline/auspex/auspex.dm | 7 ++++++- .../powers/code/discipline/bloodheal/bloodheal.dm | 3 ++- .../modules/powers/code/discipline/celerity/celerity.dm | 7 ++++++- .../powers/code/discipline/daimonion/daimonion.dm | 7 ++++++- .../modules/powers/code/discipline/dementation.dm | 7 ++++++- .../modules/powers/code/discipline/dominate/dominate.dm | 7 ++++++- .../powers/code/discipline/fortitude/fortitude.dm | 7 ++++++- .../modules/powers/code/discipline/healer_valeren.dm | 7 ++++++- .../modules/powers/code/discipline/melpominee.dm | 8 +++++++- .../modules/powers/code/discipline/mytherceria.dm | 7 ++++++- .../modules/powers/code/discipline/necromancy.dm | 7 ++++++- .../modules/powers/code/discipline/obeah/obeah.dm | 7 ++++++- .../powers/code/discipline/obfuscate/obfuscate.dm | 7 ++++++- .../code/discipline/obtenebration/obtenebration.dm | 7 ++++++- .../modules/powers/code/discipline/potence/potence.dm | 7 ++++++- .../modules/powers/code/discipline/presence/presence.dm | 7 ++++++- .../modules/powers/code/discipline/protean/protean.dm | 7 ++++++- .../modules/powers/code/discipline/quietus/quietus.dm | 7 ++++++- .../modules/powers/code/discipline/serpentis.dm | 7 ++++++- .../modules/powers/code/discipline/temporis/temporis.dm | 7 ++++++- .../powers/code/discipline/thanatosis/thanatosis.dm | 7 ++++++- .../powers/code/discipline/thaumaturgy/path_of_blood.dm | 8 +++++++- .../modules/powers/code/discipline/valeren.dm | 7 ++++++- .../powers/code/discipline/vicissitude/vicissitude.dm | 7 ++++++- .../modules/powers/code/discipline/visceratika.dm | 7 ++++++- .../CharacterPreferences/DisciplinesPage.tsx | 9 ++++++++- tgui/packages/tgui/styles/darkpack.scss | 4 ++++ 28 files changed, 166 insertions(+), 27 deletions(-) diff --git a/modular_darkpack/modules/powers/code/discipline/animalism.dm b/modular_darkpack/modules/powers/code/discipline/animalism.dm index 9cef836b2fed..94de633a1a76 100644 --- a/modular_darkpack/modules/powers/code/discipline/animalism.dm +++ b/modular_darkpack/modules/powers/code/discipline/animalism.dm @@ -1,6 +1,11 @@ /datum/discipline/animalism name = "Animalism" - desc = "Summons spectral animals over your targets. Violates Masquerade." + desc = {"Summons spectral animals over your targets. Violates Masquerade. +● Summon Rat: Passive +●● Summon Cat: Passive +●●● Summon Wolf: Passive +●●●● Summon Bat: Passive +●●●●● Skitter: Passive"} icon_state = "animalism" power_type = /datum/discipline_power/animalism diff --git a/modular_darkpack/modules/powers/code/discipline/auspex/auspex.dm b/modular_darkpack/modules/powers/code/discipline/auspex/auspex.dm index 4f3e85b35eb3..717542a2312b 100644 --- a/modular_darkpack/modules/powers/code/discipline/auspex/auspex.dm +++ b/modular_darkpack/modules/powers/code/discipline/auspex/auspex.dm @@ -8,7 +8,12 @@ /datum/discipline/auspex name = "Auspex" - desc = "Allows to see entities, auras and their health through walls." + desc = {"Allows to see entities, auras and their health through walls. +● Heightened Senses: Passive +●● Aura Perception: Passive +●●● The Spirit's Touch: Passive +●●●● Telepathy: Intelligence + Subterfuge vs. target's Willpower +●●●●● Psychic Projection: Perception + Awareness (difficulty 7)"} icon_state = "auspex" power_type = /datum/discipline_power/auspex diff --git a/modular_darkpack/modules/powers/code/discipline/bloodheal/bloodheal.dm b/modular_darkpack/modules/powers/code/discipline/bloodheal/bloodheal.dm index 7a997eeba507..f551f5d4152d 100644 --- a/modular_darkpack/modules/powers/code/discipline/bloodheal/bloodheal.dm +++ b/modular_darkpack/modules/powers/code/discipline/bloodheal/bloodheal.dm @@ -3,7 +3,8 @@ /datum/discipline/bloodheal name = "Bloodheal" - desc = "Use the power of your Vitae to mend your flesh." + desc = {"Use the power of your Vitae to mend your flesh. +● Bloodheal: Stamina + Survival (difficulty 8) - roll only on interrupted cast"} icon_state = "bloodheal" power_type = /datum/discipline_power/bloodheal selectable = FALSE diff --git a/modular_darkpack/modules/powers/code/discipline/celerity/celerity.dm b/modular_darkpack/modules/powers/code/discipline/celerity/celerity.dm index 16d2d1584d66..476fa030c50d 100644 --- a/modular_darkpack/modules/powers/code/discipline/celerity/celerity.dm +++ b/modular_darkpack/modules/powers/code/discipline/celerity/celerity.dm @@ -1,6 +1,11 @@ /datum/discipline/celerity name = "Celerity" - desc = "Boosts your speed. Violates Masquerade." + desc = {"Boosts your speed. Violates Masquerade. +● Celerity 1: Passive +●● Celerity 2: Passive +●●● Celerity 3: Passive +●●●● Celerity 4: Passive +●●●●● Celerity 5: Passive"} icon_state = "celerity" power_type = /datum/discipline_power/celerity diff --git a/modular_darkpack/modules/powers/code/discipline/daimonion/daimonion.dm b/modular_darkpack/modules/powers/code/discipline/daimonion/daimonion.dm index 978192525bed..8fa7afb7c649 100644 --- a/modular_darkpack/modules/powers/code/discipline/daimonion/daimonion.dm +++ b/modular_darkpack/modules/powers/code/discipline/daimonion/daimonion.dm @@ -1,6 +1,11 @@ /datum/discipline/daimoinon name = "Daimoinon" - desc = "Draw power from the demons and infernal nature of Hell. Use subtle power to manipulate people and when you must, draw upon fire itself and protect yourself." + desc = {"Draw power from the demons and infernal nature of Hell. Use subtle power to manipulate people and when you must, draw upon fire itself and protect yourself. +● Sense the Sin: Perception + Empathy vs. target's Self-Control + 4 +●● Fear of the Void Below: Wits + Intimidation vs. target's Courage + 4 +●●● Conflagration: No roll +●●●● Psychomania: target's lowest Virtue +●●●●● Condemnation: Intelligence + Occult vs. target's Willpower"} icon_state = "daimonion" clan_restricted = TRUE power_type = /datum/discipline_power/daimoinon diff --git a/modular_darkpack/modules/powers/code/discipline/dementation.dm b/modular_darkpack/modules/powers/code/discipline/dementation.dm index 51d6aba9757c..43b3c1bb4bee 100644 --- a/modular_darkpack/modules/powers/code/discipline/dementation.dm +++ b/modular_darkpack/modules/powers/code/discipline/dementation.dm @@ -1,6 +1,11 @@ /datum/discipline/dementation name = "Dementation" - desc = "Makes all humans in radius mentally ill for a moment, supressing their defending ability." + desc = {"Makes all humans in radius mentally ill for a moment, supressing their defending ability. +● Passion: Charisma + Empathy +●● The Haunting: Manipulation + Subterfuge +●●● Eyes of Chaos: Perception + Occult +●●●● Voice of Madness: Manipulation + Empathy +●●●●● Total Insanity: Manipulation + Intimidation"} icon_state = "dementation" clan_restricted = TRUE power_type = /datum/discipline_power/dementation diff --git a/modular_darkpack/modules/powers/code/discipline/dominate/dominate.dm b/modular_darkpack/modules/powers/code/discipline/dominate/dominate.dm index 381f8159bd79..80076d4ffb7a 100644 --- a/modular_darkpack/modules/powers/code/discipline/dominate/dominate.dm +++ b/modular_darkpack/modules/powers/code/discipline/dominate/dominate.dm @@ -2,7 +2,12 @@ /datum/discipline/dominate name = "Dominate" - desc = "Suppresses will of your targets and forces them to obey you, if their will is not more powerful than yours." + desc = {"Suppresses will of your targets and forces them to obey you, if their will is not more powerful than yours. +● Command: Manipulation + Intimidation +●● Mesmerize: Manipulation + Leadership +●●● The Forgetful Mind: Wits + Subterfuge +●●●● Conditioning: Charisma + Leadership +●●●●● Possession: Charisma + Intimidation"} icon_state = "dominate" power_type = /datum/discipline_power/dominate var/list/botched_targets //a lazylist of weakrefs diff --git a/modular_darkpack/modules/powers/code/discipline/fortitude/fortitude.dm b/modular_darkpack/modules/powers/code/discipline/fortitude/fortitude.dm index b0fc61089dc7..2e1670ac46a2 100644 --- a/modular_darkpack/modules/powers/code/discipline/fortitude/fortitude.dm +++ b/modular_darkpack/modules/powers/code/discipline/fortitude/fortitude.dm @@ -1,6 +1,11 @@ /datum/discipline/fortitude name = "Fortitude" - desc = "Boosts armor." + desc = {"Boosts armor. +● Fortitude 1: Passive +●● Fortitude 2: Passive +●●● Fortitude 3: Passive +●●●● Fortitude 4: Passive +●●●●● Fortitude 5: Passive"} icon_state = "fortitude" power_type = /datum/discipline_power/fortitude diff --git a/modular_darkpack/modules/powers/code/discipline/healer_valeren.dm b/modular_darkpack/modules/powers/code/discipline/healer_valeren.dm index 225a22b69f3c..8556a1f3fccb 100644 --- a/modular_darkpack/modules/powers/code/discipline/healer_valeren.dm +++ b/modular_darkpack/modules/powers/code/discipline/healer_valeren.dm @@ -1,6 +1,11 @@ /datum/discipline/valeren name = "Healer Valeren" - desc = "Use your third eye in healing or protecting needs." + desc = {"Use your third eye in healing or protecting needs. +● Sense Vitality: Passive +●● Anesthetic Touch: Passive +●●● Corpore Sano: Passive +●●●● Shepherd's Watch: Passive +●●●●● Unburden the Bestial Soul: Passive"} icon_state = "valeren" clan_restricted = TRUE power_type = /datum/discipline_power/valeren diff --git a/modular_darkpack/modules/powers/code/discipline/melpominee.dm b/modular_darkpack/modules/powers/code/discipline/melpominee.dm index 47560f46a109..a93acf936f36 100644 --- a/modular_darkpack/modules/powers/code/discipline/melpominee.dm +++ b/modular_darkpack/modules/powers/code/discipline/melpominee.dm @@ -1,6 +1,12 @@ /datum/discipline/melpominee name = "Melpominee" - desc = "Named for the Greek Muse of Tragedy, Melpominee is a unique discipline of the Daughters of Cacophony. It explores the power of the voice, shaking the very soul of those nearby and allowing the vampire to perform sonic feats otherwise impossible." + desc = {"Named for the Greek Muse of Tragedy, Melpominee is a unique discipline of the Daughters of Cacophony. It explores the power of the voice, shaking the very soul of those nearby and allowing the vampire to perform sonic feats otherwise impossible. +● The Missing Voice: Passive +●● Phantom Speaker: Wits + Performance (difficulty 7) +●●● Madrigal: Wits + Performance vs. target's Wits + Awareness +●●●● Siren's Beckoning: Wits + Performance vs. target's Willpower +●●●●● Virtuosa: Passive toggle +●●●●●● Shattering Crescendo: Passive"} icon_state = "melpominee" clan_restricted = TRUE power_type = /datum/discipline_power/melpominee diff --git a/modular_darkpack/modules/powers/code/discipline/mytherceria.dm b/modular_darkpack/modules/powers/code/discipline/mytherceria.dm index f97a78d23b39..1b52be5811b2 100644 --- a/modular_darkpack/modules/powers/code/discipline/mytherceria.dm +++ b/modular_darkpack/modules/powers/code/discipline/mytherceria.dm @@ -1,6 +1,11 @@ /datum/discipline/mytherceria name = "Mytherceria" - desc = "Mytherceria is a Discipline that manifests in faerie-blooded vampires such as the Kiasyd and Maeghar. It grants the vampire mystical senses, the ability to steal knowledge, and other powers attributed to fae." + desc = {"Mytherceria is a Discipline that manifests in faerie-blooded vampires such as the Kiasyd and Maeghar. It grants the vampire mystical senses, the ability to steal knowledge, and other powers attributed to fae. +● Fey Sight: Passive +●● Darkling Trickery: Passive +●●● Goblinism: Passive +●●●● Chanjelin Ward: Passive +●●●●● Riddle Phantastique: Passive"} icon_state = "mytherceria" clan_restricted = TRUE power_type = /datum/discipline_power/mytherceria diff --git a/modular_darkpack/modules/powers/code/discipline/necromancy.dm b/modular_darkpack/modules/powers/code/discipline/necromancy.dm index 89ce91769513..ea90656ca714 100644 --- a/modular_darkpack/modules/powers/code/discipline/necromancy.dm +++ b/modular_darkpack/modules/powers/code/discipline/necromancy.dm @@ -2,7 +2,12 @@ /datum/discipline/necromancy name = "Necromancy" - desc = "Offers control over another, undead reality." + desc = {"Offers control over another, undead reality. +● Shroudsight: Perception + Awareness (difficulty 7) +●● Ethereal Horde: Wits + Occult (difficulty 6) +●●● Ashes to Ashes: Wits + Occult (difficulty 6) +●●●● Cold of the Grave: Wits + Occult (difficulty 6) +●●●●● Shambling Horde: Wits + Occult (difficulty 6)"} icon_state = "necromancy" clan_restricted = TRUE power_type = /datum/discipline_power/necromancy diff --git a/modular_darkpack/modules/powers/code/discipline/obeah/obeah.dm b/modular_darkpack/modules/powers/code/discipline/obeah/obeah.dm index e3ae6845142b..3faefe9bb145 100644 --- a/modular_darkpack/modules/powers/code/discipline/obeah/obeah.dm +++ b/modular_darkpack/modules/powers/code/discipline/obeah/obeah.dm @@ -1,6 +1,11 @@ /datum/discipline/obeah name = "Obeah" - desc = "Use your third eye in healing or protecting needs." + desc = {"Use your third eye in healing or protecting needs. +● Sense Vitality: Perception + Empathy (difficulty 7) +●● Anesthetic Touch: Willpower (difficulty 8 if unwilling) +●●● Corpore Sano: No roll +●●●● Shepherd's Watch: No roll +●●●●● Unburden the Bestial Soul: Intelligence + Empathy (difficulty 8)"} icon_state = "obeah" clan_restricted = TRUE power_type = /datum/discipline_power/obeah diff --git a/modular_darkpack/modules/powers/code/discipline/obfuscate/obfuscate.dm b/modular_darkpack/modules/powers/code/discipline/obfuscate/obfuscate.dm index 6dc7c090700e..5a68987e0a18 100644 --- a/modular_darkpack/modules/powers/code/discipline/obfuscate/obfuscate.dm +++ b/modular_darkpack/modules/powers/code/discipline/obfuscate/obfuscate.dm @@ -3,7 +3,12 @@ /datum/discipline/obfuscate name = "Obfuscate" - desc = "Makes you less noticable for living and un-living beings." + desc = {"Makes you less noticable for living and un-living beings. +● Cloak of Shadows: Passive +●● Unseen Presence: Passive +●●● Mask of a Thousand Faces: Manipulation + Performance (difficulty 7) +●●●● Vanish from the Mind's Eye: Charisma + Stealth (difficulty 6) +●●●●● Cloak the Gathering: Passive"} icon_state = "obfuscate" power_type = /datum/discipline_power/obfuscate diff --git a/modular_darkpack/modules/powers/code/discipline/obtenebration/obtenebration.dm b/modular_darkpack/modules/powers/code/discipline/obtenebration/obtenebration.dm index 6c39144b65d5..b78c2331d767 100644 --- a/modular_darkpack/modules/powers/code/discipline/obtenebration/obtenebration.dm +++ b/modular_darkpack/modules/powers/code/discipline/obtenebration/obtenebration.dm @@ -1,6 +1,11 @@ /datum/discipline/obtenebration name = "Obtenebration" - desc = "Controls the darkness around you." + desc = {"Controls the darkness around you. +● Shadow Play: Passive +●● Shroud of Night: Manipulation + Occult (difficulty 7) +●●● Arms of the Abyss: Manipulation + Occult (difficulty 7) +●●●● Black Metamorphosis: Manipulation + Courage (difficulty 7) +●●●●● Tenebrous Form: Passive"} icon_state = "obtenebration" clan_restricted = TRUE power_type = /datum/discipline_power/obtenebration diff --git a/modular_darkpack/modules/powers/code/discipline/potence/potence.dm b/modular_darkpack/modules/powers/code/discipline/potence/potence.dm index 11e318c84ce1..61b9f2e8001b 100644 --- a/modular_darkpack/modules/powers/code/discipline/potence/potence.dm +++ b/modular_darkpack/modules/powers/code/discipline/potence/potence.dm @@ -1,6 +1,11 @@ /datum/discipline/potence name = "Potence" - desc = "Boosts melee and unarmed damage." + desc = {"Boosts melee and unarmed damage. +● Potence 1: Passive +●● Potence 2: Passive +●●● Potence 3: Passive +●●●● Potence 4: Passive +●●●●● Potence 5: Passive"} icon_state = "potence" power_type = /datum/discipline_power/potence diff --git a/modular_darkpack/modules/powers/code/discipline/presence/presence.dm b/modular_darkpack/modules/powers/code/discipline/presence/presence.dm index 457dcdbb3f3f..e9826e510354 100644 --- a/modular_darkpack/modules/powers/code/discipline/presence/presence.dm +++ b/modular_darkpack/modules/powers/code/discipline/presence/presence.dm @@ -2,7 +2,12 @@ /datum/discipline/presence name = "Presence" - desc = "Allows you to attract, sway, and control crowds through supernatural allure and emotional manipulation." + desc = {"Allows you to attract, sway, and control crowds through supernatural allure and emotional manipulation. +● Awe: Charisma + Performance (difficulty 7) +●● Dread Gaze: Charisma + Intimidation vs. Wits + Courage +●●● Entrancement: Appearance + Empathy vs. Willpower +●●●● Summon: Charisma + Subterfuge (difficulty 7) +●●●●● Majesty: Courage vs. Charisma + Intimidation"} icon_state = "presence" power_type = /datum/discipline_power/presence diff --git a/modular_darkpack/modules/powers/code/discipline/protean/protean.dm b/modular_darkpack/modules/powers/code/discipline/protean/protean.dm index 90bda291fa83..3846b760cd60 100644 --- a/modular_darkpack/modules/powers/code/discipline/protean/protean.dm +++ b/modular_darkpack/modules/powers/code/discipline/protean/protean.dm @@ -1,6 +1,11 @@ /datum/discipline/protean name = "Protean" - desc = "Lets your beast out, making you stronger and faster. Violates Masquerade." + desc = {"Lets your beast out, making you stronger and faster. Violates Masquerade. +● Eyes of the Beast: Passive +●● Feral Claws: Passive +●●● Earth Meld: Passive +●●●● Shape of the Beast: Passive +●●●●● Mist Form: Passive"} icon_state = "protean" clan_restricted = TRUE power_type = /datum/discipline_power/protean diff --git a/modular_darkpack/modules/powers/code/discipline/quietus/quietus.dm b/modular_darkpack/modules/powers/code/discipline/quietus/quietus.dm index 5de620b3ae87..a66d799ce87f 100644 --- a/modular_darkpack/modules/powers/code/discipline/quietus/quietus.dm +++ b/modular_darkpack/modules/powers/code/discipline/quietus/quietus.dm @@ -1,6 +1,11 @@ /datum/discipline/quietus name = "Quietus" - desc = "The signature discipline of the Banu Haqim, Quietus allows the user to create poison and assassinate their targets quietly, maipulating their blood. Violates Masquerade." + desc = {"The signature discipline of the Banu Haqim, Quietus allows the user to create poison and assassinate their targets quietly, manipulating their blood. Violates Masquerade. +● Silence of Death: Passive +●● Scorpion's Touch: Permanent Willpower (difficulty 6) +●●● Dagon's Call: Stamina vs. target's Willpower +●●●● Baal's Caress: Passive +●●●●● Taste of Death: Stamina + Athletics (difficulty 6)"} icon_state = "quietus" clan_restricted = TRUE power_type = /datum/discipline_power/quietus diff --git a/modular_darkpack/modules/powers/code/discipline/serpentis.dm b/modular_darkpack/modules/powers/code/discipline/serpentis.dm index 1dc6318e24d0..fa40ff012224 100644 --- a/modular_darkpack/modules/powers/code/discipline/serpentis.dm +++ b/modular_darkpack/modules/powers/code/discipline/serpentis.dm @@ -1,6 +1,11 @@ /datum/discipline/serpentis name = "Serpentis" - desc = "Act like a cobra, get the powers to stun targets with your gaze and your tongue, praise the mummy traditions and spread them to your childe. Violates Masquerade." + desc = {"Act like a cobra, get the powers to stun targets with your gaze and your tongue, praise the mummy traditions and spread them to your childe. Violates Masquerade. +● The Eyes of the Serpent: Passive +●● The Tongue of the Asp: Strength (difficulty 6) +●●● The Skin of the Adder: Passive +●●●● The Form of the Cobra: Passive +●●●●● The Heart of Darkness: Passive"} icon_state = "serpentis" clan_restricted = TRUE power_type = /datum/discipline_power/serpentis diff --git a/modular_darkpack/modules/powers/code/discipline/temporis/temporis.dm b/modular_darkpack/modules/powers/code/discipline/temporis/temporis.dm index 015d780c0978..9ce9fff065f5 100644 --- a/modular_darkpack/modules/powers/code/discipline/temporis/temporis.dm +++ b/modular_darkpack/modules/powers/code/discipline/temporis/temporis.dm @@ -1,6 +1,11 @@ /datum/discipline/temporis name = "Temporis" - desc = "Temporis is a Discipline unique to the True Brujah. Supposedly a refinement of Celerity, Temporis grants the Cainite the ability to manipulate the flow of time itself." + desc = {"Temporis is a Discipline unique to the True Brujah. Supposedly a refinement of Celerity, Temporis grants the Cainite the ability to manipulate the flow of time itself. +● Hourglass of the Mind: Passive +●● Recurring Contemplation: Passive +●●● Leaden Moment: Passive +●●●● Patience of the Norns: Passive +●●●●● Clotho's Gift: Passive"} icon_state = "temporis" clan_restricted = TRUE power_type = /datum/discipline_power/temporis diff --git a/modular_darkpack/modules/powers/code/discipline/thanatosis/thanatosis.dm b/modular_darkpack/modules/powers/code/discipline/thanatosis/thanatosis.dm index 7816cc6884e1..fd0a16f90192 100644 --- a/modular_darkpack/modules/powers/code/discipline/thanatosis/thanatosis.dm +++ b/modular_darkpack/modules/powers/code/discipline/thanatosis/thanatosis.dm @@ -1,6 +1,11 @@ /datum/discipline/thanatosis name = "Thanatosis" - desc = "Offers control over your own rotted body" + desc = {"Offers control over your own rotted body. +● Hag's Wrinkles: Stamina + Subterfuge +●● Putrefaction: Dexterity + Medicine vs. target's Stamina +●●● Ashes to Ashes: Passive +●●●● Withering: Manipulation + Medicine vs. target's Stamina +●●●●● Necrosis: Dexterity + Medicine vs. target's Stamina"} icon_state = "thanatosis" clan_restricted = TRUE power_type = /datum/discipline_power/thanatosis diff --git a/modular_darkpack/modules/powers/code/discipline/thaumaturgy/path_of_blood.dm b/modular_darkpack/modules/powers/code/discipline/thaumaturgy/path_of_blood.dm index 4137ead77690..b790df7aae90 100644 --- a/modular_darkpack/modules/powers/code/discipline/thaumaturgy/path_of_blood.dm +++ b/modular_darkpack/modules/powers/code/discipline/thaumaturgy/path_of_blood.dm @@ -1,6 +1,12 @@ /datum/discipline/thaumaturgy name = "Thaumaturgy" - desc = "Opens the secrets of blood magic and how you use it, allows to steal other's blood. Violates Masquerade." + desc = {"Opens the secrets of blood magic and how you use it, allows to steal other's blood. Violates Masquerade. + Difficulty scales depending on the dots used. +● A Taste for Blood: Permanent Willpower +●● Blood Rage: Permanent Willpower +●●● Blood of Potency: Permanent Willpower +●●●● Theft of Vitae: Permanent Willpower +●●●●● Cauldron of Blood: Permanent Willpower"} icon_state = "thaumaturgy" clan_restricted = TRUE power_type = /datum/discipline_power/thaumaturgy diff --git a/modular_darkpack/modules/powers/code/discipline/valeren.dm b/modular_darkpack/modules/powers/code/discipline/valeren.dm index 97b0535e9d2e..049bf87a9aee 100644 --- a/modular_darkpack/modules/powers/code/discipline/valeren.dm +++ b/modular_darkpack/modules/powers/code/discipline/valeren.dm @@ -1,6 +1,11 @@ /datum/discipline/valeren name = "Valeren" - desc = "The warrior's path of Valeren, used by the Salubri antitribu to read and exploit weakness in their enemies." + desc = {"The warrior's path of Valeren, used by the Salubri antitribu to read and exploit weakness in their enemies. +● Sense Vitality: Perception + Empathy (difficulty 7) +●● Anesthetic Touch: Willpower (difficulty 8 if unwilling) +●●● Burning Touch: Passive +●●●● Armor of Caine's Fury: Stamina + Melee (difficulty 7) +●●●●● Vengeance of Samiel: Passive"} icon_state = "valeren" clan_restricted = TRUE power_type = /datum/discipline_power/valeren diff --git a/modular_darkpack/modules/powers/code/discipline/vicissitude/vicissitude.dm b/modular_darkpack/modules/powers/code/discipline/vicissitude/vicissitude.dm index 65607628b6d9..59c01f28be5d 100644 --- a/modular_darkpack/modules/powers/code/discipline/vicissitude/vicissitude.dm +++ b/modular_darkpack/modules/powers/code/discipline/vicissitude/vicissitude.dm @@ -3,7 +3,12 @@ /datum/discipline/vicissitude name = "Vicissitude" - desc = "It is widely known as Tzimisce art of flesh and bone shaping. Violates Masquerade." + desc = {"It is widely known as Tzimisce art of flesh and bone shaping. Violates Masquerade. +● Malleable Visage: Passive +●● Fleshcrafting: Passive +●●● Bonecrafting: Strength + Medicine (difficulty 7) +●●●● Horrid Form: Passive +●●●●● Bloodform: Passive"} icon_state = "vicissitude" clan_restricted = TRUE power_type = /datum/discipline_power/vicissitude diff --git a/modular_darkpack/modules/powers/code/discipline/visceratika.dm b/modular_darkpack/modules/powers/code/discipline/visceratika.dm index 9c09d7cf1645..894eb7c6c1db 100644 --- a/modular_darkpack/modules/powers/code/discipline/visceratika.dm +++ b/modular_darkpack/modules/powers/code/discipline/visceratika.dm @@ -1,6 +1,11 @@ /datum/discipline/visceratika name = "Visceratika" - desc = "The Discipline of Visceratika is the exclusive possession of the Gargoyle bloodline and is an extension of their natural affinity for stone, earth, and things made thereof." + desc = {"The Discipline of Visceratika is the exclusive possession of the Gargoyle bloodline and is an extension of their natural affinity for stone, earth, and things made thereof. +● Skin of the Chameleon: Passive +●● Scry the Hearthstone: Perception + Awareness +●●● Bond with the Mountain: Passive +●●●● Armor of Terra: No roll - always active +●●●●● Flow Within the Mountain: Passive"} icon_state = "visceratika" clan_restricted = TRUE power_type = /datum/discipline_power/visceratika diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/DisciplinesPage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/DisciplinesPage.tsx index 51240bcb852f..194ec40bb70d 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/DisciplinesPage.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/DisciplinesPage.tsx @@ -57,7 +57,9 @@ function DisciplineCard(props: DisciplineCardProps) { {isRare ? 'Rare Discipline' : 'Common Discipline'} - {discipline.desc} + {discipline.desc.split('\n').map((line, i) => ( + {line} + ))} } > @@ -195,6 +197,11 @@ function DisciplinesInner(props: DisciplinesInnerProps) { : "A Kindred's immortal age determines their dot balance. The higher their age, the more dots they can assign."} + + + Hover over a discipline's name to view the stats used. Passives don't require a roll. + + Date: Tue, 4 Aug 2026 16:01:04 -0400 Subject: [PATCH 410/445] tfn port of corax waddling, wing flap sounds (#1306) ## About The Pull Request tfn port of corax waddling, wing flap sounds dreamseeker_jyuSF5K7tf ## Changelog :cl: add: plays a sound when corax toggle flight add: gives corax a waddle when walking in bird form /:cl: --------- Co-authored-by: chazzyjazzy <33268885+chazzyjazzy@users.noreply.github.com> --- .../werewolf_the_apocalypse/code/basic_mobs/bird.dm | 3 +++ .../werewolf_the_apocalypse/code/species/fera_species.dm | 3 +++ .../werewolf_the_apocalypse/code/species/flight_action.dm | 8 ++++++++ 3 files changed, 14 insertions(+) diff --git a/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/bird.dm b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/bird.dm index ae252e62ff10..97efbd06e690 100644 --- a/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/bird.dm +++ b/modular_darkpack/modules/werewolf_the_apocalypse/code/basic_mobs/bird.dm @@ -44,6 +44,7 @@ AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/pet_bonus, "caw") AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) + AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) update_appearance(UPDATE_ICON) /mob/living/basic/corvid/update_icon_state() @@ -90,10 +91,12 @@ // sadly, "is flying animal" does not give us flying traits when life() is called, only during VV or upon Init. We're doing this the hard way. // the corax sprites already animate up-and-down bobbing, no need to float corvid.add_traits(list(TRAIT_MOVE_FLYING, TRAIT_NO_FLOATING_ANIM), ACTION_TRAIT) + corvid.remove_traits(list(TRAIT_WADDLING), INNATE_TRAIT) // we set this while we wait for the icons to update, otherwise there is latency else to_chat(corvid, span_notice("You settle gently back onto the ground...")) corvid.remove_traits(list(TRAIT_MOVE_FLYING, TRAIT_NO_FLOATING_ANIM), ACTION_TRAIT) + corvid.AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) corvid.update_icon(UPDATE_ICON) diff --git a/modular_darkpack/modules/werewolf_the_apocalypse/code/species/fera_species.dm b/modular_darkpack/modules/werewolf_the_apocalypse/code/species/fera_species.dm index aadc5c6793a5..90ed73d3c5c8 100644 --- a/modular_darkpack/modules/werewolf_the_apocalypse/code/species/fera_species.dm +++ b/modular_darkpack/modules/werewolf_the_apocalypse/code/species/fera_species.dm @@ -322,12 +322,15 @@ if(HAS_TRAIT(human_who_gained_species, TRAIT_FERA_FLIGHT)) var/datum/action/innate/toggle_fera_flight/ability = new(human_who_gained_species) ability.Grant(human_who_gained_species) + human_who_gained_species.AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) /datum/species/human/shifter/feral/on_species_loss(mob/living/carbon/human/human, datum/species/new_species, pref_load) . = ..() for(var/datum/action/innate/toggle_fera_flight/action in human.actions) action.Remove(human) + if(HAS_TRAIT(human, TRAIT_FERA_FLIGHT)) + REMOVE_TRAIT(human, TRAIT_WADDLING, INNATE_TRAIT) /datum/movespeed_modifier/shifter abstract_type = /datum/movespeed_modifier/shifter diff --git a/modular_darkpack/modules/werewolf_the_apocalypse/code/species/flight_action.dm b/modular_darkpack/modules/werewolf_the_apocalypse/code/species/flight_action.dm index 527a86e934aa..89fe7f43a8e3 100644 --- a/modular_darkpack/modules/werewolf_the_apocalypse/code/species/flight_action.dm +++ b/modular_darkpack/modules/werewolf_the_apocalypse/code/species/flight_action.dm @@ -16,9 +16,14 @@ if (!(HAS_TRAIT(fera_mob, TRAIT_MOVE_FLYING))) to_chat(fera_mob, span_notice("You beat your wings and begin to hover gently above the ground...")) fera_mob.add_traits(list(TRAIT_MOVE_FLYING, TRAIT_NO_FLOATING_ANIM), ACTION_TRAIT) + fera_mob.remove_traits(list(TRAIT_WADDLING), INNATE_TRAIT) + playsound(fera_mob, 'modular_darkpack/modules/external_organs/sounds/wing_flap_flying.ogg', 50, TRUE) else to_chat(fera_mob, span_notice("You settle gently back onto the ground...")) fera_mob.remove_traits(list(TRAIT_MOVE_FLYING, TRAIT_NO_FLOATING_ANIM), ACTION_TRAIT) + if(!HAS_TRAIT(fera_mob, TRAIT_WADDLING)) + fera_mob.AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) + playsound(fera_mob, 'modular_darkpack/modules/external_organs/sounds/wing_flap_flying.ogg', 50, TRUE) fera_mob.update_body_parts() // fera_mob.update_icon(UPDATE_ICON) @@ -29,6 +34,9 @@ return to_chat(fera_mob, span_notice("You settle gently back onto the ground...")) fera_mob.remove_traits(list(TRAIT_MOVE_FLYING, TRAIT_NO_FLOATING_ANIM), ACTION_TRAIT) + if(!HAS_TRAIT(fera_mob, TRAIT_WADDLING)) + fera_mob.AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) + playsound(fera_mob, 'modular_darkpack/modules/external_organs/sounds/wing_flap_flying.ogg', 50, TRUE) fera_mob.update_body_parts() From 0a1df1a5fd2f41f1e4b852a212b7cb4f2cd57784 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:01:34 +0000 Subject: [PATCH 411/445] Automatic changelog for PR #1306 [ci skip] --- html/changelogs/AutoChangeLog-pr-1306.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1306.yml diff --git a/html/changelogs/AutoChangeLog-pr-1306.yml b/html/changelogs/AutoChangeLog-pr-1306.yml new file mode 100644 index 000000000000..dd98469cf705 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1306.yml @@ -0,0 +1,5 @@ +author: "buffyuwu" +delete-after: True +changes: + - rscadd: "plays a sound when corax toggle flight" + - rscadd: "gives corax a waddle when walking in bird form" \ No newline at end of file From 257ccfe62a97d1d5c3e80a28ccf9e86100b6a82c Mon Sep 17 00:00:00 2001 From: cam_dev <59076929+drgnmstrc@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:01:48 -0400 Subject: [PATCH 412/445] "I Nonni" job for Giovanni (#1333) ## About The Pull Request image Adds a nonni role for the Giovanni family, for the nonno and nonna elders of the Clan. They must be 10th generation or older, at least 80 mortal years and 200 immortal years old. Their focus is more on the more secretive business practices of the family, providing hospitality to guests at the manor, and guiding the family youth. There can be two - one on Westfield Mall. Humans and ghouls can also be nonni, maintaining the requirement of being at least 80 years old. ## Why It's Good For The Game An extra layer of hierarchy to showcase the highly organized structure of the family. These ancient souls should help the family with maintaining a public face while still giving time to their dark arts, and add a fun dimension to the family dynamic. Enshrining them officially as a role is a nice way of distinguishing them, as the Giovanni are very respectful of their elders. Seemingly this role does exist in the TTRPG so it should be lore-friendly. ## Changelog :cl: cam_dev add: "I Nonni" role for Giovanni elders /:cl: --------- Co-authored-by: chazzyjazzy <33268885+chazzyjazzy@users.noreply.github.com> --- .../Vampire/westfield_mall/westfield_mall.dmm | 6 +++- _maps/westfieldmall.json | 4 +++ code/__DEFINES/~darkpack/jobs.dm | 1 + .../modules/jobs/code/giovanni/i_nonni.dm | 33 ++++++++++++++++++ .../modules/jobs/code/giovanni/la_famiglia.dm | 2 +- .../modules/jobs/code/landmarks.dm | 1 + .../modules/jobs/icons/landmarks.dmi | Bin 26771 -> 29849 bytes .../modules/phones/code/phone_subtypes.dm | 5 +++ tgstation.dme | 1 + 9 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 modular_darkpack/modules/jobs/code/giovanni/i_nonni.dm diff --git a/_maps/map_files/Vampire/westfield_mall/westfield_mall.dmm b/_maps/map_files/Vampire/westfield_mall/westfield_mall.dmm index 752fc6365e09..1fc0c6776e43 100644 --- a/_maps/map_files/Vampire/westfield_mall/westfield_mall.dmm +++ b/_maps/map_files/Vampire/westfield_mall/westfield_mall.dmm @@ -17155,6 +17155,10 @@ /obj/effect/mapping_helpers/door/autoname, /turf/open/floor/iron/grimy, /area/vtm/westfield_mall/interior/art_store) +"lci" = ( +/obj/effect/landmark/start/darkpack/hecata/nonni, +/turf/open/floor/wood/smooth/old, +/area/vtm/interior/bianchiBank) "lra" = ( /obj/effect/turf_decal/bordur, /turf/open/floor/plating/roofwalk, @@ -57852,7 +57856,7 @@ aXr akW aQs ajj -aae +lci att azw aTq diff --git a/_maps/westfieldmall.json b/_maps/westfieldmall.json index 69d78a767557..25f3df954b81 100644 --- a/_maps/westfieldmall.json +++ b/_maps/westfieldmall.json @@ -69,6 +69,10 @@ "spawn_positions": 3, "total_positions": 3 }, + "I Nonni": { + "spawn_positions": 1, + "total_positions": 1 + }, "La Squadra": { "spawn_positions": 5, "total_positions": 5 diff --git a/code/__DEFINES/~darkpack/jobs.dm b/code/__DEFINES/~darkpack/jobs.dm index dd034b0a78ca..0f837f86bf63 100644 --- a/code/__DEFINES/~darkpack/jobs.dm +++ b/code/__DEFINES/~darkpack/jobs.dm @@ -69,6 +69,7 @@ //Giovanni #define JOB_CAPO "Capo" +#define JOB_I_NONNI "I Nonni" #define JOB_LA_SQUADRA "La Squadra" #define JOB_LA_FAMIGLIA "La Famiglia" diff --git a/modular_darkpack/modules/jobs/code/giovanni/i_nonni.dm b/modular_darkpack/modules/jobs/code/giovanni/i_nonni.dm new file mode 100644 index 000000000000..f8477743ae4d --- /dev/null +++ b/modular_darkpack/modules/jobs/code/giovanni/i_nonni.dm @@ -0,0 +1,33 @@ +/datum/job/vampire/nonni + title = JOB_I_NONNI + faction = FACTION_GIOVANNI + total_positions = 2 + spawn_positions = 2 + supervisors = "the Family and the Traditions" + config_tag = "NONNI" + outfit = /datum/outfit/job/vampire/nonni + job_flags = CITY_JOB_FLAGS + display_order = 2 + exp_required_type_department = EXP_TYPE_GIOVANNI + departments_list = list( + /datum/job_department/giovanni, + ) + + allowed_splats = list(SPLAT_GHOUL, SPLAT_NONE, SPLAT_KINDRED) + allowed_clans = list(VAMPIRE_CLAN_GIOVANNI) + minimal_generation = 10 + minimum_immortal_age = 200 + required_character_age = 80 + description = "Your long tenure has made you an honorary head of household. Use your wisdom to guide the Family, teach the Traditions and family trades, and advise the Capo." + minimum_masquerade = 0 + +/datum/outfit/job/vampire/nonni + name = "I Nonni" + jobtype = /datum/job/vampire/nonni + glasses = /obj/item/clothing/glasses/vampire/sun + uniform = /obj/item/clothing/under/vampire/suit + suit = /obj/item/clothing/suit/vampire/trench + shoes = /obj/item/clothing/shoes/vampire + l_pocket = /obj/item/smartphone/giovanni_nonni + r_pocket = /obj/item/vamp/keys/giovanni + backpack_contents = list(/obj/item/card/credit=1) diff --git a/modular_darkpack/modules/jobs/code/giovanni/la_famiglia.dm b/modular_darkpack/modules/jobs/code/giovanni/la_famiglia.dm index 50c180c1786c..92fd139c298a 100644 --- a/modular_darkpack/modules/jobs/code/giovanni/la_famiglia.dm +++ b/modular_darkpack/modules/jobs/code/giovanni/la_famiglia.dm @@ -7,7 +7,7 @@ config_tag = "LA_FAMIGLIA" outfit = /datum/outfit/job/vampire/famiglia job_flags = CITY_JOB_FLAGS - display_order = 2 + display_order = 4 exp_required_type_department = EXP_TYPE_GIOVANNI departments_list = list( /datum/job_department/giovanni, diff --git a/modular_darkpack/modules/jobs/code/landmarks.dm b/modular_darkpack/modules/jobs/code/landmarks.dm index 7499ec6eb0b3..68a94ccea1a4 100644 --- a/modular_darkpack/modules/jobs/code/landmarks.dm +++ b/modular_darkpack/modules/jobs/code/landmarks.dm @@ -70,6 +70,7 @@ JOB_START_HELPER(law_enforcement/fbi, JOB_FEDERAL_INVESTIGATOR) name = "generic hecata start" JOB_START_HELPER(hecata/capo, JOB_CAPO) +JOB_START_HELPER(hecata/nonni, JOB_I_NONNI) JOB_START_HELPER(hecata/famiglia, JOB_LA_SQUADRA) JOB_START_HELPER(hecata/squadra, JOB_LA_FAMIGLIA) diff --git a/modular_darkpack/modules/jobs/icons/landmarks.dmi b/modular_darkpack/modules/jobs/icons/landmarks.dmi index a4b22d3a310e7170cefe00d541c8cc01d8f123f4..c15a42b7ff15b6408deb9a492527b871ccd52e23 100644 GIT binary patch literal 29849 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

    9(^+7=d8;|BWvWgIHfUj7_} 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 diff --git a/modular_darkpack/modules/phones/code/phone_subtypes.dm b/modular_darkpack/modules/phones/code/phone_subtypes.dm index 813f90e3d258..ee4c4bdb4616 100644 --- a/modular_darkpack/modules/phones/code/phone_subtypes.dm +++ b/modular_darkpack/modules/phones/code/phone_subtypes.dm @@ -114,6 +114,11 @@ , alist(NETWORK_ID = VAMPIRE_LEADER_NETWORK, OUR_ROLE = "Bank Manager", USE_JOB_TITLE = FALSE) ) +/obj/item/smartphone/giovanni_nonni + contact_networks_pre_init = list( + alist(NETWORK_ID = GIOVANNI_NETWORK, OUR_ROLE = "Bank Shareholder", USE_JOB_TITLE = FALSE) + ) + /obj/item/smartphone/giovanni_squadra contact_networks_pre_init = list( alist(NETWORK_ID = GIOVANNI_NETWORK, OUR_ROLE = "Bank Security", USE_JOB_TITLE = FALSE) diff --git a/tgstation.dme b/tgstation.dme index da4e92230718..f31d14356796 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -7479,6 +7479,7 @@ #include "modular_darkpack\modules\jobs\code\garou\warder.dm" #include "modular_darkpack\modules\jobs\code\garou\wyrmfoe.dm" #include "modular_darkpack\modules\jobs\code\giovanni\capo.dm" +#include "modular_darkpack\modules\jobs\code\giovanni\i_nonni.dm" #include "modular_darkpack\modules\jobs\code\giovanni\la_famiglia.dm" #include "modular_darkpack\modules\jobs\code\giovanni\la_squadra.dm" #include "modular_darkpack\modules\jobs\code\hunters\abbe.dm" From 3682e86835860132f6dcef98c716cd5b58f17448 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:02:29 +0000 Subject: [PATCH 413/445] Automatic changelog for PR #1333 [ci skip] --- html/changelogs/AutoChangeLog-pr-1333.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1333.yml diff --git a/html/changelogs/AutoChangeLog-pr-1333.yml b/html/changelogs/AutoChangeLog-pr-1333.yml new file mode 100644 index 000000000000..cea0a06a7718 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1333.yml @@ -0,0 +1,4 @@ +author: "cam_dev" +delete-after: True +changes: + - rscadd: "\"I Nonni\" role for Giovanni elders" \ No newline at end of file From 1329aad97aa7b78b8c06e78ab218e2bd5c35b456 Mon Sep 17 00:00:00 2001 From: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:04:38 -0500 Subject: [PATCH 414/445] Seizure Flaw/Deformity (#1318) ## About The Pull Request Adds the seizure flaw/deformity from wta20. If you botch a none spammy roll. You get stunned to the floor and shaky. Bounty https://discord.com/channels/1393132000583618701/1517636261798482042 ## Why It's Good For The Game More flaws c: Evetunally used as a required deformity for crinos-born. ## Changelog :cl: add: Seizure Flaw/Deformity /:cl: --------- Co-authored-by: chazzyjazzy <33268885+chazzyjazzy@users.noreply.github.com> --- code/datums/status_effects/drug_effects.dm | 6 +- .../code/negative_quirks/seizures.dm | 55 +++++++++++++++++++ tgstation.dme | 1 + 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 modular_darkpack/modules/merits_flaws/code/negative_quirks/seizures.dm diff --git a/code/datums/status_effects/drug_effects.dm b/code/datums/status_effects/drug_effects.dm index 70184a4a123e..402e1f642b47 100644 --- a/code/datums/status_effects/drug_effects.dm +++ b/code/datums/status_effects/drug_effects.dm @@ -45,11 +45,15 @@ tick_interval = STATUS_EFFECT_NO_TICK status_type = STATUS_EFFECT_UNIQUE alert_type = /atom/movable/screen/alert/status_effect/seizure + // DARKPACK EDIT ADD START + var/dur_min = 1 SECONDS + var/dur_max = 3 SECONDS + // DARKPACK EDIT ADD END /datum/status_effect/seizure/on_apply() if(!iscarbon(owner)) return FALSE - var/amplitude = rand(1 SECONDS, 3 SECONDS) + var/amplitude = rand(dur_min, dur_max) // DARKPACK EDIT CHANGE duration = amplitude owner.set_jitter_if_lower(100 SECONDS) owner.Paralyze(duration) diff --git a/modular_darkpack/modules/merits_flaws/code/negative_quirks/seizures.dm b/modular_darkpack/modules/merits_flaws/code/negative_quirks/seizures.dm new file mode 100644 index 000000000000..fc34c8a75be1 --- /dev/null +++ b/modular_darkpack/modules/merits_flaws/code/negative_quirks/seizures.dm @@ -0,0 +1,55 @@ +/datum/quirk/darkpack/seizures + name = "Seizures" + desc = {"When you're under the gun, you lose control of your body. + When you botch an important roll, make a Willpower check (difficulty 8). + Scoring less than three successes makes you writhe uncontrollably."} + icon = FA_ICON_BRAIN + value = -4 // Made up since its not a "real" flaw and is instead listed in the crinos-born section + gain_text = span_warning("You feel less in control of your body...") + lose_text = span_notice("You feel more in control of yourself.") + ttrpg_sources = list(/datum/source_book/wta20 = 75) + COOLDOWN_DECLARE(seizure_cooldown) + +/datum/quirk/darkpack/seizures/add(client/client_source) + RegisterSignal(quirk_holder, COMSIG_LIVING_DICE_ROLLED, PROC_REF(on_dice_rolled)) + +/datum/quirk/darkpack/seizures/remove() + UnregisterSignal(quirk_holder, COMSIG_LIVING_DICE_ROLLED) + +/datum/quirk/darkpack/seizures/proc/on_dice_rolled(mob/living/roller, datum/storyteller_roll/roll_datum, atom/target, output) + SIGNAL_HANDLER + + if(!COOLDOWN_FINISHED(src, seizure_cooldown)) + return + + if(roll_datum.spammy_roll) + return + + if(roll_datum.numerical) + if(output >= 0) + return + else + if(output != ROLL_BOTCH) + return + + var/datum/storyteller_roll/seizures/seizure_roll_datum = new() + var/result = seizure_roll_datum.st_roll(roller) + + if(result == ROLL_SUCCESS) + return + + roller.apply_status_effect(/datum/status_effect/seizure/flaw) + COOLDOWN_START(src, seizure_cooldown, 1 SCENES) + + +/datum/status_effect/seizure/flaw + dur_min = 25 SECONDS + dur_max = 35 SECONDS + + +/datum/storyteller_roll/seizures + bumper_text = "seizures" + applicable_stats = list(STAT_TEMPORARY_WILLPOWER) + difficulty = 8 + successes_needed = 3 + roll_output_type = ROLL_PRIVATE diff --git a/tgstation.dme b/tgstation.dme index f31d14356796..0d15e3b124df 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -7610,6 +7610,7 @@ #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\permanent_wound.dm" #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\pierced_veil.dm" #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\prey_exclusion.dm" +#include "modular_darkpack\modules\merits_flaws\code\negative_quirks\seizures.dm" #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\short.dm" #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\smell_of_the_grave.dm" #include "modular_darkpack\modules\merits_flaws\code\negative_quirks\speech_impediment.dm" From 13139b6c13b69cc30936b0b78fee5714404a929d Mon Sep 17 00:00:00 2001 From: breadcrombs <109193991+breadcrombs@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:04:53 -0400 Subject: [PATCH 415/445] Gives vicissitude horror form ability to see through yourself (#1308) ## About The Pull Request Gives vicissitude horror form ability to see through yourself, this should probably also be extended to garou warforms and such but it's really only the horrid form that makes it hard to see if anyone is a tile above you Screenshot 2026-07-31 133035 Screenshot 2026-07-31 133039 ## Why It's Good For The Game This prevents hitting yourself or unable to hit something when its above you in horrid form ## Changelog :cl: qol: see through can be toggled on the vicissitude horror form /:cl: Co-authored-by: chazzyjazzy <33268885+chazzyjazzy@users.noreply.github.com> --- .../modules/powers/code/discipline/vicissitude/zulo.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/modular_darkpack/modules/powers/code/discipline/vicissitude/zulo.dm b/modular_darkpack/modules/powers/code/discipline/vicissitude/zulo.dm index 27f293fde116..4fbc436206f0 100644 --- a/modular_darkpack/modules/powers/code/discipline/vicissitude/zulo.dm +++ b/modular_darkpack/modules/powers/code/discipline/vicissitude/zulo.dm @@ -49,6 +49,7 @@ GLOBAL_LIST_INIT(zulo_forms, list( /mob/living/basic/zulo/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_UNMASQUERADE, INNATE_TRAIT) + AddComponent(/datum/component/seethrough_mob) /mob/living/basic/zulo/mind_initialize() . = ..() From 7f71e2d53181092796b29a8e65f745b43e1e6095 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:05:06 +0000 Subject: [PATCH 416/445] Automatic changelog for PR #1318 [ci skip] --- html/changelogs/AutoChangeLog-pr-1318.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1318.yml diff --git a/html/changelogs/AutoChangeLog-pr-1318.yml b/html/changelogs/AutoChangeLog-pr-1318.yml new file mode 100644 index 000000000000..5b4b3cab914c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1318.yml @@ -0,0 +1,4 @@ +author: "FalloutFalcon" +delete-after: True +changes: + - rscadd: "Seizure Flaw/Deformity" \ No newline at end of file From fbd3560ced3ca069787ae6a8711ef295eda88a6f Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:05:20 +0000 Subject: [PATCH 417/445] Automatic changelog for PR #1288 [ci skip] --- html/changelogs/AutoChangeLog-pr-1288.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1288.yml diff --git a/html/changelogs/AutoChangeLog-pr-1288.yml b/html/changelogs/AutoChangeLog-pr-1288.yml new file mode 100644 index 000000000000..a4d46938b70d --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1288.yml @@ -0,0 +1,4 @@ +author: "buffyuwu" +delete-after: True +changes: + - rscadd: "ported discipline stat descriptions from TFN" \ No newline at end of file From 2618f2a24716e1e80864c8a205309e66ade6afe2 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 20:05:30 +0000 Subject: [PATCH 418/445] Automatic changelog for PR #1308 [ci skip] --- html/changelogs/AutoChangeLog-pr-1308.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1308.yml diff --git a/html/changelogs/AutoChangeLog-pr-1308.yml b/html/changelogs/AutoChangeLog-pr-1308.yml new file mode 100644 index 000000000000..30e848e34676 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1308.yml @@ -0,0 +1,4 @@ +author: "breadcrombs" +delete-after: True +changes: + - qol: "see through can be toggled on the vicissitude horror form" \ No newline at end of file From 17417ff77f7f292fedca350947cd38f697c7dc4e Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 02:17:58 +0000 Subject: [PATCH 419/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-1288.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1306.yml | 5 ----- html/changelogs/AutoChangeLog-pr-1308.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1318.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1333.yml | 4 ---- html/changelogs/archive/2026-08.yml | 11 +++++++++++ 6 files changed, 11 insertions(+), 21 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-1288.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1306.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1308.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1318.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1333.yml diff --git a/html/changelogs/AutoChangeLog-pr-1288.yml b/html/changelogs/AutoChangeLog-pr-1288.yml deleted file mode 100644 index a4d46938b70d..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1288.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "buffyuwu" -delete-after: True -changes: - - rscadd: "ported discipline stat descriptions from TFN" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1306.yml b/html/changelogs/AutoChangeLog-pr-1306.yml deleted file mode 100644 index dd98469cf705..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1306.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "buffyuwu" -delete-after: True -changes: - - rscadd: "plays a sound when corax toggle flight" - - rscadd: "gives corax a waddle when walking in bird form" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1308.yml b/html/changelogs/AutoChangeLog-pr-1308.yml deleted file mode 100644 index 30e848e34676..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1308.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "breadcrombs" -delete-after: True -changes: - - qol: "see through can be toggled on the vicissitude horror form" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1318.yml b/html/changelogs/AutoChangeLog-pr-1318.yml deleted file mode 100644 index 5b4b3cab914c..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1318.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - rscadd: "Seizure Flaw/Deformity" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1333.yml b/html/changelogs/AutoChangeLog-pr-1333.yml deleted file mode 100644 index cea0a06a7718..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1333.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "cam_dev" -delete-after: True -changes: - - rscadd: "\"I Nonni\" role for Giovanni elders" \ No newline at end of file diff --git a/html/changelogs/archive/2026-08.yml b/html/changelogs/archive/2026-08.yml index 3d6f44485824..6200f320ca0c 100644 --- a/html/changelogs/archive/2026-08.yml +++ b/html/changelogs/archive/2026-08.yml @@ -16,3 +16,14 @@ - rscadd: directionals for TV chazzyjazzy: - code_imp: adds a landmark for the red news reporter +2026-08-05: + FalloutFalcon: + - rscadd: Seizure Flaw/Deformity + breadcrombs: + - qol: see through can be toggled on the vicissitude horror form + buffyuwu: + - rscadd: ported discipline stat descriptions from TFN + - rscadd: plays a sound when corax toggle flight + - rscadd: gives corax a waddle when walking in bird form + cam_dev: + - rscadd: '"I Nonni" role for Giovanni elders' From 1a2950bc64d751625cef4e5cb8cf65eb22f82129 Mon Sep 17 00:00:00 2001 From: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:26:30 -0500 Subject: [PATCH 420/445] Fixing infinite teleport move loop (#1330) ## About The Pull Request See https://github.com/tgstation/tgstation/pull/97334 This bug was copied from that proc ## Changelog :cl: code: Fixes infinite loop caused by teleport moving while having a looped chain of grabbed mobs /:cl: --------- Co-authored-by: chazzyjazzy <33268885+chazzyjazzy@users.noreply.github.com> --- modular_darkpack/modules/z_travel/code/helpers.dm | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/modular_darkpack/modules/z_travel/code/helpers.dm b/modular_darkpack/modules/z_travel/code/helpers.dm index 33163676331d..9af6609b6006 100644 --- a/modular_darkpack/modules/z_travel/code/helpers.dm +++ b/modular_darkpack/modules/z_travel/code/helpers.dm @@ -1,16 +1,13 @@ // Copypasta of get_z_move_affected /// Returns a list of movables that should also be affected when src moves through teleporters, and src. -/atom/movable/proc/get_teleport_move_affected() - . = list(src) +/atom/movable/proc/get_teleport_move_affected(list/returning_list = list()) + . = returning_list + . |= src if(buckled_mobs) . |= buckled_mobs for(var/mob/living/buckled as anything in buckled_mobs) if(buckled.pulling) . |= buckled.pulling - if(pulling) - . |= pulling - if (pulling.buckled_mobs) - . |= pulling.buckled_mobs - - if (pulling.pulling) - . |= pulling.pulling.get_teleport_move_affected() + //we pass in the list from this proc to ensure we dont reach an infinite loop due to mobs grabbed in a loop or two mobs grabing eachother. + if(pulling && !(pulling in .)) + . |= pulling.get_teleport_move_affected(.) From 796790add1a829eb0105e319f4ead99cc747cf54 Mon Sep 17 00:00:00 2001 From: cam_dev <59076929+drgnmstrc@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:26:49 -0400 Subject: [PATCH 421/445] [BOUNTY]More crosses and QoL features for them (#1302) ## About The Pull Request This PR adds the hunter's ID (that is, a gold cross), as a starting loadout item, alongside two new crosses, the silver and gothic crosses. Gold crosses were bumped up to $30, silver crosses are $25, while gothic crosses can be purchased for $20 at the costume store (at least until I get around to making a Hot Topic PR and move it there). You can also wear crosses on your neck or belt. ## Why It's Good For The Game Crosses are certainly one of those things in the United States you'll see everywhere, in great numbers, especially the further back in time you go. It makes sense for anyone to have the option to start with one, especially since religion is such a strong vehicle for manipulation and roleplay in the setting. A small enough proportion of the creatures in the setting are affected by holy symbols to where the people playing them should be ready for them in large numbers anyways, and I think it's more interesting to balance them around this quantity. ## Testing image image image image image ## Changelog :cl: cam_dev add: added crosses to loadout menu qol: crosses can be worn on belt and neck image: added two new cross sprites /:cl: --- .../modules/jobs/code/job_items.dm | 9 +++++++++ .../modules/jobs/icons/id_items.dmi | Bin 6950 -> 7413 bytes .../modules/jobs/icons/id_onfloors.dmi | Bin 1761 -> 1859 bytes .../modules/jobs/icons/id_worn.dmi | Bin 3113 -> 3331 bytes .../loadout/code/categories/pocket_items.dm | 12 ++++++++++++ .../retail/code/stores/clothing_store.dm | 3 ++- .../retail/code/stores/costume_store.dm | 1 + 7 files changed, 24 insertions(+), 1 deletion(-) diff --git a/modular_darkpack/modules/jobs/code/job_items.dm b/modular_darkpack/modules/jobs/code/job_items.dm index 9213fe9d33aa..f7edd15c812f 100644 --- a/modular_darkpack/modules/jobs/code/job_items.dm +++ b/modular_darkpack/modules/jobs/code/job_items.dm @@ -159,10 +159,19 @@ desc = "When you come into the land that the Lord your God is giving you, you must not learn to imitate the abhorrent practices of those nations. No one shall be found among you who makes a son or daughter pass through fire, or who practices divination, or is a soothsayer, or an augur, or a sorcerer, or one who casts spells, or who consults ghosts or spirits, or who seeks oracles from the dead. For whoever does these things is abhorrent to the Lord; it is because of such abhorrent practices that the Lord your God is driving them out before you (Deuteronomy 18:9-12)." icon = 'modular_darkpack/modules/jobs/icons/id_items.dmi' icon_state = "hunter_badge" + slot_flags = ITEM_SLOT_ID | ITEM_SLOT_NECK | ITEM_SLOT_BELT ONFLOOR_ICON_HELPER('modular_darkpack/modules/jobs/icons/id_onfloors.dmi') worn_icon = 'modular_darkpack/modules/jobs/icons/id_worn.dmi' COOLDOWN_DECLARE(detonation_timer) +/obj/item/card/hunter/silver + name = "silver cross" + icon_state = "hunter_silver" + +/obj/item/card/hunter/gothic + name = "gothic cross" + icon_state = "hunter_gothic" + /obj/item/card/hunter/attack_self(mob/user) . = ..() if(!COOLDOWN_FINISHED(src, detonation_timer)) diff --git a/modular_darkpack/modules/jobs/icons/id_items.dmi b/modular_darkpack/modules/jobs/icons/id_items.dmi index e01590536e47e0e6937266eb7f5fac6d6f068d7c..a40de0132372dcbeaca3a14997f522e79bf10181 100644 GIT binary patch delta 7265 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 delta 6821 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 KMNUMnLSTY3kpd?G delta 1513 zcmVbktOSYZ8s%^TRc2HN{ErAF()Uet+`uWZsFnKp|a-n00001 zbW%=J06^y0W&i*IC3;j?bVOxyV{&P5bZKvH004NLm6XA1!!Qhn&&gAS_BzUvW$d)e zXc;}MuMiYliv^BtWV>zNe#tuM4ufb1gCxEmetscutGC1Z@l@?Uj^!DDIg)qPYt`-^ z%i=;hnYnpt%EBNGCf%BtAmLnYHf5oS<6PEd@yRgaQn&LIElkUJ*F5}scO|3lNdK*` z@7TbIP6$pCE-*QT5;CIdde8%|k_JI{a=f=0I|aOMVF^hDl78vDm2h<Mz0zPPeeMGH~K5UN`q2K?r zI2#`NM`lJygR$OUL)vpLo6OzW3Fk~XYYYGY006*^;g&_vZ(HUT&D1O`EZASHgOScT zxHBPMzdIpbzc*o~@wr@%0;IDsj-oHd-(bn)BbJup^<`6tSYD3TS4=TtWo4%Exz*Lx zoIN-LzjW3>skF9#Rx0IL1LgAidbwO+4OA){8(HDH$z&3~THK%qeMSKu>HEYkcJ z`3#8Tb7MFIV|W9EtpK(L2)lyW8X#;1ur;8J9}+!AV~b#aNc0$GiXa1wJ_T3 z8=#Zp2nOi1xR_l7Hh3ZaDELgr4*&oF000000Du|Ec>i5P>Aw?h^utkg-A8mtqov^q z(fjYjW4ixiR;^YaSvc9PxkN232YUbA#$aL9Dy6`c3~Umtf%+Im-x|Z{YX9A)NHtIw z4b}BgSFbG>yiV>fRq7iK+1sDf1jjC zFi@og2nHy+P1%6Wfn>nufW3bN-+!N^h#%}$h4`Z)sPi?Pv<4!i{`(|F(gpZusELYz zFM;bJ<^H>ycKqnS0{{R30000005_H0za2nAc6XD1IcV%P1t+sRnO*hV#BR1}21HRwsc)ric0Yx5=kAS~I^VxGTz~+mWqV71| zuJo-_xdyz>fp2`T!{nertLN}=Z}EmkJ^%m!00000007cM?Z0Onwf~-V znjb$&213Vi!np-A3+>NeBm>Uz@v;0b9I?4gxSFnfu~)gi)_-?SPEW)ugeRwobob;` zbfAsb`tPa%H3OkC!wEB>_208!18V<0^Jx8d0000000000008iRqU}GFww@4e|Dm*h zb#r!hCdDWBzP0^_($;PKqZfnHyVmv}N?Rwsj$W(-vA3=5Ka{pUbRfmApzS}Dwr)AU zI8R_e*?%Z)-EwhpF}r~g+W!Bk>WMP&>+(VXDeKo^z=-<~rL8AN+kYr+9RL6T00000 z00017JKw&K_{P5FvUfShfTm@q+x^j8{Xqx2JA1F!>-Xmw(sV=y?7_ zO~cp#n}J^M$}-oWra`TN%l_5i>MFY#(CPQHoPa-J@K4V_00000z)j;PSG$l*ZVL*w P00000NkvXXu0mjf?)}gr diff --git a/modular_darkpack/modules/jobs/icons/id_worn.dmi b/modular_darkpack/modules/jobs/icons/id_worn.dmi index 06cf5b8660ce3c734644e233ba86a10092ba9955..ac9707fb2516c21d4314b73c2bd94556d78fac36 100644 GIT binary patch delta 2763 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 Date: Wed, 5 Aug 2026 19:26:52 +0000 Subject: [PATCH 422/445] Automatic changelog for PR #1330 [ci skip] --- html/changelogs/AutoChangeLog-pr-1330.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1330.yml diff --git a/html/changelogs/AutoChangeLog-pr-1330.yml b/html/changelogs/AutoChangeLog-pr-1330.yml new file mode 100644 index 000000000000..df17de51fae6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1330.yml @@ -0,0 +1,4 @@ +author: "FalloutFalcon" +delete-after: True +changes: + - code_imp: "Fixes infinite loop caused by teleport moving while having a looped chain of grabbed mobs" \ No newline at end of file From 2aa0b2db78e84535c60a026203df09553d1c22c1 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:27:10 +0000 Subject: [PATCH 423/445] Automatic changelog for PR #1302 [ci skip] --- html/changelogs/AutoChangeLog-pr-1302.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1302.yml diff --git a/html/changelogs/AutoChangeLog-pr-1302.yml b/html/changelogs/AutoChangeLog-pr-1302.yml new file mode 100644 index 000000000000..763e82f5cd0e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1302.yml @@ -0,0 +1,6 @@ +author: "cam_dev" +delete-after: True +changes: + - rscadd: "added crosses to loadout menu" + - qol: "crosses can be worn on belt and neck" + - image: "added two new cross sprites" \ No newline at end of file From 6aa6f784818ffac5c6661af572f676d70181c537 Mon Sep 17 00:00:00 2001 From: Stutternov <47883419+Stutternov@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:27:58 -0400 Subject: [PATCH 424/445] TFN Port - Brass Knuckles (#1301) ## About The Pull Request Ports a PR from TFN's github that I made adding brass knuckles to the illegal vendors. These add unarmed efficiency and unarmed damage when worn by the user, letting them do more damage when unarmed punching when worn. Spiked knuckles are given to the Baron currently, though could instead just be taken and added to the supply console etc instead if preferred. The functional difference between it and brass knuckles is just that spiked ones have pointy damage akin to a spear to the user's unarmed punches. ## Why It's Good For The Game Adds more unarmed viability to the game, was tested and seemed positive response over on TFN to brass knuckles. ## Changelog :cl: add: Adds brass knuckles to the game and illegal vendor. add: Adds spiked knuckles to the Baron. /:cl: --------- Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> --- .../~darkpack/traits/declarations.dm | 3 ++ code/_globalvars/traits/_traits.dm | 1 + .../modules/clothes/code/hands.dm | 33 ++++++++++++++++++ .../modules/clothes/icons/clothing.dmi | Bin 105914 -> 121665 bytes .../clothes/icons/clothing_onfloor.dmi | Bin 53133 -> 60887 bytes .../modules/clothes/icons/worn.dmi | Bin 211913 -> 212481 bytes .../modules/jobs/code/anarchs/baron.dm | 2 +- .../retail/code/stores/black_market.dm | 1 + .../storyteller_dice/code/roll_subtypes.dm | 2 ++ 9 files changed, 41 insertions(+), 1 deletion(-) diff --git a/code/__DEFINES/~darkpack/traits/declarations.dm b/code/__DEFINES/~darkpack/traits/declarations.dm index 00643bc96b5e..7ceb749fd630 100644 --- a/code/__DEFINES/~darkpack/traits/declarations.dm +++ b/code/__DEFINES/~darkpack/traits/declarations.dm @@ -200,4 +200,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_PROMETHEAN_CLAY "promethean_clay" #define TRAIT_UNCONTROLLABLE "uncontrollable" +// Below are traits given by items/clothing being equiped or worn +#define TRAIT_BRASSKNUCKLES "brassknuckles" + // END TRAIT DEFINES diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 6bdc61f5a5ff..6622b574a3fe 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -792,6 +792,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_WEAK_WILLED" = TRAIT_WEAK_WILLED, // DARKPACK EDIT ADD - MERITS_FLAWS "TRAIT_WYRMTAINTED" = TRAIT_WYRMTAINTED, // DARKPACK EDIT ADD - WEREWOLF "TRAIT_WYRMTAINTED_SPRITE" = TRAIT_WYRMTAINTED_SPRITE, // DARKPACK EDIT ADD - WEREWOLF + "TRAIT_BRASSKNUCKLES" = TRAIT_BRASSKNUCKLES, // DARKPACK EDIT ADD - CLOTHES ), /mob/living/carbon = list( "TRAIT_BRAINLESS_CARBON" = TRAIT_BRAINLESS_CARBON, diff --git a/modular_darkpack/modules/clothes/code/hands.dm b/modular_darkpack/modules/clothes/code/hands.dm index f669435d8138..6030b3b8b55c 100644 --- a/modular_darkpack/modules/clothes/code/hands.dm +++ b/modular_darkpack/modules/clothes/code/hands.dm @@ -80,3 +80,36 @@ heat_protection = HANDS max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT resistance_flags = NONE + +/obj/item/clothing/gloves/vampire/brassknuckles + name = "brass knuckles" + desc = "A set of tarnished brass rings fused together to create a cruel weapon for back-alley brawls. Illegal in most places." + icon_state = "brassknuckles" + resistance_flags = FIRE_PROOF + armor_type = /datum/armor/brassknuckles + clothing_traits = list(TRAIT_BRASSKNUCKLES) + +/datum/armor/brassknuckles + acid = 50 + + +/obj/item/clothing/gloves/vampire/brassknuckles/spiked + name = "spiked steel knuckles" + desc = "A set of tarnished steel rings fused together and topped with piercing metal spikes. Illegal in most places." + icon_state = "spikedknuckles" + +/obj/item/clothing/gloves/vampire/brassknuckles/spiked/equipped(mob/living/carbon/human/user, slot) + ..() + if(ishuman(user) && slot == ITEM_SLOT_GLOVES) + var/mob/living/carbon/carbon_owner = user + for(var/obj/item/bodypart/limb as anything in carbon_owner.bodyparts) + if(istype(limb, /obj/item/bodypart/arm)) + limb.unarmed_sharpness = SHARP_POINTY + +/obj/item/clothing/gloves/vampire/brassknuckles/spiked/dropped(mob/living/carbon/human/user, slot) + ..() + if(user.get_item_by_slot(ITEM_SLOT_GLOVES) == src) + var/mob/living/carbon/carbon_owner = user + for(var/obj/item/bodypart/limb as anything in carbon_owner.bodyparts) + if(istype(limb, /obj/item/bodypart/arm)) + limb.unarmed_sharpness = initial(limb.unarmed_sharpness) diff --git a/modular_darkpack/modules/clothes/icons/clothing.dmi b/modular_darkpack/modules/clothes/icons/clothing.dmi index 89648f0a1e3d644cdfb012fd764da1407167ad09..39e0ea2553e2b0fcbce592a75cab1fca57d2210c 100644 GIT binary patch literal 121665 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

    FIIE0Za+d!Lx_)! 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%0D6$<)^2q(PD{yQeM3t3dbZ9#;a+xfA!xIw4(hU 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 literal 105914 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

    ~*SHi6;7(rtTo>oj}=#Z6NE7FZ;qnI3T#Y z|HmlKrqM_NLsd`{9v31@jlR|rHkU$t&%tMmcig4WO>xYpuZTd)Nnd5S39Yn&34f^5 z?IRAc=NGz1V9kT~fq*_ak>I1q2ceJ&S~iwjBjXycalq#;(i|UULPaQ0l3l}hR-|HInDg4Z!B zV$q$V@vSx2-n8c=`QBwliKL-h7WD|V>1t=+zdAe*rc)Ra87jJX;ztxGdpxG_z7fRr zuC*u?_nLIOP1s+SNM5QSJtQEWUhsFa51SM_2~}S(AK8isH5a@1lhoX~h0~vjBh}u# z#ryq_v2(<&(i&qRsQc+oG$PR^HLv#y%d3A&#Y0J53hshKkb)KYuHkL85TYZcF-2?I ze8>n(0Ak~f`u*fdbve%sGD?mhG6dMyHg6=m&c&!5B3TGUN;T3Gp=dW_Fd1M-G>*ClCz6p6RN`^t=dyKNT$YXBTT_ee;!YUc2> zs}^WAr&_p8Qs;dQ2b$Wj5-GNm$Eb3Xw;dvDEUJeZJ&tuIoPPw(;AzJ;ZC%nE<#axu zZNKZ6A>-8Js*E8S&0+Mnxy8`iZ=8K7VV|Cc*iT1}@3;Lm(&qamG#vqx zse!#Ur>^3Pe-4H@-f=0a+@8z@l1ypFo09=idxkeF==AtQyVegoa<$NrZv!Z#(oR~O zV-|?zQ-A$j*nTx!)cebm^mWnpkBMZsPw5jBSAGn%o5VFtKv} z7iy$7WwGaM9c(y4rYHfIsHxnbl7S>0M?&5UVoY7u-IoM~50m(#MM-BjOHPmrT}&EXpA z9T`yD*85);sDlqAWQI9xh%K~%3}XEB9;he`weW(~&#E#ur})nyK`=B~+S};62<5+{ zQ<8ah#!I1}uqGX~^Q36pr4YCifMR?R|3|xQ~H{ zr3cHk&xVGk^wT>d@?738)tYP(!GY%Rg0@diEp`}VFsyqy(39@g=np3b0A!ZvF54pm za(2#k)QPIZPG0)s8W+PlfGU8YYfYrs6vv3TLAXo4uqowUF9K1tTSx{~1(uNR1cq4> zuv30RDV}jRN@2HIdjhdG6m`-(x%QaHRX(yl-ZiG|W6;!2hJRERyy@JwL9$@kevgP; zpz7O4>800nBj+Xx*lul&uWZzMnAq&29o<@h3wLmw&@jcET9GYK`&D+;PIrtDs(}d%`h27*koYMs{6}uulNg!O>Upt z3OqdKd=V9YA>6gBOffCxQdXlOJ9X4xjZ3(md4li^1n`+Ss-93}E|%B}X-t=8N15Vkw> zA(sMzf)5-XJz76CS+OYm7%X;gYo_j=oVZ`@G_R?neoXL&L$J|6|)$hZeX$I;GqjXcz?8uHxcA_p8hZX3hYn37Qo59B;+c6R}WJi>C3%_Z|J zHNfi%k+C7TqVG|4!=udH-UO zJYbeOG`*)hm(iS>Nq3Iz`C+5@tL(_nl>162=9D51eC$l?gV);xy*7T2j0k(myWium zjKd)a|MZ3h&)X%&@tj~#`*i`cRC9s3Jl|p}np60I?0y->KfFulH3`{bgp}Ge&2irS ztfO*w03 zkD;M)n%`!sj0^;%tvYum_h0!rE%K_Z%v<;0*!-vguwfEnr7Ec05)<|E`pq0Tz_cj{ zB-y?nyI(uL9m(FrM?!pJeL`9TGmac2N0Y))fN{K@J>$*ajijQY`{U%hU;~f`IPuffO%I9iU@#8_hrFoCE!x*g&4&wFiLzCMqq_r_U|-ab;ouJ z5>fNyd^sbf0=Kh5n?CTirGb>9CdU=LL!Z~{N#3Gd_TURm+FQ?i_GT}pfec#Rf1w3GjvPUbi7)$9 z)o$v3lxs|@C;ug{IKJc@++BN55w(&E`J?gXZrie)n$n5cm`GvPWBzLjoT_S-$ zHC-lQUzXx4;toKPK&9YN3#fjNvVV!sb$TS65L4k4^6O!d>2t9gQ?WU7oce{F7Ft58 z*r`}B+t>9iAZs7wrO)JK(=jIkL*#>0Y>i~ihN6J7Iq*597;SKQ=Rl==Fkv$iqJ?#C zj>@slE$F1I#lROsanaXZUG9%^u^Y#IR=TeoVLeM=p9H`y5jT0zIwUkl$AGBjQ}heh zDghc4075Td%;pZ@FaUEF2K;PLBC3^cZ+VzJ{*g$QfY+uy6KHN@wfe6nnEgZ9V(WlA&)l5aU&hoTqr7xuzQ=l>JEqSa{ypt?y${^E7&nOqnoA^`IjCF}V{ zt$H(jkT7&jRf0PVbpj^9SSI>I09pbjHbjTE^WSwZYE1bHf2pe=&;%U;ef}b|Dwhuz zH)0-hr-wp&U~C3|T;czM$h;Q$|3AQJNoiceL)+a}e-=_IV-5yj0>g8q_H<%P<%oX6 z8MTH2AL*cXI|z+ExvvHU>?)>Zl)?UyvA4M_{w_($m<>u!Zqmu^N z0|MsEN@mmqh%H-o)kzZgP=vp)p$|y7s#g%S^`Gc_;q>r#7VmKDSN4mx(FyJG{FsoCWi>A_j{Y;6(e7t z{2Jb#syjT>e-mtyvP`q_yKE-@%dQd|^ZaR!`Un|zW89GX-R7ow;GXNBn{Csbu{$K4 zrrAlwKtl4t^;`^5hV?uAmU@{|q=JZ^XO?(316{p|$X!Xd^-;IX#~B(>F$!EtVi0;Y5Nz zTnaZ3>?!VC82s)0(^op+HLn2YU#g{e{t)0G*acZY5I!VpN%RAoe2Nl+08KcyyuDI zdM72bM0r(-S)Ka8hUDYNI~&JpK6WpX&o(&2wmLJVD<76E{)hp@d|D$?9IyJu z_MWZk^m$cHVqtAb&6hz}Y!WV$kA(+PMcb~?VhCkSu76+gKXwkNzX4dx*L%12vS%u_ zI4jt@GNB)A9!~(we$2ou_gfXGwmMHP9ZO8IzwBJ)<`DcZmVii3&Nk_BJNd2RaAv8T zpy1VyE9nx?e(mR6QlVL1UtAmu-WtRRFHpP-L_wvPzi1_@P zqc~<$;E&^Q>Q6s5&xd8bTPvi8ZN$drK`m=eXWq%Alz^6l*;@>2vQV%P_;(9J(X#8; zK-2n!zsVQ-msrb&+CP=g(i^BiiW;yOSP6`nsaZs}QDidqjMv`j*t@r7!eMz52T)rIe{S1jz%tafcoe5`c1SDt^;! z?2$Lo9``jbLDUNcgBJw%fZULwW30Ndh`D86M!uv|8Lp6rFn$4nr zt#A}56+!8b)W%SgLH9a0N?*$8-F*nW`S)z&^}Fbn!TuvnPonj(;6~*-4qany(p^h8 zXnXsT$3P9z!ee2}j{|Hzi5IXUxC>{I&Y{<4$a!5lLl_6*eQAUEx2o@mFWlO2jj+gi(Q%V$sxKbch!wCXzi+cK+_NUDLr@K{j^OD%e7}%4Nz_TMKJcmQ}VO@8+y6 zzLw{DC}vL0muD!>BK`ShM<*Jp5FGho7oVtpcnlkCd5HtAb_u)J43Q!B37JzQ?tNZ~ zvZueEGzav&z3UfB90F->&es>Vy##dv&8_GC({OW=erXF4+xN#GLI5qX*Nj|<%NkKr zKKr+%H;FE0!l?hiZLwNEfpe8DVQf`+1e%lw2@YRsA@&=qfp^h_#6|s)sCZwtu%ENS z11SVD-tw5DSqo*tUH?JUWy9ajvG)u~L@x zke_37=$N|6z_n3Y>w`G9v>L(u18WI7XAlbnyP?V6{eE@&P zqX7%MNZWIrCl{B6LnoKS(oVxa^fTb-&PIzGrz@=ZQ4qc0=pf#YT_pO&_Ly__0Rja! z2i3wf0QZbBxin46h0Xr6QmLzcgInOayr=v`$nTBv>TRG_!NI1iU`wTr(d-`&e>J@; z?3c!Kj1&xzGD}@v;*_NM#S2y=wZXd+q%F>fb5sE_RP5Pr^R3W~;ZK#dE4!lDpv-lr z!;cNM0@v^selH$SauzU{ha&!kT=}th(L7ShR&eo3VGMstpHwHS(%_xUHJY+{IzwfH0BXB zC?0>tZE}HHon=)kloOUOY4@nsif~9^tGg2%OwpwJVI8mifj)c)z>KC|9pibUza4t- zNS@>^9Mc5c6Izu|jjo&?7<^4<4#4Y5M)P67yBgB{P59UsfU>;wBHC>hW((rX4I`+2<1 zj5ZDwTq!_q|DdCt_ee{dIlyR<1D&|Ta@CQ53_LB`9g(%OBFR)1P&AY{%u+ zi;7-XILA$|KjOa6z7~{sHi}{i@zNlRqJmO?-D8pvx4lk9N!Jd`X9^3}85Oo!1?Bt^ zR=9V!71E1Z@@x{XIW<4fp1|jvk;Z_v$Q-L`)FYj;d>smf7nIPi+2#BSLoS3ym#U$( zI`MC#y*%#&_`2v?ND#^kRph9@L2vw_kYmETA3gwoLBrK2W?sz!{sEARqYEW5kiUGf zb%T=6(gT?@bawIZ!{G%eKxC2-s5!25n)m zGcSuxBG||b-#2GkTgBK9kOE+f!Gou>jGnUg-zdMg`;!^G(B~krV;3>|`WU<1ccIk= zT6R+ea9+1tfSS`_oB*P1f0Wq^>%pfm!qXju$=4H7`n6)x?dFF3EYCao{yE0x+|AB@ zk_l9$U=dWhO->YeHh#5v_|cjezjTd8o`8UcAG)DJp_`7TgBz0eYhi-ajT&8vM(FEL zSM3#=*31bAF&2|^c;oDQbRv_ZTr2~%g>;dQm-Cvhe zceWU;CyuFCc=9C^UA1E!@~4Wk*v;gLBuVVW4FS`!zv+3O$*wunK;f7vUGJTvfgl1k zm|pcf>Zl<6RKp2@d`(S+C)7ReGY6*%wkAmU~DMd1`c&jFMEf zgTv0SkvT0@{^0veH8;h@5aq;dkQLLF#I46;{j1?3!79%Kg3`ZG)hnc2jcBz#I9&0T zK55frI$nC4Pu=xYIRZ+4hW?CTY87!q9x#UQ-?;UO?B+7sEu8@ zHm~-boMXClse5e%bSW2-@>D8JrU8Pjh2%mJwfD;OMEER<-ldh<^m2cg(1wL+sO`eoVjz>sDq0Ol$A znSw9vz{@_AS>`>FyvXsNcWg$cV&D7lO-Pr!Oi-CTKYFUqQiDCu^=|FYKFGZa5!g9r z9UHb31nSrTa?s@OliR0f)Z88hb!%M;u+G>>oZAwM9n2uLscKm~6;@+ZQ`(D(iJFlP z)gfB5LyUu``tvVcvQYiB{}}z=*LVOBR=LP7q<|0Pg<#u+2@}fo>eZ`Y*0e9} z9wC0g2X*J2#aErrKmWYlZ0yP_uPm3DnW4jfcQT}voVz2 zGVFpm!ZLI}y!`J=`}ddX?>~EdAnzj@h!8%oFG_NU+*7=*pb$RxA4o0?g!hUQxh%7# znBWNrL0K-6LuC@Ey#ERy#BPgTs4bbvC8J{hZbdhvlWo@}n7HMiggW%)+K3E}#+Wf< z?8oQ}Lg?V!+#kS!>5s!-)>CH98tV23p3&~KcT zYG2$Mnm4V303luk`&V`!m3A-No=f4tC$sfabz?e;_lfaK^)ez4yf_$BGWJd3z$?pP z{i+0rS4UTiy-%V7U;sYYQv-8!7?tpW^>94^9q122_#m8n5giT+A(%qwCj59w z7a#)BA@M3yumC2|2Luy=!aR{k2LzDfCGPSfB+UwIUiDl50>O&B77WM zKJm~KPxKIzOY7FPj-db&xEm02F)-mCFqmT9{LQaqRP$zZd~a9>K*F-wB{kHcD^~#N z)3>+I{Xh-~1qg8y!!GF=^;K^6dR6hjUbUxu1(mhcvIwiS=%67a-mGx!$JXo1SFF+@ z6;#3?Bm*%Cl>&rd0({7dnPmHcV0<3fiBa`>|hn5YYew z+t>jT?nj^gH+pV=ePYu#`sz=x@V0Y(sxA;1t45DLMB=ff>Ywkn9>7fw=iKP2RMNqh~JI=KP} zZt?`DW9Qaw>f=ct>uJ>MZ|ZIA`N8I6NIHcB0{Ec5pFUNCh+Oy3@Q2jM(PQIwLq@*y zi3X4RM@-aE!F#??NHm2r!ZKV4jtHLPhj8G-9XD&&uI;$pDFffB9MFqiE$ZdLD?TR( zA$<6mfJ)&i?vrVg9JjIIk`${@yoyPqKg?XHAGd5}^#gH&5W-!H70a&o{7~Bq);Y#=@D9ZKF;8EeA z?NTID1%v+;PV{gP=FVNNZz_k0H=L6vc!WJA9Ofa2}1}$!!m?nn5>F0j0BWK#LRnK!JORwG<`5+VjGh~A@iVzZA_NK6`R*v z)N?Gth|KNa{Z1r&pd7bP{~JAx2{HK&pC4C&xcWo<8^VE>2VnD{i^Ne>0zf!`d;R=O zME%tYt2=*w0Us#G1;C*C#h=rGyPP_q!WBtX&Wl^+i|LdEB`24|%n?aerTzOmRKEX! zkaAn|Ra(2Y`mqTgI^?3P<09M-FD1A#=Yq}xzye@G;9ywBQqzb#;sQSSe@8MaUWecU z7o_O*lX_2bol{b)EZG&$OK7Z6sa3gXcVC3~k0*psfRBeBdO$BDQ567!T#w9}R^k*w zFLw_3yL5CJ%8Z7Au)d>xT9p(&Xx_!&s7SeiROZa3ucy^m7rJ_P8CmDX#}%o?v4ol$tkQ^CJNjpTAk#v-N>k#m~Jl~uqB8Av9mreb^uNvI6NAQ{!G^FfvN?=KbK zfAa(>kH;vbHS;+DB9L@G*!yDtUdzxXfFyCEx0iVnIq7DwCx`?PI3!F?#fjf3L?>Q9 z;rA8o{3LT$WS3O=USf?E>--lgSj7Yo$~>{N?3TG7fDee!0RcP)4Y*2&T_7w&UJeR; zA2P3F{^FG>JK%GC8Q0|%PO2+CN<&Bb=XVlwL7-Is4c>U;jRILEgCx~d!UyHG+AYy> zO6&XQ_xP~@gv(BerGO6zCttV=ru*RqKpfs@iIY8Ggz$mT%ZDFKj_dRL2_S?b2%)18 zK+2Zl29RjN7opp^J#?Ze00iIx0X)9^baKSLN9su`RH#s)Ohp3_$b=AtI^cBFZ&=yh zhwg_NdhruLie9h4%(Y<7Nxblp==+KXiexSdD_~HS69UMgub7R<;t^wXUl-+iAl$-G zO9&jHLWK&IoCp;F5jY&Xc3N)BmMsTuCxW4upvB|7Oe#$TJc4>Za68zKBq&}6)IGyBUP~+5L3(xp%|8NN<0k{Fk>rpKuTA>I3QH$c7zHQDpYWijtD`- zj~ma03RN2N6+l9T3Kc3;@=Cz9D1wf?q02%^cMBFIxVyWP;1XPdJHdhmzjy+{f;$8PArPG44uRnA4#D01PV+qP zJO5|a%(oVBx=;7Lbl2HkwX16PXLVIMOf(WS001x*-wR|gX*?*X{N^^lgII={+6{b`sJoyp8WnvySjQnyc$cd z>UkpXAE(lfNlP8A??=C|ooqD_(qfmw@Ka6%Mka+(0iR-c2K7n)U52Y(6*nioPG`y9 zGT|pr>5HOqcp{dNV>CQv+`%$O!5z0i$2C;iKCEgQ+xOly2KtIyZZKy=i-K~G2A>kC z`7raaoSA{bAy6dZ?jE6rb~N7PU~O8>evRUUZ{~jY|V&5>WWHH>- ztM`;}Q%~=pV|3DXe%6&%H-0WJZRxZNW{nhA>p63`;Uw8PhMhvw?12lSuf_67CUMd# z3gZafUlh|c&)sBY{gcv=$*v0|6V?^v7~gx2_aI$tX1gCSSRwVtGLUziZKS_pmw*yX zHp89LR?gI&P&U6sxO=RM#2a5anxCJ^fm0n9n?%T(O~O;pRpXo_HrcW;W~3gIh+RfG zWc(UiGk3T5=^N!Wt?b)4)ogRO$d954zel8M=ItAPTb2w->Xj4dp)vH@vkW619?k^B zY3Uy(n{OW^o4b3qG8nj7=F-RZqozi(^vqhAkOLsL$CaHadoGn-$i!I6sS!#a4JjHC$M+;gNGbNL zdj$sx%IVu?#2dS53s+r{Mj5#ppe9;N3S1Z0F&IJj+zUH&4lkWBf4&WJhiZIA6Nwy4 zFzO@vOrxDfGD=|Q$%uH>DI%i52WT&`r1J_KZI6pN&Nt`m7flN6EE0xM)O_qNA!{w z^l_qI*7GmEWsODN320R&SQq_;Lxd_gC=|JE_?1EKbdi~p_*U{?WBmOCM-@SVt3*#T z=M{ z^F0vFW#2QLW}Dz{Fpyu*6-l@@!LYOkMF`tGXM;TCw*dc=M7+65A7Vu7ewm}b_s+un z$hwV4+t|3Ea1gJlyKC3lt1L+dU-z?|s{7Xav&UlJyQ|A5j;o{AZ%cu;py#Co6lA5e zK4uv@k18ygUO-n0ul0igdmtb_9ZaH_^` z7k<^{`CBFg6C!%>j2!6zN+BVA>3{aM=>W9zFQ(x&ojl2nTZa5i0wCmt=FDm=kA!&? zD;FM9>=h5If+4%^tiEOS_p+*P1-$U`|BO&OwYD-2i5aPFzjy+el@w1b9`2g19vDP# z@x7h;PtYiTJLhjN;X0F(|A?X$>|x6agte3Dy*DNxeXx%=~^zO40|=Y2mSNxSX<>ssx%=R zdZh`#3E?Gd2`U9khX56|&MbnJ$P>nTif~QW6>-|hiS0u(M8GQZbx_ITf9Co9Fa8*L z@npAHGNHyEd}cSIXn$JrWphtI485vWz zUfr^_>3+S8D47C2)Oo|?oFYClR+#kIQFP1X3+Rp?U{fNk@5d`a989qCdWR98^Z`Ko zaoa=8*oGX+{h(tGhb(x@0H=t*p^wf^O+&Zay{G%WsT0NBG18jHXq$DeA#yu7x6>#t zi4R#Dbdt6B<~R2JJ+Et(YQEBppj38(2Z=;Mas@YfyXg4g*4}*Xla@At-j~qlbp`Jq z)xz#&vVS9@KfLAW=6l2zZb4u`dBtn@&c*9oY1r)&4dP-?me>iu>Bv??(g5e8Ah?4$&8!Hj@r#BgWV0_wNJW zF;vo7SOwn3QEk5d4%fDW@lkdy8k=)O%3rE*1*`K~G4g&oU2&rBuaL}BfrkL<;fT~v3o4cm zFD6n~0AvvAGRUz8HRONuU{39W(Z}eARdvhIqHkusA4`;d&6pt2&cOu9#B$0&s~$pD z6a0uw*7HP3vn_@C+dJ+7ICnRXp5mWB-(0f(mH2?y(c&ZdlEn5U5Q?;(I{oS41p}4u z!CU&@MJgGuLEDVlx&i>qfD;+!@67g8YlKUjR|zlP6#U-w$q5ac@!bL(^u$Ne3%5(lwNOg( zV{Z&VDHsSy<4{g7WE4D{@2stbBoHa#9ulIpkFRN}8neE}PQ7QdTlW=U0R$faj%=WaFI@$}tNa54(%f1QUu&XZpG6VJiU?_e$~LGWLjEAt)Z+kCp7 z{J$L3UEIP$1J!dm=ZS3Kx;p@t9hEkv8RXIL`|tv9IVN{t8F4!}KFT`*QBD_`owYGILwcus1D*D!QrVx0Dc-X(dUywc>yen0h$Ux2kT@ z^`f*`7HuoSa@L)#J+!cL5x7L-VC52~kg|f{v?1+dR>C(oO4-po$921(PD6YspGFnB zDTF`Mjr>{58OaQ-tidQ*)_1tJ0PW6c87H(*Yd7Je@UX?UX@b7B+Iy1|mzl|$#_vdo zs_WSqB;$MFw2*Ff>@VqXSRGo}yt@N?{T(@y*?#rd$y3t0f#-McpRbjuLoBlIv5M)e zdlyvJg1oXb`I2v6&K z$Dy%plbJHq#uBg7TOxu~a!ijak@aiSiyPc$M&`#m$YotRHkUWGG)> z^}>~JTQ8NMzHB0Iz8Euxd_4a+H|WN`i{b0DF=NPu;Q*K7mM4K@mve`b=EzS<*VG&H zR-w9+(HBw7gVz~f-s?iLZaqq~9G`vTH$MWw?>si-?%Fe>DsPKsw9}5;irk6vTpXij z^EN)W>_dKQ>rk(c(^6Xr(jD7r1BsPiAU0}(^dr@W8RDLJ0~o% zR7rfwd-OEFcU+KmJr+Zmt#~aBI*{}_SNHnyy4Xc>?C^R%g za;4vSta8GuOb6nN1aH$PA-o#SE8XGVZ9I(Aq)P+RZWK*9?4hIi#i z+rH;1GkWvpyS_ljudLiUfgtjhu#2ZWJn~FaEI7QYUZov0sDb2;3=iBD?Kelmhi^QT z;IL`j7Wy)1kjWl!o7kqD##5sl`B05GF!7;vAn5A@B#+2M2G=zs+6tn+ob8(oScXkcayk4Kw~VIuNz)mD&dYH?Gx%@?W$7PT4`XC;&ilOoXKUf<`noLIH=+V}F#>qz%1c z#I37qBOdb{q>sAAoX6FV{Se^kamqewghw$?`X@W7qHfseQ$8&@(C+$@(rPH3kN;Yq z+gp!ycW>LS?Be<)2=r!Al$64PS|9l(-LjGTa^TzT2#e)3aQQgVcJL!7X^H{pkD#}m z;_ofRxA?Fc&l`f3aY+JX{yaHp;Vr(R1K5q5F%q>qyYk^zCzcmIac;GR$=I0)=CSXt zy4OerB&U`AFX2DmPAz&~G+wX4`>bwfQa@yBhA!5)gIpKYby^fwb8DvQzD)509i>x* z1!pW&nG=kq&6x|3@rUiRdYfJI}=8J?axnDMV9!8FjYb2(mbWKdW5EmEk z>+d&r$|!_kCh?6<>3oZbh{XX=8cBPCRKk#fOzTRXH91uOD_-&-GWo@A9J#o9ucpnM zkv3^yb^M=eN2}h*cz}X8Und3UitTVF4@#93c0|X*3PD7`kcHi=&-WX2O@!rCOZaOy zo3c!J*S$*N*yL)BOz%cjPI1havliB0QtM1c?_GbL%O9FTx?gz0mXTYYU1=eTgu48_ zu<1%8@46}efY<(@45TCGPQl7s8%C*M_w`;YN!_34z0N}-NT?O_-~eLwB(y^-lgInA zCy7twVk(!KHfkwAOhi-BEUu+-%{717)cFKQ( zbG(!pfRmk_=*t7%!fGNWUw#~5N5K4z7-{tGK~F?=4(<1UQ;aQoU^=?721lVr)LehW zVttj9`8yQ>x#VPvbYg!FH0_(OIXThg{4E26PlazSPyzQeAv}fmb?9e1LKgAow@yHN z4MiPK3(|WI-Gqx8n#)Zw8syI65OmE2GF2r!(ibvd$~IHxGvjU1*o11h?ai{iTnd=p z?er=@2Qh)wLpp-ZaPAj?AQ*!~lG2&4ZL|``lCW4Ye ztQi|vz0RLgK9mr37ZiOoI{lX{vpMtlnefawR#s9UzkkrPKO3_^&PD=gKdN0gz35Lt zSV}>cXzxP~4htv^u1J)*@B5;EjyuMJ2UM6nG7qNmnT$H?wMAZ#@_xC+blLkMWO*L@ zo({8Cze;WY4Y0cHxPUO1|AHO>!V$HEH;L)hh~<7e-5Y}$IzeS+rEIa5Iq0Y^9fq<* zQfzriXlZFVMPjUCqiKN(%i|*-ev*l~7u#5>>LpW`M}cS!ME2}6RlLzmnuR#Ps6AML zRwW$P{H2osg@U>4j7i|o!^6L2*X}>vIN*nVG`x$7%az>0yvy)uqgR6{Q#=a36t59! zyng%5mx6BGt+w-xRab;3(VGp{R4}~<+Bx))0KrUM+p$8QX+|G;Qpaj~O3I2cesn@L zG8sQnqtM^65$JEQkKrk>;|C`YqZne$&Ta!6{^5*mg7Nd?UDNtnUKcJ|DPAxfFuIxD z`(8gFxdb5uX~`$!;Fp zI{ZQrm{>1ZKSzJ2a&;Ekl90$~z8+q%d^)d2ZK10E{Y#6PDrEqdl=QtB3qOBy-#=ID zne5b0lrNLT_`m8o<4SRF{bq8~u*-KOR8Hd#`=Q00_e-Y)b?JxlcMVi2I3>7;M+hM7 zpZ5CR8caY(c|;{=IfezyZ$^(gbuyi6=3)^IEa(8JHT4clQuQ0(LX7wjUzsr(W(RN- zvAn=eB_Cehp+Z4H!T;ScCIjXQM4&~HE_A6_MS(Vop+CoR)lwlrkVoxcR&WOXt|S!^ z((>wa`)3nw0BAc>lMM*qx20e0=6JcqDp~(n4fGUYe|=OE5C8oWZT=^-EW+PVCTKzJ z0xmA?3-|jJzc1?($cF(>?voTCR7t1HI~yXI4@43|+z7O1D*}?jvI#&kK{axnj-Zl+ z1%?rE@l(ui_{_FH-9NU@&?=MjMa&HyH(?u7$A0-N+w;5V^qiw@T{;Mhs>Sztkbs@B zF#i^dmir^ijS2S*>Q{R!*ghezAWu&>hqh>-x$*>dPSs%nsiJ0N6B%t|dR{rv(b077 z)!%KTWaBU(Wn~qfS77~0Qm)xaqqrgr0g;>HQL|f0QgibvJiEvZnIUQ>Lkvcb2qCft6^$XiF?!$E&iPAnfV%@!ZN;%ED^x4RPvg8YVA1U+lf=(I0t1Z4uy8evAyb?1w%VfcZx zvGHOB0G^Fb{{zWzNB^vk*bOQp_jr-iuTOoQ3=jCczUX$A(*)O^Tt%wkr;E96ZZm&u|1pG*U<1^x4J0BIPt`%c{ong53Z^$28?X zyy%i6mAbNO^$OoRYrU$smkR)BOqo}Kb%(lj74a$jR(rd;b!W^ZYt&Pz{9?}M?#Hyt zEArQeNufSfH6gv^J{Z$v%EIjjEnc)wLE~rkmf0+JH6~jPc!|lnhLgy%@Suf0m{Wk> zbZz%I67-{#f_h=#fKv5>;Y$Caj))|>aCwMr^e-wHENDjm{*^X1Hn6yMP`1eT`G6^@ zdAGuYl1A;SI^fqS6IpF!3-(CF0KEx#Q#12sM zmR7$e1%$oMZN4I=!X6y(R*z#}yxaw#5qH-z^6HeY_MEeWh2rC5fWW}PzySX0jJDlB zD#q+7G~C>Hir?|#IQt!A;0o`WQX2vU}JAnx7R;y)lr7 zw;07`BPI*)bg?yWkQV<*KOwus$~YQ3RTCC*j&^FQm)kWCi+rVlPxKy#j9WKu%2)vk zf)8PFyJs;#QZjdShn(P2)AnDWahXos*ll3t;51s0Vb8=00YkuFsB$6xo7>61DMZ>q z(@1@mingvJ`7<=Qp0#Qd4`GP4{BlC(X&}hHPCLo9;qn|<=foULOvLC3UUN!FLGDaK!@!tQh_o-Fh1c{r0X$ftQU36KvFRIgwl zLPG{z7*msYtf_$gOED4dwl;HQ^8rv?JYI*%zgpd6 z2Gbk*B(#|9!dCLIckD!PzDbIRzkVNO%T2)O6Hu9xB5$=YOvMAUS`U(=C^Fu`SA6JI zRj7z~A2O^1#_x&<7M^Vhw_#;4tw&OC8;1UNt5W6RK$y&C>RRibi3>EtjEv%AWS^#l zb&`PLT*wj)sBnKGi4OO^goQue(~({srvO_?K;n4RRPcF`>jx9x)1G^lIFOUTMGK98 zAC{148^&eol6jk}AO$+h){lWQ zSK)9>wgVTOaCJs_?ygRx$G_UzY{a>_xzYVgy?Pfa zz;vxM4BW;d_19PwI3VJelbTpG-!^e;m^PD2(P9oWE|)uAZeiQ>X?X?yXdvU$z~K~% zk=P4FShK+woFDu~d$fHm&H`4D@m@;`^7~iCAIJ}N9(=}@2F-LWu2_11#2GC1?zC@| zOPL3Ivg6vHxJ0pPC{?5CE zO%%a#jf$3@etjxsXrL_|n*s~m2Tae%fW{iTISU#EZ@cK#19WuzF#mK!(@G3Q(Q6L+ z@_ORyF7AJ7_wyp~t4m|Z&&X-Y$L7@*W;7z;e8(KNroq%Ka+e$HtR^)Q$)5F?D^}8V zMDZj!Su(Qjx2%6&nKcC_=7L@>ou9wBgG2CgZp5NtOgdQFP*+C*xd;j>8vX?WvZBFJ!_9d;)uFp2rd{6D(Be1OS3>J+Xw3 z2oHK1XE1JB|ASjG2Gr{w)V#(+TN+-W9Fq)B2CSdDCK66XH}0HC{Q8YU>X{uJ+Xh!^ zv)Qc#u~UKS{rO61B>`-`)wQ;MY{ah4Cco|$3+iNB>3nfn4TRO!)-xQT(tGa02XLzv z;8s=#veCVExp~PjesVF%baQu4k+vARD-0{n-@*|dFA9PdDn@i6-m^`=S;#O6_%Yz~ zg=_*joc^Mk!95GMiMoM8zB?f)={!mU-eci5_9Pp;vQmkN+};fh4Ncz6%A&zZAs#8I z5_S0#;wNiw+f_q7!>HL=z0NZs1SzU`6ijUCGx2jYvb_st75X0$(LJ||aNxzw2Q;oz zv$K~iKSm&#v5w??i}PKp%V%;qS`A%^DO?=qZC4RPjT8SEv3^MBSV7;u?66p=S#_8| z*DbR0>Ea!mPdM^3Bm4!A263x)#a-qj^A?zoz$RWTIzgHT){KUHk`oDcS2xNfL1O}v z|DHtay&K^HzXt-s4hVVx(@*J{tLs_!8<2$eHJ(W{_g$bsgJM;Yfzn68%53Ea7fQm- z2Om76^=zPJoOzfu?&TgPfbwwznGIg%m%nkFK6 z7(kE`b6^El{hT1uT_FH4vGHg1pLaR`+02y`eS+CQG8@R~pvv?iW458-k21ZRJR!Z= z4EtZ-<7Snb*RMhFe*zh}nZn-C=)nqH?!ZyaA4Rhl1LPf{+sO($8nTTD@v3SSvU^8V z2SodOu~OYWgf-49Z)eL{%Wbtrg27@0t(FmAs8K%;ZY{zO9D zGbwI#hn%_%+qG62fjgEwe{hAq!`irQ9FdS@kI*S6x+q1#I{PDGj%Td=~)Dy|kA*<8#}VMzLfHV!tIfNVd)B!fEtQj&h1xp;1;O%SE3j+tN0r6C3c7uZq z@XwdK9-rxV4X&uVW$(vEHw` zDFg&{WCjrI>Pwdk38;BZP7;SQG48*QM^qazV16kPz`DLsw=q)ccH=XEeZ#X)2JTus z+*A%uz$BNj%lcaWfjl%c0w%aFXWQKdk<=VHmudmUSpU@mtiNshwSrQ?uzq{k->(Q} zxbn;_ERK8nCsRo0*N#4m#6bHMIceC{MLkz=HUTOsDxSfq+JfW~GaxqzGn9#X>rANN z>RR;}^9M`qL=Y)|EOcES;Ah??Wr`K`$6v0_ru2Br6I?IAQlhC!{9ah7;<mgu-*>5yFUJK z2E8?B9A^2myV54-;J|8T*7n9LGBJe|0*H4!?GR0ogK$QXf&k#uTbDgu`YW~F_wqVh zhUrCQqqKA6L#Dbph$0=YB$TwX`-=&vB{w!CQf@e~1{ z7SrEzru+P{rXu7DP8Q0_F5J2bQEBdC1LOOjJ}?rA03e5s{6i zvvX-V?AteLX#wctrG!;KK6UGiWv0B$NM8_GEu-(#Vot)Lt;p3fq&kSL@~NE`NCJx^ zrzYhLK!rz_0l*piK-BCoM!0Vm=v%q@Jxvy5F`C|CVbw%7@ZEQG97C38RNZVwSRh&doQN7!cn+7c% zL?5gME|jnU5T~3Z(Ya}WkDF;cA_xbB9|Mx5dX;bh&e7uZHd(VdmRm-~P%N z1m*R24YRI0ZNe(deO-MF*@K~-KnxmSv@T~Z+;yB1UCdEa>~IA?9UmAK3goI0bM6?p z+$^mE}<> zM&J5RpK6|5BvY7|c{7dl4)-6P-0b*wW(KH{t9YXaglKh$f85VUu&tp}N4hcCvAu)+ip9%CyRus)cbF&uDF2;EIV@EW z7BlS%2J$O0-sP7-ez5YTcoYU z&2SQQlLXd3e8Ly_&?UDY9G~5rvfnBKQvG3VN`nt*uJP9V6V$6`Ki7D56h52~b^Xh?Vgm*M z7(!}lc=J$Py23H#m6R;R?`;2)RuHRbW44NAao|I&OW#F0t8MeBz~I?Pp`W<4jb*1# zpU*!`S)ttjNHTFDFeO@#^T>_CjR#&>Ij0i zYY2XqhvfoEPsr(d2=d$4nwr^!nIc1go`~=K^?dDn0S`$`sVs4quG1Blr@7sR%e%QI zdT7kU<+cZZNKj{|1_IKZB&-E%K;W>GWy1WxeuZI{0(@GeUJ^2XIV(=!7~+8Yg$W9z zzBMm?Dr;%VyZdPbmKkwy)CCYFc_1s@-6-Xdc(vJ;9ZWD-7HAZ6_7klj{P?n`j|5Dr z8W@mVDAMuT*$rlu5$BIFA_!yWvI8GBxo#S2y6TF~xlSuz-FO}=Y^mygDg24V)EEWT z2G;v5c2=ohgBB6RpCW4eUdZLuXs(<@l*hr`K2PdM0u?+X4&65?k9d>caj`oA0ZMes zt(`KAVbc(l{e2Vm6!aC5eRm1R3}t$diEnkpG0!4{=3>eIyc=EK`dB-cAHX%_gQKq1 zmJRHoJ$}2pl(koEj6_@Q{JJg1JL& zr|I;B!n3w*D{I8%DwELp*Atm!*13eQD$B*`MdI48sho`iA-3t}^E9JFLzyht+{HWP zd9c7As!KSE8?N(S++=UH$AlM*iNADMzNqV&ai@WSGB_MOOs2 zoQDS=!0C+z7`e)B6Yi>1_+B3oPT-XuMCF~gULcX-;86Clu&|`f)`$o09pr#Z+~TWd zaPfE?`&oHJq9Xb>`89l8>k&Dk_uA#YvV%d6gvS+41XhQ0aq@sv+fwJ15Q>x27O6O< zq~D_m>REq(h8tX9?_t2B^Ga)$H^8oXe7w{|6f%YV=qTY^xl!bg2h4iq*T)LqpUS*y zuf!N=ht1VZ%V#_O@fBbKa`*2xKoVvfxssmPg?xHSw4zr!QiHsXe6RdcAo2$J?^~_h z^6j0+kj*|0PEKze9P(4cBAA_7uAm6z=s=b2_^St-Wo3uCOCEr7!k%1sYGp{>26
  • uUZ)3I&?AHrte^|i2B!7;EjheW@6*Z9C ze4#9+uW0XIWb15uX9D)TPZNRn(lRm-0IVmJFjyFc%E(bn=VK=PMJUz+%dX>i0;Xu> z{)R>OQ6=S_Bj2-fZ4{IcDDj2weyd)CXwD0X(i*KUpo7CveBbs2aZ7?Fvl&)c4G@A+ z$&5sUK7ngQnz3c5)^U3G>EJ=*N3cJFznkr}!1Th@%SclSQxRg%5k{v`gw%eAmxLA?b!IavLq;rBo;( z{&g-Pr4-lXN_NML%YP3g zTOQuZh^cdvQ=j{j)kJ5G$3)HmK0U6Dw^S(uT0g&(^59 zT*P)`bQP7DB#Ywl44P>^^Q+Djm0~SwDk>@J0CA!He*)9zzSEcNrQ0wAl6{*8!yJi{ z6`|zYXk%wOq49220?(?_(-3fstX~9H9OAQa)XiTjdaLjKUr3|48Dy8bto@Nt{=}e`WG$YyW%R|FJ2%iE-r7cN*B;2|uVd=v0zc z97Tm4&~8g~4Alb4HGzQTYrgaI91?OBv~rL!V>?VBWh8t(^0}`~DN*p5kM}vtj)z~H z?D$vK_9RF)yc9w!B-HRXOYJ~kj&~lZeS zsE1@|z>N@>LY#!KC<);5bl}|=;=58SU zSPULKOxg48?l0p4x$Yis{DL4GA*Aobr&B3BJrglK&Ud{!sklZx^;_5O+IKgpN~JEcFs25JIR5ePbAGg1v1z zwRLcd47Is{tazk*t|WA*|9z=+j{29NT)3USh$ zC5V+YD9;ZW1Ox9B|NZ%jPoD-Y9vdsj=>!$WQ9DH^IGG&&-_P)uUenW@CjU0^ZAh2B zh?M>FnHL)QOi$yvy2i9 z^c{vmSg}|c(he;nzqsE|v% RaD{7MlWY)1VUIUVle_b#lxhL^zNOY3Mrc_Nscv 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

    K1ZRIwhA0@MB( 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

    E1dCZLA3I68 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 literal 53133 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

    E~{+MYyTHisekKxoF8W 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⪼i#zCmn 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 JmNI_#{{RMxmJ|R0 diff --git a/modular_darkpack/modules/jobs/code/anarchs/baron.dm b/modular_darkpack/modules/jobs/code/anarchs/baron.dm index ca815fab1478..2fc867529b19 100644 --- a/modular_darkpack/modules/jobs/code/anarchs/baron.dm +++ b/modular_darkpack/modules/jobs/code/anarchs/baron.dm @@ -32,4 +32,4 @@ gloves = /obj/item/clothing/gloves/vampire/work l_pocket = /obj/item/smartphone/baron r_pocket = /obj/item/vamp/keys/baron - backpack_contents = list(/obj/item/phone_book=1, /obj/item/card/credit=1) + backpack_contents = list(/obj/item/phone_book=1, /obj/item/card/credit=1, /obj/item/clothing/gloves/vampire/brassknuckles/spiked=1) diff --git a/modular_darkpack/modules/retail/code/stores/black_market.dm b/modular_darkpack/modules/retail/code/stores/black_market.dm index 20ca6c5b2181..adcde9064ea0 100644 --- a/modular_darkpack/modules/retail/code/stores/black_market.dm +++ b/modular_darkpack/modules/retail/code/stores/black_market.dm @@ -11,6 +11,7 @@ new /datum/data/vending_product("LSD pill bottle", /obj/item/storage/pill_bottle/lsd, 50), new /datum/data/vending_product("knife", /obj/item/knife/vamp, 85), new /datum/data/vending_product("switchblade", /obj/item/switchblade/vamp, 85), + new /datum/data/vending_product("knuckledusters", /obj/item/clothing/gloves/vampire/brassknuckles, 100), new /datum/data/vending_product("stake", /obj/item/vampire_stake, 100), new /datum/data/vending_product("Surgery dufflebag", /obj/item/storage/backpack/duffelbag/sec/surgery, 100), new /datum/data/vending_product("Handcuffs", /obj/item/restraints/handcuffs, 50), diff --git a/modular_darkpack/modules/storyteller_dice/code/roll_subtypes.dm b/modular_darkpack/modules/storyteller_dice/code/roll_subtypes.dm index e2a0cda9ae7c..fe42637d391b 100644 --- a/modular_darkpack/modules/storyteller_dice/code/roll_subtypes.dm +++ b/modular_darkpack/modules/storyteller_dice/code/roll_subtypes.dm @@ -52,6 +52,8 @@ . = ..() if(HAS_TRAIT(roller, TRAIT_RAZOR_CLAWS)) // Your still using claws. A bit homebrew tho. . += 1 + if(HAS_TRAIT(roller, TRAIT_BRASSKNUCKLES)) // Method for giving brass knuckles bonus punch damage. It's blunt, and punch damage is naturally low, so equals out. + . += 2 /datum/storyteller_roll/damage/bite bumper_text = "damage (bite)" From d664cdff86b9fd215e33a1c9a20f37311970ebe0 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:28:30 +0000 Subject: [PATCH 425/445] Automatic changelog for PR #1301 [ci skip] --- html/changelogs/AutoChangeLog-pr-1301.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1301.yml diff --git a/html/changelogs/AutoChangeLog-pr-1301.yml b/html/changelogs/AutoChangeLog-pr-1301.yml new file mode 100644 index 000000000000..6df2c2eaeb53 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1301.yml @@ -0,0 +1,5 @@ +author: "Stutternov" +delete-after: True +changes: + - rscadd: "Adds brass knuckles to the game and illegal vendor." + - rscadd: "Adds spiked knuckles to the Baron." \ No newline at end of file From 6db6808f213e728abea98092a61d9b05b5d5c432 Mon Sep 17 00:00:00 2001 From: Stutternov <47883419+Stutternov@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:28:40 -0400 Subject: [PATCH 426/445] Nerfs Acute Hearing (#1340) ## About The Pull Request I've been using this trait and it feels a bit.. too strong. A person standing 4 tiles away from me can be heard with full clarity, as the 'hearing distance' is effectively the number assigned +1 With this change it changes it from clear coherent hearing at 4 tiles apart down to 2. So you can hear whispers clearly 1 extra tile away, and jarbled ones at another tile after that. This change does nerf it but it feels still useful, given even if you can't hear full whispers ~3 tiles away from you, you get some of the jarbled text to match it up. ## Why It's Good For The Game This made whispering really pointless with how many people can hear it. Sure, there's a level of masquerade to it but it makes entire non-masq breaching things whispered heard by a lot of people due to how cheap the trait is. I feel either this should be merged, or a increase in the merit's cost if it has that range. ## Changelog :cl: balance: Reduces bonus tiles on acute hearing from 3 to 1 (hearing whispers 4 tiles away down to 2) /:cl: --- code/modules/mob/living/living_say.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index f56773199848..0fc3970e3af6 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -305,7 +305,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( // DARKPACK EDIT ADD START - MERITS_FLAWS - (acute sense) if (HAS_TRAIT(src, TRAIT_ACUTE_HEARING) && !HAS_TRAIT(speaker, TRAIT_SIGN_LANG))// we can't HEAR sign, so we don't care for it - message_range += 3 // Increase how far we can hear + message_range += 1 // Increase how far we can hear // DARKPACK EDIT ADD END var/atom/movable/virtualspeaker/holopad_speaker = speaker From 0e4405dd81100a7337c48b116d37d918a7d1b974 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:29:11 +0000 Subject: [PATCH 427/445] Automatic changelog for PR #1340 [ci skip] --- html/changelogs/AutoChangeLog-pr-1340.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1340.yml diff --git a/html/changelogs/AutoChangeLog-pr-1340.yml b/html/changelogs/AutoChangeLog-pr-1340.yml new file mode 100644 index 000000000000..e6bd2ed9f47b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1340.yml @@ -0,0 +1,4 @@ +author: "Stutternov" +delete-after: True +changes: + - balance: "Reduces bonus tiles on acute hearing from 3 to 1 (hearing whispers 4 tiles away down to 2)" \ No newline at end of file From 120d63459194ad5d2321e17bcc0c8e37ab96a451 Mon Sep 17 00:00:00 2001 From: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:31:19 -0500 Subject: [PATCH 428/445] Resync `install_byond.sh` to TG (#1341) ## About The Pull Request Syncs us back up with TG at https://github.com/tgstation/tgstation/blob/master/tools/ci/install_byond.sh The website is missing versions we need and is depricated. image And it ought to be a FALLBACK instead of the main option anyway. I will go and pr a fallback url to TG. --- tools/ci/install_byond.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/install_byond.sh b/tools/ci/install_byond.sh index 49c9ea5aa76b..4d65a3b0fc9c 100755 --- a/tools/ci/install_byond.sh +++ b/tools/ci/install_byond.sh @@ -14,7 +14,7 @@ else rm -rf "$HOME/BYOND" mkdir -p "$HOME/BYOND" cd "$HOME/BYOND" - curl -H "User-Agent: tgstation/1.0 CI Script" "https://spacestation13.github.io/byond-builds/${BYOND_MAJOR}/${BYOND_MAJOR}.${BYOND_MINOR}_byond_linux.zip" -o byond.zip + curl -H "User-Agent: tgstation/1.0 CI Script" "http://www.byond.com/download/build/${BYOND_MAJOR}/${BYOND_MAJOR}.${BYOND_MINOR}_byond_linux.zip" -o byond.zip unzip byond.zip rm byond.zip cd byond From b10eb33593f804e5a8dbf2038a3d6b2e85ca23f9 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 02:23:38 +0000 Subject: [PATCH 429/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-1301.yml | 5 ----- html/changelogs/AutoChangeLog-pr-1302.yml | 6 ------ html/changelogs/AutoChangeLog-pr-1330.yml | 4 ---- html/changelogs/AutoChangeLog-pr-1340.yml | 4 ---- html/changelogs/archive/2026-08.yml | 13 +++++++++++++ 5 files changed, 13 insertions(+), 19 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-1301.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1302.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1330.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1340.yml diff --git a/html/changelogs/AutoChangeLog-pr-1301.yml b/html/changelogs/AutoChangeLog-pr-1301.yml deleted file mode 100644 index 6df2c2eaeb53..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1301.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Stutternov" -delete-after: True -changes: - - rscadd: "Adds brass knuckles to the game and illegal vendor." - - rscadd: "Adds spiked knuckles to the Baron." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1302.yml b/html/changelogs/AutoChangeLog-pr-1302.yml deleted file mode 100644 index 763e82f5cd0e..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1302.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "cam_dev" -delete-after: True -changes: - - rscadd: "added crosses to loadout menu" - - qol: "crosses can be worn on belt and neck" - - image: "added two new cross sprites" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1330.yml b/html/changelogs/AutoChangeLog-pr-1330.yml deleted file mode 100644 index df17de51fae6..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1330.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - code_imp: "Fixes infinite loop caused by teleport moving while having a looped chain of grabbed mobs" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1340.yml b/html/changelogs/AutoChangeLog-pr-1340.yml deleted file mode 100644 index e6bd2ed9f47b..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1340.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Stutternov" -delete-after: True -changes: - - balance: "Reduces bonus tiles on acute hearing from 3 to 1 (hearing whispers 4 tiles away down to 2)" \ No newline at end of file diff --git a/html/changelogs/archive/2026-08.yml b/html/changelogs/archive/2026-08.yml index 6200f320ca0c..41a33bc86369 100644 --- a/html/changelogs/archive/2026-08.yml +++ b/html/changelogs/archive/2026-08.yml @@ -27,3 +27,16 @@ - rscadd: gives corax a waddle when walking in bird form cam_dev: - rscadd: '"I Nonni" role for Giovanni elders' +2026-08-06: + FalloutFalcon: + - code_imp: Fixes infinite loop caused by teleport moving while having a looped + chain of grabbed mobs + Stutternov: + - balance: Reduces bonus tiles on acute hearing from 3 to 1 (hearing whispers 4 + tiles away down to 2) + - rscadd: Adds brass knuckles to the game and illegal vendor. + - rscadd: Adds spiked knuckles to the Baron. + cam_dev: + - rscadd: added crosses to loadout menu + - qol: crosses can be worn on belt and neck + - image: added two new cross sprites From 3bce10f1e73cc1f7bf333064a73432af0f01dd91 Mon Sep 17 00:00:00 2001 From: FalloutFalcon Date: Wed, 5 Aug 2026 23:53:09 -0500 Subject: [PATCH 430/445] fix a strange compile fail im not sure why is exlusive to us but doesnt matter --- .../food_and_drinks/recipes/tablecraft/recipes_pastry.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm index 054bcd397b11..82118dc3e5dc 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm @@ -432,7 +432,7 @@ result = /obj/item/food/donkpocket/deluxe dish_category = DISH_PASTRY removed_foodtypes = BREAKFAST - crafting_flags = parent_type::crafting_flags | CRAFT_MUST_BE_LEARNED + // crafting_flags = parent_type::crafting_flags | CRAFT_MUST_BE_LEARNED // DARKPACK EDIT REMOVAL - (Causes a strange compile fail i dont care to fix.) /datum/crafting_recipe/food/donkpocket/deluxe/nocarb time = 1.5 SECONDS From 5726024b78178eb7d77fda6d30ea2e4ed1174417 Mon Sep 17 00:00:00 2001 From: FalloutFalcon Date: Thu, 6 Aug 2026 00:35:16 -0500 Subject: [PATCH 431/445] fix unit tests for tg pull --- code/modules/unit_tests/required_map_items.dm | 2 +- .../modules/weapons/icons/worn_melee.dmi | Bin 4131 -> 9960 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/unit_tests/required_map_items.dm b/code/modules/unit_tests/required_map_items.dm index 5afb8045b498..b252e726bca7 100644 --- a/code/modules/unit_tests/required_map_items.dm +++ b/code/modules/unit_tests/required_map_items.dm @@ -32,7 +32,6 @@ expected_types += /obj/machinery/computer/communications expected_types += /obj/machinery/drone_dispenser expected_types += /obj/item/piggy_bank/vault - */ // DARKPACK EDIT REMOVAL END // each map should probably have all of them expected_types += /obj/item/storage/photo_album/bar @@ -47,6 +46,7 @@ expected_types += /obj/item/storage/photo_album/hos expected_types += /obj/item/storage/photo_album/qm expected_types += /obj/item/storage/photo_album/rd + */ // DARKPACK EDIT REMOVAL END /// Types that must never be mapped in. /// Anything listed here is expected to be spawned by code instead. diff --git a/modular_darkpack/modules/weapons/icons/worn_melee.dmi b/modular_darkpack/modules/weapons/icons/worn_melee.dmi index 4e4969ed998815c3a79a65915baf4e7c729abdfa..97b298b945830eaf5be08209d87c0cbb343c90f8 100644 GIT binary patch literal 9960 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 literal 4131 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 From 48772c187fce6264a3293206dab9369a42c20afa Mon Sep 17 00:00:00 2001 From: FalloutFalcon Date: Thu, 6 Aug 2026 01:24:27 -0500 Subject: [PATCH 432/445] update screenshot for tg pull --- .../screenshot_hair_gradient_hair_gradient.png | Bin 674 -> 659 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/code/modules/unit_tests/screenshots/screenshot_hair_gradient_hair_gradient.png b/code/modules/unit_tests/screenshots/screenshot_hair_gradient_hair_gradient.png index 193ba8db2788f727cd9da1f192d2a4389f196770..479a70ec2ac94be11cf52527073c66bf3e16d6c9 100644 GIT binary patch delta 351 zcmV-l0igb(1(OAkj06AR(UFfiCEA{n@4vB~cYVm0pX95x?6SPTk(sQ5iL-cb>c6!4 z(Z*w`7nPCifPWE5L_t(Ijir%kQvxvzhKn34D2gKD2}bZhK|DwlN_PMMzcSg%`k`58 z)J$hP-S_F6qt5**DqGcRtyHbo8%eUAG@FewqP4TzZtv}P4i3Afe$+eepL9<9y|Z)M z;No&Hyc)puXnbSpw|DmwczB$`>}mdN=r6Bt3nW?);eY$ba%m7N@7IKk#NMyW90D+r z>fSEWp*SGY8cTEn0CCW-eILHV^1yHzf{j5)^$0971F`|2bjYCqV(B#^#K0ARGb|Y7 zx7H!~IX^`Jasgl(_n~PO(8Xz%#z3g;%OT6+Hdr$&A2HgLE xFitE7!YGvK&kkGwHo=>RlqWR_&SSF`@d;@tL=o_Z=tuwn002ovPDHLkV1l>>t!V%N delta 366 zcmV-!0g?Wb1)>Fzj05@6#*vRXCE>58|KQP`cYVm0pX95x?6SPTk(sQ5iM)Gp+Mbf? zzqBIXD)o`=fPWxKL_t(Ijh&K*Qo}F|h7F|+1qx-9GF!ynvof@fr1yU-q%d+v8TpJHyl)mq4^?RKXq7K?7TlOvXvS9-nGwf_3XrmJrawud|Y-Qi$w-%oIG zI2s>~;P~Y9%+=2?F0bJF<`(YmA08e3>G@@X5GN7v^?z+Tb%y25nX+PV;vr$-`C;gU|{@|ksW|V7}Z7d=v5zn0tjM|L@`kSJ;0`5 zK|X*?+9PPE#MlH_kVth_f(V;65L$-H!7?B<+90+fD+|4A>R+vj*Wz~)0b){%9H0fF zwv*Yb-!}jx6_B@}ssZ^wpe3mN$Li|Qb~4zGx_?x#`4R;^b?Wu&ce0R0NJ8w2O#lD@ M07*qoM6N<$f Date: Thu, 6 Aug 2026 12:37:09 -0500 Subject: [PATCH 433/445] Fixes masquerade breaching clan spawns (#1255) ## About The Pull Request Nossie wont get ripped out of deathmatches and other spawns. image I can also set my own loadout items and if they protect you, your shit wont get replaced. image ## Why It's Good For The Game Fixes https://github.com/DarkPack13/SecondCity/issues/848 Fixes https://github.com/The-Final-Nights/The-Final-Nights-Rebase/issues/388 ## Changelog :cl: fix: Masquerade breaches no longer get smuggled out of deathmatch arena and other spawns not meant to send them to the sewers fix: Loadout items wont get replaced by robes and masks assuming they cover your breaches /:cl: --------- Co-authored-by: chazzyjazzy <33268885+chazzyjazzy@users.noreply.github.com> --- code/_globalvars/traits/admin_tooling.dm | 1 + .../masquerade/code/masquerade_landmark.dm | 12 +++---- .../modules/splats/code/subsplat/_subsplat.dm | 20 +++++++++--- .../code/vampire_clan/__vampire_clan.dm | 31 ++++++++++++------- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index da65cfa50859..35c9f7af183f 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -411,6 +411,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_LOCAL_SIXTHSENSE" = TRAIT_LOCAL_SIXTHSENSE, // DARKPACK EDIT ADD "TRAIT_LOUD_WARCRY" = TRAIT_LOUD_WARCRY, // DARKPACK EDIT ADD - WEREWOLF "TRAIT_MASQUERADE_VIOLATING_EYES" = TRAIT_MASQUERADE_VIOLATING_EYES, // DARKPACK EDIT ADD + "TRAIT_MASQUERADE_VIOLATING_FACE" = TRAIT_MASQUERADE_VIOLATING_FACE, // DARKPACK EDIT ADD "TRAIT_MERIT_UNTAMABLE" = TRAIT_MERIT_UNTAMABLE, // DARKPACK EDIT ADD - MERITS_FLAWS "TRAIT_METAMORPH" = TRAIT_METAMORPH, // DARKPACK EDIT ADD - MERITS_FLAWS "TRAIT_NEEDS_BLOOD" = TRAIT_NEEDS_BLOOD, // DARKPACK EDIT ADD - Hunger and Frenzy diff --git a/modular_darkpack/modules/masquerade/code/masquerade_landmark.dm b/modular_darkpack/modules/masquerade/code/masquerade_landmark.dm index 3d47c93ac5b7..716d33387df8 100644 --- a/modular_darkpack/modules/masquerade/code/masquerade_landmark.dm +++ b/modular_darkpack/modules/masquerade/code/masquerade_landmark.dm @@ -1,13 +1,11 @@ /obj/effect/landmark/latejoin_masquerade - name = "JoinLate" + name = "JoinLate Masquerade" /obj/effect/landmark/latejoin_masquerade/Initialize(mapload) - . = ..() - REGISTER_REQUIRED_MAP_ITEM(1, INFINITY) - GLOB.masquerade_latejoin += src -/obj/effect/landmark/latejoin_masquerade/Destroy() - . = ..() + ..() + + GLOB.masquerade_latejoin += loc + return INITIALIZE_HINT_QDEL - GLOB.masquerade_latejoin -= src diff --git a/modular_darkpack/modules/splats/code/subsplat/_subsplat.dm b/modular_darkpack/modules/splats/code/subsplat/_subsplat.dm index b83087902e52..6b655a62c6e0 100644 --- a/modular_darkpack/modules/splats/code/subsplat/_subsplat.dm +++ b/modular_darkpack/modules/splats/code/subsplat/_subsplat.dm @@ -32,6 +32,7 @@ // Applies on_join_round effects when a client logs into this mob if(joining_round) RegisterSignal(gaining_mob, COMSIG_MOB_LOGIN, PROC_REF(on_join_round)) + RegisterSignal(gaining_mob, COMSIG_HUMAN_CHARACTER_SETUP_FINISHED, PROC_REF(apply_after_setup)) /** * Undoes the effects of on_gain more or less @@ -42,7 +43,7 @@ */ /datum/subsplat/proc/on_lose(mob/living/carbon/human/losing_mob) SHOULD_CALL_PARENT(TRUE) - UnregisterSignal(losing_mob, COMSIG_MOB_LOGIN) + UnregisterSignal(losing_mob, list(COMSIG_MOB_LOGIN, COMSIG_HUMAN_CHARACTER_SETUP_FINISHED)) return @@ -52,8 +53,7 @@ * at roundstart. Anything that's not innate * to the subsplat and more part of its social * structure or whatnot should go in here. - * Will teleport Masquerade-breaching Clans to - * safe areas and give them their subsplat keys by + * Will give them their subsplat keys by * default. * * Arguments: @@ -61,7 +61,6 @@ */ /datum/subsplat/proc/on_join_round(mob/living/carbon/human/joining) SIGNAL_HANDLER - SHOULD_CALL_PARENT(TRUE) if(subsplat_keys && check_doors_for_keys && key_has_matching_door(subsplat_keys)) @@ -69,6 +68,19 @@ UnregisterSignal(joining, COMSIG_MOB_LOGIN) +/** + * Applies anything that should only be applied when joining the round through late-start or round-start + * This exludes spawning as a ghost and other such sources. This is for stuff like outfits or teleporting players to locations. + * + * Arguments: + * * source - Human with this subsplat joining the round. + */ +/datum/subsplat/proc/apply_after_setup(mob/living/carbon/human/source) + SIGNAL_HANDLER + SHOULD_CALL_PARENT(TRUE) + + UnregisterSignal(source, COMSIG_HUMAN_CHARACTER_SETUP_FINISHED) + /// Displays description and roleplay level of the subsplat. /datum/subsplat/proc/show_lore(mob/user) if(desc) diff --git a/modular_darkpack/modules/vampire_the_masquerade/code/vampire_clan/__vampire_clan.dm b/modular_darkpack/modules/vampire_the_masquerade/code/vampire_clan/__vampire_clan.dm index 92373f6cc395..1b3d5652bd9a 100644 --- a/modular_darkpack/modules/vampire_the_masquerade/code/vampire_clan/__vampire_clan.dm +++ b/modular_darkpack/modules/vampire_the_masquerade/code/vampire_clan/__vampire_clan.dm @@ -91,20 +91,29 @@ losing_mob.remove_faction(id) -/datum/subsplat/vampire_clan/on_join_round(mob/living/carbon/human/joining) +/datum/subsplat/vampire_clan/apply_after_setup(mob/living/carbon/human/source) . = ..() - if (HAS_TRAIT(joining, TRAIT_MASQUERADE_VIOLATING_FACE)) + // if they spawn with the masquerade violating face trait, give them the things to cover up so they aren't stuck in the sewer. + if (HAS_TRAIT(source, TRAIT_MASQUERADE_VIOLATING_FACE) || HAS_TRAIT(source, TRAIT_MASQUERADE_VIOLATING_EYES)) if (length(GLOB.masquerade_latejoin)) - var/obj/effect/landmark/latejoin_masquerade/LM = pick(GLOB.masquerade_latejoin) - if (LM) - joining.forceMove(get_turf(LM)) - // if they spawn with the masquerade violating face trait, give them the things to cover up so they aren't stuck in the sewer. - var/obj/item/clothing/suit/hooded/robes/darkred/new_robe = new(joining.loc) - joining.equip_to_appropriate_slot(new_robe, FALSE) - - var/obj/item/clothing/mask/vampire/venetian_mask/fancy/new_mask = new(joining.loc) - joining.equip_to_appropriate_slot(new_mask, FALSE) + var/turf/join_spot = pick(GLOB.masquerade_latejoin) + if(join_spot) + source.forceMove(join_spot) + + if(HAS_TRAIT(source, TRAIT_MASQUERADE_VIOLATING_FACE) && !(source.obscured_slots & HIDEFACE)) + var/obj/item/clothing/mask/vampire/venetian_mask/fancy/new_mask = new(source.loc) + source.equip_to_appropriate_slot(new_mask, FALSE) + if(HAS_TRAIT(source, TRAIT_MASQUERADE_VIOLATING_EYES) && !source.is_eyes_covered()) + var/obj/item/clothing/glasses/vampire/sun/new_glasses = new(source.loc) + source.equip_to_appropriate_slot(new_glasses, FALSE) + + // We dont have masquerade breaching body. YET. + /* + var/obj/item/clothing/suit/hooded/robes/darkred/new_robe = new(source.loc) + source.equip_to_appropriate_slot(new_robe, FALSE) + */ + /// effect from daimonion psychomania /datum/subsplat/vampire_clan/proc/psychomania_effect(mob/living/target, mob/living/owner) From a999ac856b005f9e625f18c4990739ab948ae727 Mon Sep 17 00:00:00 2001 From: Lucy Date: Thu, 6 Aug 2026 13:39:21 -0400 Subject: [PATCH 434/445] fix a minor runtime with deactivating discipline powers (#1345) ## About The Pull Request title. super simple runtime fix (yes this runtime was observed live) ## Changelog no player-facing hcanges --- .../modules/powers/code/discipline/__discipline_power.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modular_darkpack/modules/powers/code/discipline/__discipline_power.dm b/modular_darkpack/modules/powers/code/discipline/__discipline_power.dm index 91ef200f59db..78508bef81a1 100644 --- a/modular_darkpack/modules/powers/code/discipline/__discipline_power.dm +++ b/modular_darkpack/modules/powers/code/discipline/__discipline_power.dm @@ -650,7 +650,7 @@ SEND_SIGNAL(src, COMSIG_POWER_DEACTIVATE, src, target) SEND_SIGNAL(owner, COMSIG_POWER_DEACTIVATE, src, target) - if (target) + if (istype(target)) SEND_SIGNAL(target, COMSIG_POWER_DEACTIVATE_ON, src) if (!multi_activate) From fc2d45017b3629b252b210fec9b3fb274b705212 Mon Sep 17 00:00:00 2001 From: Chen Marisa <18307183+flleeppyy@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:42:23 -0700 Subject: [PATCH 435/445] Detective UI (#1336) ## About The Pull Request Adds the detective UI as a possible option. Its color scheme and theme reasonably fits the codebase I think. I added the dice icon as well dont worry. image image ## Why It's Good For The Game Waiter waiter! I'd like another UI choice please! ## Changelog :cl: add: Detective UI is now a possible UI to choose from in preferences. /:cl: --- code/_onclick/hud/hud.dm | 1 + .../master_files/icons/hud/screen_detective.dmi | Bin 0 -> 11331 bytes 2 files changed, 1 insertion(+) create mode 100644 modular_darkpack/master_files/icons/hud/screen_detective.dmi diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 700a723c1a0e..dcd4bc2c58fb 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -8,6 +8,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list( "World-of-Darkness" = 'modular_darkpack/master_files/icons/hud/screen_darkness_new.dmi', // DARKPACK EDIT ADD - (Put at the top because it has almost full coverage now!) "Pentex-Knox" = 'modular_darkpack/master_files/icons/hud/screen_pentexknox.dmi', // DARKPACK EDIT ADD - (Trasen-Knox logo with a Pentex logo) + "Detective" = 'modular_darkpack/master_files/icons/hud/screen_detective.dmi' // DARKPACK EDIT ADD - I'd like one more waiter /* // DARKPACK EDIT REMOVAL "Midnight" = 'icons/hud/screen_midnight.dmi', "Retro" = 'icons/hud/screen_retro.dmi', diff --git a/modular_darkpack/master_files/icons/hud/screen_detective.dmi b/modular_darkpack/master_files/icons/hud/screen_detective.dmi new file mode 100644 index 0000000000000000000000000000000000000000..b8e6435681e2402010a707b2d70544c72d98a7df GIT binary patch literal 11331 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 LPrXXjI`n@5uM9^} literal 0 HcmV?d00001 From 2bc8e1c6d07d5f1830df4ab4f93e15da69b2c340 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:45:26 +0000 Subject: [PATCH 436/445] Automatic changelog for PR #1255 [ci skip] --- html/changelogs/AutoChangeLog-pr-1255.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1255.yml diff --git a/html/changelogs/AutoChangeLog-pr-1255.yml b/html/changelogs/AutoChangeLog-pr-1255.yml new file mode 100644 index 000000000000..9eef68bf73c8 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1255.yml @@ -0,0 +1,5 @@ +author: "FalloutFalcon" +delete-after: True +changes: + - bugfix: "Masquerade breaches no longer get smuggled out of deathmatch arena and other spawns not meant to send them to the sewers" + - bugfix: "Loadout items wont get replaced by robes and masks assuming they cover your breaches" \ No newline at end of file From e2a5918b41a13b2c3f9c95cab6d824e92e1bf1eb Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:36:06 +0000 Subject: [PATCH 437/445] Automatic changelog for PR #1336 [ci skip] --- html/changelogs/AutoChangeLog-pr-1336.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1336.yml diff --git a/html/changelogs/AutoChangeLog-pr-1336.yml b/html/changelogs/AutoChangeLog-pr-1336.yml new file mode 100644 index 000000000000..24b2ab57a3ca --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1336.yml @@ -0,0 +1,4 @@ +author: "flleeppyy" +delete-after: True +changes: + - rscadd: "Detective UI is now a possible UI to choose from in preferences." \ No newline at end of file From adbdaf901801c60ac5081fbee00c44abe98c5ddf Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:33:08 +0000 Subject: [PATCH 438/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-1255.yml | 5 ----- html/changelogs/AutoChangeLog-pr-1336.yml | 4 ---- html/changelogs/archive/2026-08.yml | 8 ++++++++ 3 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-1255.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-1336.yml diff --git a/html/changelogs/AutoChangeLog-pr-1255.yml b/html/changelogs/AutoChangeLog-pr-1255.yml deleted file mode 100644 index 9eef68bf73c8..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1255.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "FalloutFalcon" -delete-after: True -changes: - - bugfix: "Masquerade breaches no longer get smuggled out of deathmatch arena and other spawns not meant to send them to the sewers" - - bugfix: "Loadout items wont get replaced by robes and masks assuming they cover your breaches" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-1336.yml b/html/changelogs/AutoChangeLog-pr-1336.yml deleted file mode 100644 index 24b2ab57a3ca..000000000000 --- a/html/changelogs/AutoChangeLog-pr-1336.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "flleeppyy" -delete-after: True -changes: - - rscadd: "Detective UI is now a possible UI to choose from in preferences." \ No newline at end of file diff --git a/html/changelogs/archive/2026-08.yml b/html/changelogs/archive/2026-08.yml index 41a33bc86369..ab188c1e7091 100644 --- a/html/changelogs/archive/2026-08.yml +++ b/html/changelogs/archive/2026-08.yml @@ -40,3 +40,11 @@ - rscadd: added crosses to loadout menu - qol: crosses can be worn on belt and neck - image: added two new cross sprites +2026-08-07: + FalloutFalcon: + - bugfix: Masquerade breaches no longer get smuggled out of deathmatch arena and + other spawns not meant to send them to the sewers + - bugfix: Loadout items wont get replaced by robes and masks assuming they cover + your breaches + flleeppyy: + - rscadd: Detective UI is now a possible UI to choose from in preferences. From 08f5f66ccdaf55dff574dd04bf9d133d7004daaf Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 04:56:57 +0000 Subject: [PATCH 439/445] Automatic changelog for PR #1338 [ci skip] --- html/changelogs/AutoChangeLog-pr-1338.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1338.yml diff --git a/html/changelogs/AutoChangeLog-pr-1338.yml b/html/changelogs/AutoChangeLog-pr-1338.yml new file mode 100644 index 000000000000..eda8cac2747a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1338.yml @@ -0,0 +1,4 @@ +author: "Beautiful TG coders" +delete-after: True +changes: + - code_imp: "TG Pull. Thank you TG." \ No newline at end of file From 5d3f57fc0b54f9f2e21921286503c8ef8c540b5e Mon Sep 17 00:00:00 2001 From: baabass1 Date: Thu, 6 Aug 2026 22:15:08 -0700 Subject: [PATCH 440/445] Fix missing inhand icon state for door shield (#1219) ## About The Pull Request This pull request fixes the missing in-hand icon state for the door shield item. The issue was caused by the door shield defining `icon_state = "door"` but not defining `inhand_icon_state`, preventing the correct sprite from being displayed when the item is held. This change adds the missing ```dm inhand_icon_state = "door" ``` assignment to the door shield item definition in `modular_darkpack/modules/doors/code/door_item.dm`. No sprite assets or gameplay behavior were modified. ## Why It's Good For The Game This change restores the intended in-hand appearance of the door shield by ensuring the correct sprite is displayed when the item is held. The fix is minimal, reuses the existing sprite assets, and does not affect gameplay or unrelated systems. ## Changelog :cl: fix: Fixed the door shield not displaying its correct in-hand sprite when held. code: Added the missing `inhand_icon_state` assignment for the door shield item. /:cl: --- .../modules/deprecated/icons/lefthand.dmi | Bin 37448 -> 37451 bytes .../modules/deprecated/icons/righthand.dmi | Bin 37204 -> 37207 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/modular_darkpack/modules/deprecated/icons/lefthand.dmi b/modular_darkpack/modules/deprecated/icons/lefthand.dmi index 70c8b27714e71a9d14438c37771fd8747366ca8d..0742a8ca97030f5c54238879bab2db66e4be8386 100644 GIT binary patch delta 660 zcmV;F0&D%qqyo#N0+1wsczRS=bVOxyV{&P5bZKvH004NLm6y#<OF+Su!`W6!XWEpPbZY$zq|#JaR_6SHCilKD~Mdd6U7r2HP~@l ztZeWaYVU0XhaDvmA3{NR!JQUt<+wnFB%9wCVz#u(nm@YTe!kG*H>lt z)k*zeb%IM6v~u53omj3g$)!0FLuZ$gLi%PJ@0*Ro2(RXUXxeSN{pVjr+h+f*>@yA) znHeFCMKMe*y(nZc#gJqVMPQn3QOr~7C%I}0^^=^x6y-(55y?%uhgo(-1ZI(1iR3p? zbSiF)z}(rm$o|hIuwsvxvpgrqNd45_iCpzGXN^*rZsi6D)9pbwc<1)6m2+b(_}bYL z`soX8zuVk@oRVa)MNtcsMqa7N0WOe(VuzTa_gEEe3MwRhquOux+x=M)L|Ekn_e4{t zIuSf1$DyATtIy{(p;?7FBa~HfdCbtaa;KT|tz29@$_Y0^-|ln+>K)Y$sEC^5BjO2* uu6&$@q8Se#oU$etp0Fyh|BAdc56}1J{^_y#3)S=nUZp7x{j9U10;Z2#Ydd8C delta 657 zcmV;C0&e}wqyosK0+1wsb$V1-bVOxyV{&P5bZKvH004NLm6y#<Y1}4i5=U`51)hEnfEcaB+Iy_@s~yK5f9KNtdieeLZ0?^PyAxIp zl+D5He_VH)HMr)@!R&6jjmI$g_Wr>Q8k&Q-sEf5&OPDUYjSH23&B5%u%~&BJn}g}U zNDGV>Vids=3W|`2>_lkr5HiCmf^!a|?2|p6P(J_i7DUD&Oi}h|v0`f=aurV$PgvF9 zk@I5ZfY;ClZzDK7QWo(sRD>72)`Gp97if^>@P|swmR8yGCn>xBByZ$94WlBJT!8zZ za`96tD(J|t$|>Q09EzeW{F0k4j4?vO<}0mO_y0xj{)?8c{hXH5c}~k^Qaoiv9T!ho zQN_hmMmeAY%!O)G82ljR6WU1Rl$e}O8;P8pM;nQp9IuT;P7Zc?3s>W!(2eCSTwyBh zRXKfiRzFys;TlG*-FH-HmK)4+YtF=Qv`ft)f1}2`ZsRb2!mBx$e%o*V`d2Zw*?(*M zjDtmHCdgA&h^eI)g)OESvV21kn3AmuJ*8omyQb1G%k@i9UQ`^BJfsIm@>N7&7O9m; zJ`+W!;@Fcyv1(w&29b)akW-{#?(amdc}hj&6y{rb^uc_)*GX>b?|Qi^ri!meTf;Da zq5XF|n^Tg147Mt|fbzsERXMd4N>FSM2?mc%F`%G9){m&&cDLP~m4Ad)E^SXVA61V7 z57}`TX2sI;`Hs*m!CVmPs(5oG7bVOxyV{&P5bZKvH004NLm6y#< zeQAC_{CRvf_fL<_2@3~G=4cMryJoWnSG_r!{Y|s+7)D>;Ke$3gb2Jxau@*}X<3+P^ zp|ClcL$etQ%*f_w+Aq=qqlFknFo%pHOF+Su!`W6!XWEpPbZY$zq|#JaR_6S zHCilKD~Mdd6U7r2HP~@ltZeXq8fx!t1cx0Z5g$T9c)^_(Y~{E>g(REb7h<-w%9=k( zS@kD*Bl|QAicoR}Zhy(iPjgm59m6Vzgj2|hwD3!+Ix~g{Gge<{#d7!&z5fv{yZxM& z!+B22X_7r9MIL8QNs-0bQ$pFG49uBIRS5hbFv+Dk5kqH}l0y1s8t@yA)nHeFCMKMe*y(nZc#gJqVMPQn3QOr~7C%I}0^^=^x6y-(5 z5y?%uhgo(-1ZI(1iR3qbQFJP9jKJL4xXAv`C9q2J delta 622 zcmV-!0+IdKq5{;S0+1vec6wA;bVOxyV{&P5bZKvH004NLm6y#< zY+NX8j^@y8h5|FPIhyv1w7_T~MiI;*qX>COPK0_7Au+5XIHfSiKH1X=TW zPf~XMN#4kJ8U{rwIRm%9+iw5)S24EPe{1`UgGFXWNMlimsihZ%Ev6Wfd_xhKW?K|`O8q2vO`(30>zAUu zs5l~dNcS+yR}q0(q*fw-`Aig@iepa-#VUan8^oMtg&ZUGQ+p?J)zh3cPGP#0M;}bL z2c6{3?OiKZ#aQsQvnBM?7utTevpFTnV2h#)D2=>QkyBeB2gUX`#1|lBMcv$vL_dwuqv_tn!Gd*&-dp3>9P3>uUK57x>TV9 Iv!(*Ej_PMHu>b%7 From 65ed11ccb4782aa995db5a00e40fdc0baa4fa967 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 05:15:31 +0000 Subject: [PATCH 441/445] Automatic changelog for PR #1219 [ci skip] --- html/changelogs/AutoChangeLog-pr-1219.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1219.yml diff --git a/html/changelogs/AutoChangeLog-pr-1219.yml b/html/changelogs/AutoChangeLog-pr-1219.yml new file mode 100644 index 000000000000..a6b11014bc70 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1219.yml @@ -0,0 +1,5 @@ +author: "baabass1" +delete-after: True +changes: + - bugfix: "Fixed the door shield not displaying its correct in-hand sprite when held." + - code_imp: "Added the missing `inhand_icon_state` assignment for the door shield item." \ No newline at end of file From 6c6bb3bb8d61310e4ee343b1a33adc1445dfd7b9 Mon Sep 17 00:00:00 2001 From: Lucy Date: Fri, 7 Aug 2026 01:29:08 -0400 Subject: [PATCH 442/445] allow you to see phone numbers on examine (+ clean up some phone shitcode) (#1344) ## About The Pull Request made it so you can see a phone's number by examining it (albeit you have to be holding it) also did a lot of shitcode cleanup. it's still kinda shitcode but it's slightly a tiny bit better now. also deleting endpost forums only checked for admin client-side lmao ## Why It's Good For The Game kinda stupid you have to call someone else / get someone else to look at ur published number to see ur own phone number too lazy to make it a memory so this is better for now i guess also shitcode cleanup good ## Changelog :cl: qol: You can now see your phone number by examining your phone. code: Cleaned up a lot of phone shitcode. /:cl: --- code/__DEFINES/~darkpack/phones.dm | 8 - .../modules/phones/code/_phone.dm | 224 ++++++++++-------- .../modules/phones/code/phone_effects.dm | 5 - .../modules/phones/code/phone_procs.dm | 10 +- .../modules/phones/code/phone_subsystem.dm | 9 +- .../phones/code/stationary_subtypes.dm | 30 +-- 6 files changed, 143 insertions(+), 143 deletions(-) diff --git a/code/__DEFINES/~darkpack/phones.dm b/code/__DEFINES/~darkpack/phones.dm index e0ca254dd09a..839b2c2a8987 100644 --- a/code/__DEFINES/~darkpack/phones.dm +++ b/code/__DEFINES/~darkpack/phones.dm @@ -8,14 +8,6 @@ #define PHONE_CALLING 2 #define PHONE_IN_CALL 3 -#define PHONE_NO_SIM (1<<1) -#define PHONE_OPEN (1<<2) - -DEFINE_BITFIELD(phone_flags, list( - "PHONE_NO_SIM" = PHONE_NO_SIM, - "PHONE_OPEN" = PHONE_OPEN, -)) - // Icons used for call history logging #define PHONE_CALL_ACCEPTED "fa-phone-volume" #define PHONE_CALL_DECLINED "fa-phone-slash" diff --git a/modular_darkpack/modules/phones/code/_phone.dm b/modular_darkpack/modules/phones/code/_phone.dm index 1374c2c3dde2..324235d8ba13 100644 --- a/modular_darkpack/modules/phones/code/_phone.dm +++ b/modular_darkpack/modules/phones/code/_phone.dm @@ -3,6 +3,7 @@ desc = "A portable device to call anyone you want." icon = 'modular_darkpack/modules/phones/icons/phone.dmi' ONFLOOR_ICON_HELPER('modular_darkpack/modules/phones/icons/phone_onfloor.dmi') + base_icon_state = "phone" icon_state = "phone" inhand_icon_state = "phone" lefthand_file = 'modular_darkpack/modules/phones/icons/lefthand.dmi' @@ -26,10 +27,14 @@ var/obj/machinery/newscaster/irc_channel // Do we have a SIM card? var/obj/item/sim_card/sim_card + /// If we should spawn a SIM card as init, what should its type be? + var/default_sim_card_type = /obj/item/sim_card // There's a wiki in our phone. Literally. var/obj/item/book/manual/wiki/wiki_book - // Phone flags, for things like if its open or if it has no sim card. - var/phone_flags = NONE + // Is the phone opened? + var/opened = FALSE + /// Is this phone always open? + var/always_open = FALSE // The phone's current state. VAR_PRIVATE/current_state = PHONE_AVAILABLE // The number the phone has dialed. @@ -42,8 +47,6 @@ var/ringer = TRUE // If the phone shows balloon alerts when ringing. var/vibration = TRUE - // Passive particle effect generation for when on call - var/obj/effect/abstract/particle_holder/particle_generator // If the phone's microphone is muted. var/muted = FALSE // ID of the timer that the phone uses for ringing. Deleted once the user denies a phone call or misses it. @@ -67,8 +70,8 @@ /obj/item/smartphone/Initialize(mapload) . = ..() GLOB.phones_list += src - if(!sim_card) - sim_card = new() + if(!sim_card && default_sim_card_type) + sim_card = new default_sim_card_type(src) sim_card.phone_weakref = WEAKREF(src) phone_radio = new(src) phone_radio.keyslot = new @@ -113,8 +116,7 @@ if(our_contact.number == sim_card.phone_number) contact_network.contacts -= our_contact - if(particle_generator) - QDEL_NULL(particle_generator) + remove_shared_particles(/particles/phone_ringing, delete_on_empty = FALSE) lose_hearing_sensitivity(ROUNDSTART_TRAIT) UnregisterSignal(src, COMSIG_MOVABLE_HEAR) @@ -136,30 +138,32 @@ . += span_notice("[EXAMINE_HINT("Alt-Click")] or [EXAMINE_HINT("Right-Click")] to toggle the screen.") if(sim_card) . += span_notice("[EXAMINE_HINT("Ctrl-Click")] to remove [sim_card].") + if(sim_card.phone_number && (user.is_holding(src) || isobserver(user))) + . += span_notice("Its phone number is [span_bold("[sim_card.phone_number]")].") else . += span_notice("You can [EXAMINE_HINT("Insert")] a SIM card.") /obj/item/smartphone/attack_self(mob/user, modifiers) . = ..() - if(!(phone_flags & PHONE_OPEN)) + if(!opened) toggle_screen(user) ui_interact(user) /obj/item/smartphone/click_alt(mob/user) - if(!(user.is_holding(src))) + if(!user.is_holding(src)) return CLICK_ACTION_BLOCKING toggle_screen(user) return CLICK_ACTION_SUCCESS /obj/item/smartphone/item_ctrl_click(mob/user) - if(!(user.is_holding(src))) + if(!user.is_holding(src)) return CLICK_ACTION_BLOCKING if(!sim_card) balloon_alert(user, "no sim card!") return CLICK_ACTION_BLOCKING if(do_after(user, 2 SECONDS, src)) balloon_alert(user, "you remove \the [sim_card]!") - log_phone("[key_name(usr)] removed a SIM card with the number [sim_card.phone_number].") + log_phone("[key_name(user)] removed a SIM card with the number [sim_card.phone_number].") switch(current_state) if(PHONE_CALLING) hang_up_phone_call(dialed_number) @@ -170,43 +174,55 @@ user.put_in_hands(sim_card) sim_card.phone_weakref = null sim_card = null - phone_flags |= PHONE_NO_SIM + SStgui.update_uis(src) return CLICK_ACTION_SUCCESS return CLICK_ACTION_BLOCKING /obj/item/smartphone/item_interaction(mob/living/user, obj/item/tool, list/modifiers) - if(istype(tool, /obj/item/sim_card)) - if(sim_card) - balloon_alert(user, "[sim_card] already installed!") - return ITEM_INTERACT_BLOCKING - balloon_alert(user, "you insert \the [tool]!") - sim_card = tool - user.transferItemToLoc(tool, src) - sim_card.phone_weakref = WEAKREF(src) - phone_flags &= ~PHONE_NO_SIM - log_phone("[key_name(usr)] inserted a SIM card with the number [sim_card.phone_number].") - return TRUE - return ..() + if(!istype(tool, /obj/item/sim_card)) + return NONE + if(sim_card) + balloon_alert(user, "[sim_card] already installed!") + return ITEM_INTERACT_BLOCKING + if(!user.transferItemToLoc(tool, src)) + balloon_alert(user, "failed to install [tool]!") + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "you insert \the [tool]!") + sim_card = tool + sim_card.phone_weakref = WEAKREF(src) + log_phone("[key_name(user)] inserted a SIM card with the number [sim_card.phone_number].") + SStgui.update_uis(src) + return ITEM_INTERACT_SUCCESS + +/obj/item/smartphone/update_icon_state() + . = ..() + icon_state = (opened && !always_open) ? "[base_icon_state]_on" : base_icon_state + inhand_icon_state = (opened && !always_open) ? "[base_icon_state]_on" : base_icon_state /obj/item/smartphone/ui_status(mob/user, datum/ui_state/state) - if(!(phone_flags & PHONE_OPEN)) + if(!opened) return UI_CLOSE return ..() /obj/item/smartphone/ui_interact(mob/user, datum/tgui/ui) - . = ..() - var/datum/asset/background_files = get_asset_datum(/datum/asset/simple/phone_assets) - if(user.client) - background_files.send(user.client) ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "Telephone") ui.open() +/obj/item/smartphone/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/simple/phone_assets), + ) + /obj/item/smartphone/ui_data(mob/living/user) var/list/data = list() - data["my_number"] = sim_card ? sim_card.phone_number : "No SIM card inserted." - data["no_sim_card"] = (phone_flags & PHONE_NO_SIM) ? TRUE : FALSE + if(sim_card) + data["my_number"] = sim_card.phone_number + data["no_sim_card"] = FALSE + else + data["my_number"] = "No SIM card inserted" + data["no_sim_card"] = TRUE data["phone_in_call"] = (current_state == PHONE_IN_CALL) ? TRUE : FALSE data["phone_ringing"] = (current_state == PHONE_RINGING) ? TRUE : FALSE data["phone_calling"] = (current_state == PHONE_CALLING) ? TRUE : FALSE @@ -302,14 +318,16 @@ return data -/obj/item/smartphone/ui_act(action, params, datum/tgui/ui) +/obj/item/smartphone/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return + + var/mob/user = ui.user switch(action) if("call") - start_phone_call(usr, params["number"]) - log_phone("[key_name(usr)] called [params["number"]].") + start_phone_call(user, params["number"]) + log_phone("[key_name(user)] called [params["number"]].") return TRUE if("hang") @@ -320,93 +338,93 @@ return TRUE if("accept") - accept_phone_call(usr) - log_phone("[key_name(usr)] answered a phone call.") + accept_phone_call(user) + log_phone("[key_name(user)] answered a phone call.") return TRUE if("decline") decline_phone_call() - log_phone("[key_name(usr)] declined a phone call.") + log_phone("[key_name(user)] declined a phone call.") return TRUE if("publish_number") - to_chat(usr, span_notice("This text will represent you in the phonebook. example: Jane Doe | Anarchy Rose Manager")) - var/name = tgui_input_text(usr, "Phonebook Name", "Publish Number") + to_chat(user, span_notice("This text will represent you in the phonebook. example: Jane Doe | Anarchy Rose Manager")) + var/name = tgui_input_text(user, "Phonebook Name", "Publish Number", max_length = MAX_MESSAGE_LEN) if(!name) - to_chat(usr, span_danger("You must input text to publish your number.")) + to_chat(user, span_danger("You must input text to publish your number.")) return if(!sim_card) - to_chat(usr, span_danger("You must insert a SIM card to publish your number.")) + to_chat(user, span_danger("You must insert a SIM card to publish your number.")) return - name = trim(copytext_char(sanitize(name), 1, MAX_MESSAGE_LEN)) for(var/contact in SSphones.published_phone_numbers) if(SSphones.published_phone_numbers[contact] == sim_card.phone_number) - to_chat(usr, span_danger("Error: This number is already published.")) + to_chat(user, span_danger("Error: This number is already published.")) return TRUE SSphones.published_phone_numbers[name] = sim_card.phone_number - to_chat(usr, span_notice("Your number is now published.")) + to_chat(user, span_notice("Your number is now published.")) sim_card.published = TRUE sim_card.published_name = name - log_phone("[key_name(usr)] published their number ([name])/[sim_card.phone_number] to the phonebook.") + log_phone("[key_name(user)] published their number ([name])/[sim_card.phone_number] to the phonebook.") return TRUE if("unpublish_number") for(var/contact in SSphones.published_phone_numbers) - if(SSphones.published_phone_numbers[contact] == sim_card.phone_number) - log_phone("[key_name(usr)] unpublished their number ([contact])/[sim_card.phone_number] from the phonebook.") - SSphones.published_phone_numbers.Remove(contact) - sim_card.published = FALSE - sim_card.published_name = null - to_chat(usr, span_notice("Your number is now unpublished.")) - return TRUE + if(SSphones.published_phone_numbers[contact] != sim_card.phone_number) + continue + log_phone("[key_name(user)] unpublished their number ([contact])/[sim_card.phone_number] from the phonebook.") + SSphones.published_phone_numbers -= contact + sim_card.published = FALSE + sim_card.published_name = null + to_chat(user, span_notice("Your number is now unpublished.")) + return TRUE if("custom_background") - to_chat(usr, span_danger("Do NOT use images that can be considered offensive or obscene, or that contain references to something that happened after the year [CURRENT_STATION_YEAR]. Recommended image dimensions: 400x600 ")) - custom_background = tgui_input_text(usr, "Input background image URL", "Custom Background") + to_chat(user, span_danger("Do NOT use images that can be considered offensive or obscene, or that contain references to something that happened after the year [CURRENT_STATION_YEAR]. Recommended image dimensions: 400x600 ")) + custom_background = tgui_input_text(user, "Input background image URL", "Custom Background") if(!custom_background) - to_chat(usr, span_danger("You must input a URL to set a custom background.")) + to_chat(user, span_danger("You must input a URL to set a custom background.")) return phone_background = custom_background - log_phone("[key_name(usr)] set a custom background image on [src]: [custom_background]") + log_phone("[key_name(user)] set a custom background image on [src]: [custom_background]") return TRUE if("add_contact") - var/number = tgui_input_text(usr, "Input number", "Add Contact") + var/number = tgui_input_text(user, "Input number", "Add Contact") if(length(number) > 15) - to_chat(usr, span_danger("Entered number is too long")) + to_chat(user, span_danger("Entered number is too long")) return FALSE var/stripped_number = replacetext(number, " ", "") // remove spaces - var/new_contact_name = tgui_input_text(usr, "Input name", "Add Contact") + var/new_contact_name = tgui_input_text(user, "Input name", "Add Contact") if(!new_contact_name || !number) - to_chat(usr, span_danger("You must provide both a name and a number.")) + to_chat(user, span_danger("You must provide both a name and a number.")) return FALSE var/datum/phonecontact/new_contact = new() new_contact.number = "[stripped_number]" new_contact.name = "[new_contact_name]" contacts += new_contact - log_phone("[key_name(usr)] added a new contact: [new_contact_name] ([stripped_number])") + log_phone("[key_name(user)] added a new contact: [new_contact_name] ([stripped_number])") return TRUE if("remove_contact") - var/number = tgui_input_text(usr, "Input number", "Remove Contact") + var/number = tgui_input_text(user, "Input number", "Remove Contact") if(length(number) > 15) - to_chat(usr, span_danger("Entered number is too long")) + to_chat(user, span_danger("Entered number is too long")) return FALSE for(var/datum/phonecontact/contact in contacts) if(contact.number == number) contacts -= contact - log_phone("[key_name(usr)] removed a contact with number: [number]") + log_phone("[key_name(user)] removed a contact with number: [number]") return TRUE return FALSE if("block") - var/block_number = tgui_input_text(usr, "Input number to block", "Block Contact") + var/block_number = tgui_input_text(user, "Input number to block", "Block Contact") if(!block_number) - to_chat(usr, span_warning("You must provide a number.")) + to_chat(user, span_warning("You must provide a number.")) return FALSE if(length(block_number) > 15) - to_chat(usr, span_warning("Invalid number.")) + to_chat(user, span_warning("Invalid number.")) return FALSE var/datum/phonecontact/blocked_contact = new() @@ -417,9 +435,9 @@ return TRUE if("unblock") - var/result = tgui_input_text(usr, "Input number to unblock", "Unblock Contact") + var/result = tgui_input_text(user, "Input number to unblock", "Unblock Contact") if(!result) - to_chat(usr, span_warning("You must provide a number.")) + to_chat(user, span_warning("You must provide a number.")) return FALSE for(var/datum/phonecontact/unblocked_contact in blocked_contacts) if(unblocked_contact.name == result) @@ -429,25 +447,22 @@ if("delete_call_history") if(!length(phone_history_list)) - to_chat(usr, span_danger("You have no call history to delete.")) + to_chat(user, span_danger("You have no call history to delete.")) return FALSE - to_chat(usr, "Your total amount of history saved is: [length(phone_history_list)]") - var/number_of_deletions = tgui_input_number(usr, "Input the amount that you want to delete", "Deletion Amount", max_value = length(phone_history_list)) + to_chat(user, span_notice("Your total amount of history saved is: [length(phone_history_list)]")) + var/number_of_deletions = tgui_input_number(user, "Input the amount that you want to delete", "Deletion Amount", max_value = length(phone_history_list)) if(!number_of_deletions) return FALSE //Delete the call history depending on the amount inputed by the User if(number_of_deletions > length(phone_history_list)) //Verify if the requested amount in bigger than the history list. - to_chat(usr, "You cannot delete more items than the history contains.") + to_chat(user, span_warning("You cannot delete more items than the history contains.")) return FALSE else - for(var/i in 1 to number_of_deletions) - //It will always delete the first item of the list, so the last logs are deleted first - var/item_to_remove = phone_history_list[1] - phone_history_list -= item_to_remove - to_chat(usr, "[number_of_deletions] call history entries were deleted. Remaining: [length(phone_history_list)]") + phone_history_list.Cut(1, number_of_deletions + 1) + to_chat(user, span_notice("[number_of_deletions] call history entries were deleted. Remaining: [length(phone_history_list)]")) return TRUE if("terminal_sound") @@ -457,21 +472,23 @@ if("silent") ringer = !ringer - balloon_alert(usr, "ringer [ringer ? "on" : "off"]!") + balloon_alert(user, "ringer [ringer ? "on" : "off"]!") + if(!ringer) + remove_shared_particles(/particles/phone_ringing, delete_on_empty = FALSE) return TRUE if("vibration") vibration = !vibration - balloon_alert(usr, "vibration [vibration ? "on" : "off"]!") + balloon_alert(user, "vibration [vibration ? "on" : "off"]!") return TRUE if("speaker") if(phone_radio.canhear_range == 1) phone_radio.canhear_range = 3 - balloon_alert(usr, "speaker on!") + balloon_alert(user, "speaker on!") else phone_radio.canhear_range = 1 - balloon_alert(usr, "speaker off!") + balloon_alert(user, "speaker off!") return TRUE if("set_background") @@ -481,10 +498,10 @@ if("mute") muted = !muted phone_radio.set_listening(!muted) - balloon_alert(usr, "[muted ? "muted" : "unmuted"]!") + balloon_alert(user, "[muted ? "muted" : "unmuted"]!") if("wiki") - wiki_book.display_content(usr) + wiki_book.display_content(user) if("view_conversation") current_viewed_conversation = params["contact_number"] @@ -494,22 +511,24 @@ return TRUE if("submit_post") - submit_post(params["body"]) + submit_post(user, params["body"]) return TRUE if("endpost_registration") - endpost_registration() + endpost_registration(user) return TRUE if("remove_endpost") + if(!is_admin(user.client)) + CRASH("Non-admin somehow tried to call remove_endpost") var/post_index = text2num(params["post_index"]) if(!post_index) - to_chat(usr, "Invalid post index.") + to_chat(user, span_warning("Invalid post index.")) return FALSE var/list/selected_post = SSphones.endpost_posts[post_index] SSphones.endpost_posts.Cut(post_index, post_index + 1) - to_chat(usr, "Post '[selected_post["body"]]' by [selected_post["author"]] removed.") - log_phone("[key_name(usr)] removed an endpost: [selected_post["body"]] by [selected_post["author"]]", list("author" = selected_post["author"], "body" = selected_post["body"])) + to_chat(user, span_notice("Post '[selected_post["body"]]' by [selected_post["author"]] removed.")) + log_phone("[key_name(user)] removed an endpost: [selected_post["body"]] by [selected_post["author"]]", list("author" = selected_post["author"], "body" = selected_post["body"])) return TRUE if("keyboard_click") @@ -522,7 +541,7 @@ var/message_text = params["message_text"] if(!contact_number || !message_text) return FALSE - send_text_message(contact_number, message_text) + send_text_message(user, contact_number, message_text) if(ringer) playsound(loc, 'modular_darkpack/modules/phones/sounds/text_send.ogg', 50, TRUE) return TRUE @@ -534,7 +553,7 @@ if(convo.contact_number == contact_number) return convo -/obj/item/smartphone/proc/send_text_message(contact_number, message_text) +/obj/item/smartphone/proc/send_text_message(mob/user, contact_number, message_text) if(!contact_number || !message_text) return FALSE @@ -556,7 +575,7 @@ receiving_phone.conversations += recv_conversation recv_conversation.add_message(message_text, FALSE) addtimer(CALLBACK(receiving_phone, PROC_REF(after_text_received), contact_name, message_text), rand(1 SECONDS, 2 SECONDS)) //simulate random delay before sending an audible/visible alert - log_phone("[key_name(usr)] sent a text to [contact_number]: [message_text]", list("sender" = contact_name, "receiver" = recv_contact_name, "message" = message_text)) + log_phone("[key_name(user)] sent a text to [contact_number]: [message_text]", list("sender" = contact_name, "receiver" = recv_contact_name, "message" = message_text)) return TRUE //stuff to do after a text is received @@ -583,15 +602,10 @@ return formatted_messages /obj/item/smartphone/proc/toggle_screen(mob/user) - if(phone_flags & PHONE_OPEN) - phone_flags &= ~PHONE_OPEN - else - phone_flags |= PHONE_OPEN - icon_state = (phone_flags & PHONE_OPEN) ? "phone_on" : "phone" - inhand_icon_state = (phone_flags & PHONE_OPEN) ? "phone_on" : "phone" - update_icon() + opened = always_open || !opened + update_appearance(UPDATE_ICON_STATE) -/obj/item/smartphone/proc/submit_post(body) +/obj/item/smartphone/proc/submit_post(mob/user, body) if(!body) return FALSE @@ -606,13 +620,13 @@ ) UNTYPED_LIST_ADD(SSphones.endpost_posts, new_post) - log_phone("[key_name(usr)] submitted a new endpost: [trim(body)] as [endpost_username]") + log_phone("[key_name(user)] submitted a new endpost: [trim(body)] as [endpost_username]") return TRUE -/obj/item/smartphone/proc/endpost_registration() - var/new_username = tgui_input_text(usr, "Choose your username:", "EndPost Registration", "", 14) //max size of 14 chars or it starts bumping elements around - log_phone("[key_name(usr)] [endpost_username ? "updated" : "registered"] their username as [new_username]") +/obj/item/smartphone/proc/endpost_registration(mob/user) + var/new_username = tgui_input_text(user, "Choose your username:", "EndPost Registration", max_length = 14) //max size of 14 chars or it starts bumping elements around + log_phone("[key_name(user)] [endpost_username ? "updated" : "registered"] their username as [new_username]") endpost_username = new_username return TRUE diff --git a/modular_darkpack/modules/phones/code/phone_effects.dm b/modular_darkpack/modules/phones/code/phone_effects.dm index 0eddabad1ceb..d4193e1d869a 100644 --- a/modular_darkpack/modules/phones/code/phone_effects.dm +++ b/modular_darkpack/modules/phones/code/phone_effects.dm @@ -10,8 +10,3 @@ gravity = list(0, 0.1) position = generator(GEN_SPHERE, 0, 16, NORMAL_RAND) spin = generator(GEN_NUM, -1, 1, NORMAL_RAND) - - -/obj/item/smartphone/proc/setup_particles() - if(!particle_generator) - particle_generator = new(src, /particles/phone_ringing, PARTICLE_ATTACH_MOB) diff --git a/modular_darkpack/modules/phones/code/phone_procs.dm b/modular_darkpack/modules/phones/code/phone_procs.dm index b2744e0af5aa..db3423e400a0 100644 --- a/modular_darkpack/modules/phones/code/phone_procs.dm +++ b/modular_darkpack/modules/phones/code/phone_procs.dm @@ -59,17 +59,17 @@ if(current_state == PHONE_RINGING) START_PROCESSING(SSprocessing, src) if(ringer) - setup_particles() + add_shared_particles(/particles/phone_ringing, particle_flags = PARTICLE_ATTACH_MOB) - if(current_state == PHONE_IN_CALL || current_state == PHONE_AVAILABLE) + else if(current_state == PHONE_IN_CALL || current_state == PHONE_AVAILABLE) if(phone_ringing_timer) deltimer(phone_ringing_timer) - if(particle_generator) - QDEL_NULL(particle_generator) + phone_ringing_timer = null + remove_shared_particles(/particles/phone_ringing, delete_on_empty = FALSE) STOP_PROCESSING(SSprocessing, src) /obj/item/smartphone/proc/check_missing_sim_card(mob/user) - if(phone_flags & PHONE_NO_SIM) + if(QDELETED(sim_card)) balloon_alert(user, "no SIM!") return TRUE return FALSE diff --git a/modular_darkpack/modules/phones/code/phone_subsystem.dm b/modular_darkpack/modules/phones/code/phone_subsystem.dm index dab078bed12d..7fb10bc0d378 100644 --- a/modular_darkpack/modules/phones/code/phone_subsystem.dm +++ b/modular_darkpack/modules/phones/code/phone_subsystem.dm @@ -15,6 +15,12 @@ SUBSYSTEM_DEF(phones) // Posts for the endpost feed var/list/endpost_posts = list() +/datum/controller/subsystem/phones/Recover() + assigned_phone_numbers = SSphones.assigned_phone_numbers + frequencies_in_use = SSphones.frequencies_in_use + published_phone_numbers = SSphones.published_phone_numbers + endpost_posts = SSphones.endpost_posts + // Generates a random phone number from the available ranges, ten digits, starts with a 415 or 628. /datum/controller/subsystem/phones/proc/random_number() return "[pick("415","628")][rand(0,9)][rand(0,9)][rand(0,9)][rand(0,9)][rand(0,9)][rand(0,9)][rand(0,9)]" @@ -59,5 +65,4 @@ SUBSYSTEM_DEF(phones) for(var/obj/item/sim_card/sim_card as anything in assigned_phone_numbers) if(sim_card.phone_number == phone_number) gotten_sim_card = sim_card - var/gotten_phone = gotten_sim_card?.phone_weakref?.resolve() - return gotten_phone + return gotten_sim_card?.phone_weakref?.resolve() diff --git a/modular_darkpack/modules/phones/code/stationary_subtypes.dm b/modular_darkpack/modules/phones/code/stationary_subtypes.dm index 723127447a9f..c793f547145a 100644 --- a/modular_darkpack/modules/phones/code/stationary_subtypes.dm +++ b/modular_darkpack/modules/phones/code/stationary_subtypes.dm @@ -3,14 +3,12 @@ desc = "Ring ring. Ring ring. Ring ring." ONFLOOR_ICON_HELPER(null) icon = 'modular_darkpack/modules/phones/icons/phone.dmi' + base_icon_state = "payphone" icon_state = "payphone" anchored = TRUE - -/obj/item/smartphone/payphone/Initialize(mapload) - sim_card = new /obj/item/sim_card/landline() - sim_card.phone_weakref = WEAKREF(src) - phone_flags |= PHONE_OPEN - . = ..() + opened = TRUE + always_open = TRUE + default_sim_card_type = /obj/item/sim_card/landline /obj/item/smartphone/payphone/attack_hand(mob/user, list/modifiers) . = ..() @@ -21,14 +19,12 @@ desc = "The usual phone of a cleaning company used to communicate with employees" ONFLOOR_ICON_HELPER(null) icon = 'modular_darkpack/modules/phones/icons/phone.dmi' + base_icon_state = "phone_black" icon_state = "phone_black" anchored = TRUE - -/obj/item/smartphone/clean/Initialize(mapload) - sim_card = new /obj/item/sim_card/cleaner() - sim_card.phone_weakref = WEAKREF(src) - phone_flags |= PHONE_OPEN - . = ..() + opened = TRUE + always_open = TRUE + default_sim_card_type = /obj/item/sim_card/cleaner /obj/item/smartphone/clean/attack_hand(mob/user, list/modifiers) . = ..() @@ -39,14 +35,12 @@ desc = "A phone used for emergency calls." ONFLOOR_ICON_HELPER(null) icon = 'modular_darkpack/modules/phones/icons/phone.dmi' + base_icon_state = "phone_red" icon_state = "phone_red" anchored = TRUE - -/obj/item/smartphone/emergency/Initialize(mapload) - sim_card = new /obj/item/sim_card/emergency() - sim_card.phone_weakref = WEAKREF(src) - phone_flags |= PHONE_OPEN - . = ..() + opened = TRUE + always_open = TRUE + default_sim_card_type = /obj/item/sim_card/emergency /obj/item/smartphone/emergency/attack_hand(mob/user, list/modifiers) . = ..() From 68029d2d56f8dcc563a137fcc31437fd1ed90535 Mon Sep 17 00:00:00 2001 From: "darkpack13-api[bot]" <220339179+darkpack13-api[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 05:29:30 +0000 Subject: [PATCH 443/445] Automatic changelog for PR #1344 [ci skip] --- html/changelogs/AutoChangeLog-pr-1344.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-1344.yml diff --git a/html/changelogs/AutoChangeLog-pr-1344.yml b/html/changelogs/AutoChangeLog-pr-1344.yml new file mode 100644 index 000000000000..44faf8c4eadb --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-1344.yml @@ -0,0 +1,5 @@ +author: "Absolucy" +delete-after: True +changes: + - qol: "You can now see your phone number by examining your phone." + - code_imp: "Cleaned up a lot of phone shitcode." \ No newline at end of file From 5f9c62b8db422457b7f1e79887911be30f0c23a0 Mon Sep 17 00:00:00 2001 From: Lucy Date: Fri, 7 Aug 2026 01:33:14 -0400 Subject: [PATCH 444/445] don't spawn NPCs during unit tests (+minor `SShumannpcpool` fixes) (#1346) ## About The Pull Request this simply disables the NPC spawning pool stuff during unit tests. because spawning random NPCs and respawning them if they die is _asking_ for flaky failures. also properly remove npcs from from `SShumannpcpool.currentrun` when they're destroyed and slightly cleans up `SShumannpcpool.fire()` ## Why It's Good For The Game less overhead and issues for unit tests ## Changelog no player-facing changes at all, only affects unit tests --- .../modules/npc/code/human/__npc.dm | 1 + .../npc/code/human/npc_human_subsystem.dm | 29 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/modular_darkpack/modules/npc/code/human/__npc.dm b/modular_darkpack/modules/npc/code/human/__npc.dm index e50d09b43ba8..7a5625e2ef01 100644 --- a/modular_darkpack/modules/npc/code/human/__npc.dm +++ b/modular_darkpack/modules/npc/code/human/__npc.dm @@ -132,6 +132,7 @@ drop_on_death_list = null GLOB.npc_list -= src GLOB.alive_npc_list -= src + SShumannpcpool.currentrun -= src SShumannpcpool.try_repopulate() return ..() diff --git a/modular_darkpack/modules/npc/code/human/npc_human_subsystem.dm b/modular_darkpack/modules/npc/code/human/npc_human_subsystem.dm index 44b18ccbabbf..59f0c72a5feb 100644 --- a/modular_darkpack/modules/npc/code/human/npc_human_subsystem.dm +++ b/modular_darkpack/modules/npc/code/human/npc_human_subsystem.dm @@ -1,6 +1,10 @@ SUBSYSTEM_DEF(humannpcpool) name = "Human NPC Pool" - ss_flags = SS_POST_FIRE_TIMING|SS_BACKGROUND +#ifndef UNIT_TESTS + ss_flags = SS_POST_FIRE_TIMING | SS_BACKGROUND +#else + ss_flags = SS_NO_INIT | SS_NO_FIRE +#endif priority = FIRE_PRIORITY_NPC runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME wait = 0.3 SECONDS @@ -23,28 +27,20 @@ SUBSYSTEM_DEF(humannpcpool) return ..() /datum/controller/subsystem/humannpcpool/fire(resumed = FALSE) - - if (!resumed) - var/list/activelist = GLOB.npc_list - src.currentrun = activelist.Copy() + if(!resumed) + src.currentrun = GLOB.npc_list.Copy() //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun - - while(currentrun.len) - var/mob/living/carbon/human/npc/NPC = currentrun[currentrun.len] + while(length(currentrun)) + var/mob/living/carbon/human/npc/NPC = currentrun[length(currentrun)] --currentrun.len - - if (QDELETED(NPC)) - GLOB.npc_list -= NPC - stack_trace("Found a null in npc_list [NPC.type]!") - continue - - if (MC_TICK_CHECK) + NPC?.handle_automated_movement() + if(MC_TICK_CHECK) return - NPC.handle_automated_movement() /datum/controller/subsystem/humannpcpool/proc/try_repopulate() +#ifndef UNIT_TESTS if (!length(GLOB.npc_spawn_points)) return @@ -58,3 +54,4 @@ SUBSYSTEM_DEF(humannpcpool) /mob/living/carbon/human/npc/business \ ) new creating_npc(get_turf(chosen_spawn_point)) +#endif From 8cbffabc5a9b1112d5a56034e055572dded66f2f Mon Sep 17 00:00:00 2001 From: FalloutFalcon Date: Sat, 8 Aug 2026 13:28:47 -0500 Subject: [PATCH 445/445] fix landmarks.dmi --- .../modules/jobs/icons/landmarks.dmi | Bin 27410 -> 31976 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/modular_darkpack/modules/jobs/icons/landmarks.dmi b/modular_darkpack/modules/jobs/icons/landmarks.dmi index 70f1a070c1201e2e3dfc04bab5736378956c92b4..9f6026a02a6a44cd28c2f359d5c663510bd542b9 100644 GIT binary patch literal 31976 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 literal 27410 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 NvVz8oa(OfV{|Dbq0RsR4

  • +
    @@ -223,30 +226,12 @@ const GuideSection = () => { Living Heart. - - Make yourself a focus to be able - to cast various advanced spells to assist you in acquiring harder and - harder sacrifices. - - - - Accomplish all of your objectives to be able to learn the{' '} - final ritual. Complete the ritual - to become all powerful! - - - WARNING! -
    Accumulating a total of {points_to_aura}  + - Accumulating a total of {points_to_aura}  knowledge points -  to manifest a visible aura of  - Mansus energy around you. Simply - gaining the points is sufficent, spending them will not trigger it. -
    - This aura will be visible to all those around you and will mark you as - a heretic. Consider the risks before accumulating too much knowledge! -
    - Keep in mind that using a  - Codex Cicatrix will also make you - very obvious as a heretic when draining  - influences +  will manifest a visible aura of  + Mansus energy around you. This aura + will be visible to all those around you and will mark you as a + Heretic. Consider the risks before accumulating too much knowledge!
    @@ -328,6 +313,38 @@ const KnowledgeTree = () => { ); }; +// take • in from byond and make sure it's rendered properly +function bulletpointHelper(text: string) { + return text.replace(/•/g, '•'); +} + +// description or info text may have
    s, +// we need translate it into separate children for the tooltip to render it properly +function formatTooltipText(text: string) { + return ( + + {text.split('
    ').map((line, index) => { + const isBulletPoint = line.includes('•'); + if (isBulletPoint) { + line = line.replace(/•/g, '•'); + } + return ( + + {line} + + ); + })} +
    + ); +} + type KnowledgeNodeProps = { node: Knowledge; purchaseCategory?: ShopCategory; @@ -359,9 +376,30 @@ const KnowledgeNode = (props: KnowledgeNodeProps) => {
    + + + ) : ( + + )}
    From 535a1551b355e6708d5238c278fbb3fa807e20fe Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 05:43:07 +0000 Subject: [PATCH 327/445] Automatic changelog for PR #97251 [ci skip] --- html/changelogs/AutoChangeLog-pr-97251.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97251.yml diff --git a/html/changelogs/AutoChangeLog-pr-97251.yml b/html/changelogs/AutoChangeLog-pr-97251.yml new file mode 100644 index 000000000000..e4fe70f7dcbc --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97251.yml @@ -0,0 +1,4 @@ +author: "QuiteLiterallyAnything" +delete-after: True +changes: + - qol: "Bluespace Artillery Cannons now indicate the space they will occupy prior to deployment." \ No newline at end of file From 4706188c07188d3dad8539f8654156dd0b60708a Mon Sep 17 00:00:00 2001 From: antropod Date: Thu, 30 Jul 2026 11:49:48 +0600 Subject: [PATCH 328/445] Construction Update: Cement & Concrete (#97063) ## About The Pull Request Port from Shiptest https://github.com/shiptest-ss13/Shiptest/pull/950 by tmtmtl30. Sprites by triplezeta from original PR 4 years ago. Adds some things beside original PR. Adds **cement** and **concrete** along with the stronger **hexement** and **hexacrete** that can be used to pour walls and floors. Pouring concrete requires reinforcement structure: grille for **concrete wall,** catwalk floor for flooring and girder for **hexacrete wall**. After pouring it takes some time to harden. Hardened concrete structure can be smashed down with mining tool like pickaxe or drill. If you walk over freshly poured concrete floor it will leave a trail of footprints. While flooring is wet, it can be chiseled with set of patterns. Pouring can be done by splashing or just left-clicking with a container. It takes 10u of concrete to pour a wall, and 2u for the floor. Foam reaction (fluorosurfactant + water+ concrete) can also be used. **Pavement** is a variation of concrete flooring, made with **pavement concrete.** Pavement and hexacrete floors provide minor speed boost when walked on. Cement is a main reagent of concrete is synthesizable in a chemical lab or factory. To make cement, mix 2 parts of carbon, 2 parts of oxygen, 2 parts of hydrogen, 1 part of water and heat to 400K. Hexement is synthesized by adding stable plasma to cement. **Pavement cement** is made by mixing cement with oil. Concrete is made by adding sand and its derivatives (basalt ash, cement powder) to the cement. For each 5u of cement you need 1 sand. Hexacrete and pavement concrete are made similarly. Cement is not safe for human consumption and may cause intoxication and brain damage as well as mood change. Premade **concrete mix** can be ordered from cargo for 1000 cr, comes in 2 jerrycans of 200u and a bucket. Mixing concrete mix with water produces ready-to-use concrete. Cement mix jerrycans can be found in maintenance loot piles. Hexacrete jerrycans sometimes appear on a black market. **Wall support** is a new structure that can be used to safely deconstruct a wall, preserving equipment that is mounted on that wall. Wall supports can be constructed by using rods on an existing wall. Replacing all station walls with concrete ones is now possible. Crafting guide reflects all the structures, tiles and walls that can be made from concrete as well as recipes for making concrete. This does not include recipes for cement, but those can be found in reagent dispenser built-in help. Crafting guide recipes are placed under new category: **Construction**. image
    Figure 1: Concrete, hexacrete walls and tiles, pavement, jerrycans of concrete mix and hexacrete. Wall supports and broken walls. ## Why It's Good For The Game Adds new ways of expressing yourself as engineer. ## Changelog :cl: add: Added cement and concrete, walls, floors and pavements. Check your crafting guide under category Construction. /:cl: --------- Co-authored-by: Lucy --- code/__DEFINES/crafting.dm | 1 + code/__DEFINES/footsteps.dm | 7 + code/__DEFINES/icon_smoothing.dm | 31 +-- code/_globalvars/lists/crafting.dm | 1 + code/_globalvars/lists/maintenance_loot.dm | 1 + code/datums/components/atom_mounted.dm | 12 +- .../mood_events/generic_negative_events.dm | 5 + .../objects/effects/decals/cleanable/mess.dm | 7 + code/game/objects/structures/wall_support.dm | 88 ++++++++ code/game/turfs/closed/wall/concrete_walls.dm | 120 +++++++++++ code/game/turfs/closed/walls.dm | 27 ++- code/game/turfs/open/floor/concrete_floor.dm | 189 ++++++++++++++++++ code/modules/art/statues.dm | 4 + .../cargo/markets/market_items/misc.dm | 11 + code/modules/cargo/packs/engineering.dm | 12 ++ .../recipes/tablecraft/recipes_guide.dm | 101 ++++++++++ code/modules/mining/ores_coins.dm | 30 +++ .../chemistry/reagents/other_reagents.dm | 118 +++++++++++ .../reagents/chemistry/recipes/others.dm | 25 +++ .../reagents/reagent_containers/cups/_cup.dm | 31 +++ .../reagents/reagent_containers/jerrycan.dm | 17 ++ icons/obj/debris.dmi | Bin 22665 -> 20191 bytes icons/obj/medical/chemical.dmi | Bin 65855 -> 72442 bytes icons/obj/ore.dmi | Bin 12825 -> 5976 bytes icons/obj/structures.dmi | Bin 97824 -> 98164 bytes icons/turf/floors/concrete.dmi | Bin 0 -> 14293 bytes icons/turf/floors/hexacrete.dmi | Bin 0 -> 1949 bytes icons/turf/walls/concrete.dmi | Bin 0 -> 7883 bytes icons/turf/walls/concrete.png | Bin 0 -> 1076 bytes icons/turf/walls/concrete.png.toml | 2 + icons/turf/walls/hexacrete.dmi | Bin 0 -> 7391 bytes icons/turf/walls/hexacrete.png | Bin 0 -> 948 bytes icons/turf/walls/hexacrete.png.toml | 2 + sound/effects/footstep/concrete1.ogg | Bin 0 -> 12976 bytes sound/effects/footstep/concrete2.ogg | Bin 0 -> 10136 bytes sound/effects/footstep/concrete3.ogg | Bin 0 -> 9755 bytes sound/effects/footstep/concrete4.ogg | Bin 0 -> 10528 bytes sound/effects/footstep/concrete5.ogg | Bin 0 -> 11284 bytes tgstation.dme | 3 + .../interfaces/PersonalCrafting/constants.ts | 1 + 40 files changed, 827 insertions(+), 19 deletions(-) create mode 100644 code/game/objects/structures/wall_support.dm create mode 100644 code/game/turfs/closed/wall/concrete_walls.dm create mode 100644 code/game/turfs/open/floor/concrete_floor.dm create mode 100644 icons/turf/floors/concrete.dmi create mode 100644 icons/turf/floors/hexacrete.dmi create mode 100644 icons/turf/walls/concrete.dmi create mode 100644 icons/turf/walls/concrete.png create mode 100644 icons/turf/walls/concrete.png.toml create mode 100644 icons/turf/walls/hexacrete.dmi create mode 100644 icons/turf/walls/hexacrete.png create mode 100644 icons/turf/walls/hexacrete.png.toml create mode 100644 sound/effects/footstep/concrete1.ogg create mode 100644 sound/effects/footstep/concrete2.ogg create mode 100644 sound/effects/footstep/concrete3.ogg create mode 100644 sound/effects/footstep/concrete4.ogg create mode 100644 sound/effects/footstep/concrete5.ogg diff --git a/code/__DEFINES/crafting.dm b/code/__DEFINES/crafting.dm index 01548013fad9..6b019a3b06b7 100644 --- a/code/__DEFINES/crafting.dm +++ b/code/__DEFINES/crafting.dm @@ -111,3 +111,4 @@ #define CAT_ENTERTAINMENT "Entertainment" #define CAT_TOOLS "Tools" #define CAT_CULT "Blood Cult" +#define CAT_CONSTRUCTION "Construction" diff --git a/code/__DEFINES/footsteps.dm b/code/__DEFINES/footsteps.dm index 8dddfceac57d..195611a88fb2 100644 --- a/code/__DEFINES/footsteps.dm +++ b/code/__DEFINES/footsteps.dm @@ -8,6 +8,7 @@ #define FOOTSTEP_LAVA "lava" #define FOOTSTEP_MEAT "meat" #define FOOTSTEP_CATWALK "catwalk" +#define FOOTSTEP_CONCRETE "concrete" //barefoot sounds #define FOOTSTEP_WOOD_BAREFOOT "woodbarefoot" #define FOOTSTEP_WOOD_CLAW "woodclaw" @@ -100,6 +101,12 @@ GLOBAL_LIST_INIT(footstep, list( 'sound/effects/footstep/catwalk3.ogg', 'sound/effects/footstep/catwalk4.ogg', 'sound/effects/footstep/catwalk5.ogg'), 100, 1), + FOOTSTEP_CONCRETE = list(list( + 'sound/effects/footstep/concrete1.ogg', + 'sound/effects/footstep/concrete2.ogg', + 'sound/effects/footstep/concrete3.ogg', + 'sound/effects/footstep/concrete4.ogg', + 'sound/effects/footstep/concrete5.ogg'), 100, 1), )) //bare footsteps lists diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm index 2c57b4ccfdbb..732bad4108dc 100644 --- a/code/__DEFINES/icon_smoothing.dm +++ b/code/__DEFINES/icon_smoothing.dm @@ -132,21 +132,23 @@ DEFINE_BITFIELD(smoothing_junction, list( #define SMOOTH_GROUP_FLOOR_SIDERITE S_TURF(54) //![/turf/open/misc/asteroid/basalt/smooth/siderite] #define SMOOTH_GROUP_FLOOR_SHALE S_TURF(55) //![/turf/open/misc/asteroid/basalt/smooth/shale] #define SMOOTH_GROUP_FLOOR_BASALT S_TURF(56) //![/turf/open/misc/asteroid/basalt] +#define SMOOTH_GROUP_FLOOR_HEXACRETE S_TURF(57) ///turf/open/floor/concrete -#define SMOOTH_GROUP_CLOSED_TURFS S_TURF(57) ///turf/closed -#define SMOOTH_GROUP_MATERIAL_WALLS S_TURF(58) ///turf/closed/wall/material -#define SMOOTH_GROUP_SYNDICATE_WALLS S_TURF(59) ///turf/closed/wall/r_wall/plastitanium/syndicate, /turf/closed/indestructible/syndicate -#define SMOOTH_GROUP_HOTEL_WALLS S_TURF(60) ///turf/closed/indestructible/hotelwall -#define SMOOTH_GROUP_MINERAL_WALLS S_TURF(61) ///turf/closed/mineral, /turf/closed/indestructible -#define SMOOTH_GROUP_RED_ROCK_WALLS S_TURF(62) ///turf/closed/mineral/asteroid, /turf/closed/mineral/random/stationside/asteroid -#define SMOOTH_GROUP_SHALE_WALLS S_TURF(63) ///turf/closed/mineral/random/volcanic/shale -#define SMOOTH_GROUP_BOSS_WALLS S_TURF(64) ///turf/closed/indestructible/riveted/boss -#define SMOOTH_GROUP_SURVIVAL_TITANIUM_WALLS S_TURF(65) ///turf/closed/wall/mineral/plastitanium/survival -#define SMOOTH_GROUP_TURF_OPEN_CLIFF S_TURF(66) ///turf/open/cliff -#define SMOOTH_GROUP_HIEROPHANT S_TURF(67) ///turf/closed/indestructible/riveted/hierophant -#define SMOOTH_GROUP_PLASTINUM_WALLS S_TURF(68) ///turf/closed/indestructible/riveted/plastinum - -#define MAX_S_TURF 68 //Always match this value with the one above it. +#define SMOOTH_GROUP_CLOSED_TURFS S_TURF(58) ///turf/closed +#define SMOOTH_GROUP_MATERIAL_WALLS S_TURF(59) ///turf/closed/wall/material +#define SMOOTH_GROUP_SYNDICATE_WALLS S_TURF(60) ///turf/closed/wall/r_wall/plastitanium/syndicate, /turf/closed/indestructible/syndicate +#define SMOOTH_GROUP_HOTEL_WALLS S_TURF(61) ///turf/closed/indestructible/hotelwall +#define SMOOTH_GROUP_MINERAL_WALLS S_TURF(62) ///turf/closed/mineral, /turf/closed/indestructible +#define SMOOTH_GROUP_RED_ROCK_WALLS S_TURF(63) ///turf/closed/mineral/asteroid, /turf/closed/mineral/random/stationside/asteroid +#define SMOOTH_GROUP_SHALE_WALLS S_TURF(64) ///turf/closed/mineral/random/volcanic/shale +#define SMOOTH_GROUP_BOSS_WALLS S_TURF(65) ///turf/closed/indestructible/riveted/boss +#define SMOOTH_GROUP_SURVIVAL_TITANIUM_WALLS S_TURF(66) ///turf/closed/wall/mineral/titanium/survival +#define SMOOTH_GROUP_TURF_OPEN_CLIFF S_TURF(67) ///turf/open/cliff +#define SMOOTH_GROUP_HIEROPHANT S_TURF(68) ///turf/closed/indestructible/riveted/hierophant +#define SMOOTH_GROUP_PLASTINUM_WALLS S_TURF(69) ///turf/closed/indestructible/riveted/plastinum + + +#define MAX_S_TURF 69 //Always match this value with the one above it. #define S_OBJ(num) ("-" + #num + ",") /* /obj included */ @@ -167,6 +169,7 @@ DEFINE_BITFIELD(smoothing_junction, list( #define SMOOTH_GROUP_SURVIVAL_TITANIUM_POD S_OBJ(15) ///turf/closed/wall/mineral/plastitanium/survival/pod, /obj/machinery/door/airlock/survival_pod, /obj/structure/window/reinforced/shuttle/survival_pod #define SMOOTH_GROUP_HIERO_WALL S_OBJ(16) ///obj/effect/temp_visual/elite_tumor_wall, /obj/effect/temp_visual/hierophant/wall #define SMOOTH_GROUP_BAMBOO_WALLS S_OBJ(17) //![/turf/closed/wall/mineral/bamboo, /obj/structure/falsewall/bamboo] +#define SMOOTH_GROUP_CONCRETE_WALLS S_OBJ(18) ///turf/closed/wall/concrete #define SMOOTH_GROUP_PAPERFRAME S_OBJ(21) ///obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe diff --git a/code/_globalvars/lists/crafting.dm b/code/_globalvars/lists/crafting.dm index 72a501bc6529..be06e4795c30 100644 --- a/code/_globalvars/lists/crafting.dm +++ b/code/_globalvars/lists/crafting.dm @@ -22,4 +22,5 @@ GLOBAL_LIST_INIT(crafting_category, list( CAT_ENTERTAINMENT, CAT_TOOLS, CAT_CULT, + CAT_CONSTRUCTION, )) diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm index d70e63f29eb7..6c9575dff228 100644 --- a/code/_globalvars/lists/maintenance_loot.dm +++ b/code/_globalvars/lists/maintenance_loot.dm @@ -239,6 +239,7 @@ GLOBAL_LIST_INIT(uncommon_loot, list(//uncommon: useful items /obj/item/reagent_containers/cup/jerrycan/space_cleaner = 2, //If you can't find the janitor, the medbay might also appreciate some readymade cleaner. /obj/item/reagent_containers/cup/jerrycan/sus = 1, //what assitant wouldn't like a vibrant jug o' phlog? /obj/item/reagent_containers/cup/jerrycan/diethylamine = 1, //Im sure the botanist would appreciate it, but it is also really cool for maintenance science projects. + /obj/item/reagent_containers/cup/jerrycan/concrete_mix = 2, ) = 2, ) = 8, diff --git a/code/datums/components/atom_mounted.dm b/code/datums/components/atom_mounted.dm index 4ec4cb0224fe..469c7c940a73 100644 --- a/code/datums/components/atom_mounted.dm +++ b/code/datums/components/atom_mounted.dm @@ -49,7 +49,7 @@ var/reload = FALSE //For special effects where we explicitly want to preserve objects after change - if(flags & CHANGETURF_INHERIT_MOUNTS) + if(flags & CHANGETURF_INHERIT_MOUNTS || locate(/obj/structure/wall_support) in source) reload = TRUE //if we transforming from open to open turf we can skip deconstruction under some conditions else if(isopenturf(source) && ispath(path, /turf/open)) @@ -73,7 +73,12 @@ SIGNAL_HANDLER PRIVATE_PROC(TRUE) - drop_wallmount() + if (istype(source, /obj/structure/wall_support)) + var/obj/target = parent + if(!target.find_and_mount_on_atom()) + drop_wallmount() + else + drop_wallmount() /// If we get dragged from our wall (by a singulo for instance) we should deconstruct /datum/component/atom_mounted/proc/on_move(datum/source, atom/old_loc, dir, forced, list/old_locs) @@ -148,6 +153,7 @@ /obj/structure/window, /obj/structure/fence, /obj/structure/falsewall, + /obj/structure/wall_support, ) return attachables @@ -183,7 +189,7 @@ else var/list/obj/attachables = get_mountable_objects() for(var/obj/attachable in target) - if(is_type_in_list(attachable, attachables)) + if(is_type_in_list(attachable, attachables) && !QDELING(attachable)) attachable_atom = attachable break if(attachable_atom) diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 345abcb5d5b1..a73b118d2437 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -660,3 +660,8 @@ description = "I hear a voice whispering, and I don't like what it says." mood_change = -3 timeout = 30 SECONDS + +/datum/mood_event/cement + description = span_warning("I was forced to eat cement...") + mood_change = -6 + timeout = 4 MINUTES diff --git a/code/game/objects/effects/decals/cleanable/mess.dm b/code/game/objects/effects/decals/cleanable/mess.dm index 57a58183b344..6d91ed525888 100644 --- a/code/game/objects/effects/decals/cleanable/mess.dm +++ b/code/game/objects/effects/decals/cleanable/mess.dm @@ -405,3 +405,10 @@ GLOBAL_LIST_EMPTY(nebula_vomits) /obj/effect/decal/cleanable/can_bits name = "shredded can" desc = "This story doesn't hold water anymore." + +/obj/effect/decal/cleanable/concrete_dust + name = "concrete dust" + desc = "Fine powder generated by breaking concrete structures." + icon = 'icons/obj/debris.dmi' + icon_state = "concrete_dust" + beauty = -20 diff --git a/code/game/objects/structures/wall_support.dm b/code/game/objects/structures/wall_support.dm new file mode 100644 index 000000000000..69321121f154 --- /dev/null +++ b/code/game/objects/structures/wall_support.dm @@ -0,0 +1,88 @@ +/** + * Wall supports are designed to help you safely deconstruct walls, without fear + * of all wall-mounted equipment like APCs or light fixtures falling off. + * All of the logic for handling wall-mounted equipment is in `atom_mounted.dm` + * (/datum/component/atom_mounted) +**/ +/obj/structure/wall_support + desc = "A temporary support structure for wall-mounted equipment. Allows you to safely deconstruct walls." + name = "Wall Support" + icon = 'icons/obj/structures.dmi' + icon_state = "wall_support" + base_icon_state = "wall_support" + layer = WALL_OBJ_LAYER - 0.1 + density = FALSE + anchored = TRUE + pass_flags_self = NONE + obj_flags = CONDUCTS_ELECTRICITY | CAN_BE_HIT | IGNORE_DENSITY + pressure_resistance = 5*ONE_ATMOSPHERE + armor_type = /datum/armor/structure_grille + max_integrity = 50 + integrity_failure = 0.4 + custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT) + var/rods_type = /obj/item/stack/rods + var/rods_amount = 2 + +/obj/structure/wall_support/Initialize(mapload) + . = ..() + register_context() + +/obj/structure/wall_support/examine(mob/user) + . = ..() + if(resistance_flags & INDESTRUCTIBLE) + return + if(anchored) + . += span_notice("It's secured in place with [EXAMINE_HINT("screws")]. The rods look like they could be [EXAMINE_HINT("cut")] through.") + else + . += span_notice("The anchoring screws are [EXAMINE_HINT("unscrewed")]. The rods look like they could be [EXAMINE_HINT("cut")] through.") + +/obj/structure/wall_support/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(held_item?.tool_behaviour == TOOL_WIRECUTTER) + context[SCREENTIP_CONTEXT_RMB] = "Deconstruct" + return CONTEXTUAL_SCREENTIP_SET + if(held_item?.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Unanchor" : "Anchor"]" + return CONTEXTUAL_SCREENTIP_SET + return . + +/obj/structure/wall_support/wirecutter_act_secondary(mob/living/user, obj/item/tool) + add_fingerprint(user) + tool.play_tool_sound(src, 100) + deconstruct(TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/structure/wall_support/screwdriver_act(mob/living/user, obj/item/tool) + if(!isturf(loc)) + return FALSE + add_fingerprint(user) + if(!tool.use_tool(src, user, 0, volume=100)) + return FALSE + set_anchored(!anchored) + user.visible_message(span_notice("[user] [anchored ? "fastens" : "unfastens"] [src]."), \ + span_notice("You [anchored ? "fasten [src] to" : "unfasten [src] from"] the floor.")) + return ITEM_INTERACT_SUCCESS + +/obj/structure/wall_support/play_attack_sound(...) + return call(src, /obj/structure/grille::play_attack_sound())(arglist(args)) + +/obj/structure/wall_support/atom_deconstruct(disassembled = TRUE) + var/obj/rods = new rods_type(drop_location(), rods_amount) + transfer_fingerprints_to(rods) + +/obj/structure/wall_support/atom_break() + . = ..() + if(broken) + return + atom_integrity = 20 + broken = TRUE + rods_amount = 1 + var/obj/item/dropped_rods = new rods_type(drop_location(), rods_amount) + transfer_fingerprints_to(dropped_rods) + update_appearance() + +/obj/structure/wall_support/should_atmos_process(datum/gas_mixture/air, exposed_temperature) + return exposed_temperature > T0C + 1500 && !broken + +/obj/structure/wall_support/atmos_expose(datum/gas_mixture/air, exposed_temperature) + take_damage(1, BURN, 0, 0) diff --git a/code/game/turfs/closed/wall/concrete_walls.dm b/code/game/turfs/closed/wall/concrete_walls.dm new file mode 100644 index 000000000000..f191eed93ccd --- /dev/null +++ b/code/game/turfs/closed/wall/concrete_walls.dm @@ -0,0 +1,120 @@ +/turf/closed/wall/concrete + name = "concrete wall" + desc = "A thick wall made of concrete, poured around steel supports." + icon = 'icons/turf/walls/concrete.dmi' + icon_state = "concrete-0" + base_icon_state = "concrete" + explosive_resistance = 2 + rad_insulation = RAD_MEDIUM_INSULATION + rust_resistance = RUST_RESISTANCE_REINFORCED + + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_CONCRETE_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + canSmoothWith = SMOOTH_GROUP_CONCRETE_WALLS + hardness = 30 + + sheet_type = /obj/item/stack/ore/glass/concrete_powder + sheet_amount = 2 + girder_type = /obj/structure/grille + break_sound = 'sound/effects/break_stone.ogg' + + /// Time to fully cure + var/time_to_harden = 60 SECONDS + /// fraction ranging from 0 to 1 -- 0 is fully soft, 1 is fully hardened + /// don't change this in subtypes unless you want them to spawn in soft on maps + var/harden_lvl = 1 + +/turf/closed/wall/concrete/Initialize(mapload) + . = ..() + check_harden() + +/turf/closed/wall/concrete/deconstruction_hints(mob/user) + . = list() + . += span_notice("[p_They()] look[p_s()] like you could smash [p_them()] with [tool_behaviour_name(TOOL_MINING)].") + switch(harden_lvl) + if(0.8 to 0.99) + . += "[p_They()] look[p_s()] nearly dry." + if(0.4 to 0.8) + . += "[p_They()] look[p_s()] a little wet." + if(0 to 0.4) + . += "[p_They()] look[p_s()] freshly poured." + return + +/turf/closed/wall/concrete/try_clean(obj/item/W, mob/living/user) + // You can't weld dents on concrete wall + return FALSE + +/turf/closed/wall/concrete/try_decon(obj/item/I, mob/user) + if (I.tool_behaviour == TOOL_MINING) + to_chat(user, span_notice("You begin breaking the wall...")) + if(I.use_tool(src, user, slicing_duration, volume=100)) + // break_prob decreases linearly from 100 to hardness as it cures + var/break_prob = hardness + (1 - harden_lvl) * (100 - hardness) + if(prob(break_prob)) + to_chat(user, span_notice("You break the wall.")) + dismantle_wall() + new /obj/effect/decal/cleanable/concrete_dust(src) + else + add_dent(WALL_DENT_HIT) + playsound(src, break_sound, 50, TRUE) + + return TRUE + + return FALSE + +/turf/closed/wall/concrete/break_wall() + var/atom/girder = ..() + girder.atom_break() + return girder + +/turf/closed/wall/concrete/update_icon(updates=ALL) + . = ..() + if(!.) + return + remove_filter("harden") + if(harden_lvl == 1) + return + + var/offset = (1 - harden_lvl) * 0.4 + var/base = 1 - offset + var/col_filter = list(base, 0, 0, 0, base, 0, 0, 0, base, offset, offset, offset) + add_filter("harden", 1, color_matrix_filter(col_filter, FILTER_COLOR_RGB)) + return + +/turf/closed/wall/concrete/proc/check_harden() + harden_lvl = clamp(harden_lvl, 0, 1) + if(harden_lvl < 1) + START_PROCESSING(SSobj, src) + update_icon(UPDATE_ICON_STATE) + +/turf/closed/wall/concrete/process(seconds_per_tick) + var/time_per_tick = seconds_per_tick SECONDS + harden_lvl = min(harden_lvl + (time_per_tick / time_to_harden), 1) + if(harden_lvl == 1) + STOP_PROCESSING(SSobj, src) + update_icon(UPDATE_ICON_STATE) + +/turf/closed/wall/concrete/reinforced + name = "hexacrete wall" + desc = "A thick wall made of steel-reinforced hexacrete. Sturdier than a normal concrete wall." + icon = 'icons/turf/walls/hexacrete.dmi' + icon_state = "hexacrete-0" + base_icon_state = "hexacrete" + + hardness = 15 + explosive_resistance = 3 + rad_insulation = RAD_HEAVY_INSULATION + rust_resistance = RUST_RESISTANCE_REINFORCED + girder_type = /obj/structure/girder + + time_to_harden = 60 SECONDS + +/turf/closed/wall/concrete/reinforced/rcd_vals(...) + return call(src, /turf/closed/wall/r_wall::rcd_vals())(arglist(args)) + +/turf/closed/wall/concrete/reinforced/rcd_act(...) + return call(src, /turf/closed/wall/r_wall::rcd_act())(arglist(args)) + +/turf/closed/wall/concrete/reinforced/rust_turf(magic = FALSE) + return call(src, /turf/closed/wall/r_wall::rust_turf())(magic) + diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm index 2501b3767b38..7d90ad3c4da1 100644 --- a/code/game/turfs/closed/walls.dm +++ b/code/game/turfs/closed/walls.dm @@ -40,6 +40,8 @@ var/girder_state = GIRDER_NORMAL /// How long this wall takes to make by using its sheet type on a girder. var/make_delay = 4 SECONDS + /// Sound played when wall is dismantled + var/break_sound = 'sound/items/tools/welder.ogg' /turf/closed/wall/Initialize(mapload) . = ..() @@ -64,6 +66,9 @@ if((initial(smoothing_flags) & SMOOTH_DIAGONAL_CORNERS) && held_item.tool_behaviour == TOOL_WRENCH) context[SCREENTIP_CONTEXT_LMB] = "Adjust Wall Corner" return CONTEXTUAL_SCREENTIP_SET + else if(istype(held_item, /obj/structure/wall_support::rods_type)) + context[SCREENTIP_CONTEXT_LMB] = "Build Wall Support" + return CONTEXTUAL_SCREENTIP_SET /turf/closed/wall/mouse_drop_receive(atom/dropping, mob/user, params) //Adds the component only once. We do it here & not in Initialize() because there are tons of walls & we don't want to add to their init times @@ -94,7 +99,7 @@ if(devastated) devastate_wall() else - playsound(src, 'sound/items/tools/welder.ogg', 100, TRUE) + playsound(src, break_sound, 100, TRUE) var/newgirder = break_wall() if(newgirder) //maybe we don't /want/ a girder! transfer_fingerprints_to(newgirder) @@ -202,7 +207,7 @@ add_fingerprint(user) //the istype cascade has been spread among various procs for easy overriding - if(try_clean(tool, user) || try_decon(tool, user)) + if(try_clean(tool, user) || try_decon(tool, user) || try_build_support(tool, user)) return ITEM_INTERACT_SUCCESS return NONE @@ -239,6 +244,24 @@ return FALSE +///Helper for building wall_support on top of desired wall +/turf/closed/wall/proc/try_build_support(obj/item/I, mob/user) + if(!isstack(I) || !istype(I, /obj/structure/wall_support::rods_type)) + return FALSE + var/obj/item/stack/rods = I + var/amount_needed = /obj/structure/wall_support::rods_amount + if(rods.get_amount() < amount_needed) + to_chat(user, span_warning("You need at least [amount_needed] rods for that!")) + return FALSE + to_chat(user, span_notice("You start constructing wall support...")) + if(do_after(user, 2 SECONDS, target = src)) + var/obj/structure/wall_support/WS = new(src) + rods.use(amount_needed) + rods.transfer_fingerprints_to(WS) + to_chat(user, span_notice("You place [WS] on [src].")) + return TRUE + return FALSE + /turf/closed/wall/singularity_pull(atom/singularity, current_size) ..() wall_singularity_pull(current_size) diff --git a/code/game/turfs/open/floor/concrete_floor.dm b/code/game/turfs/open/floor/concrete_floor.dm new file mode 100644 index 000000000000..06d343c1747c --- /dev/null +++ b/code/game/turfs/open/floor/concrete_floor.dm @@ -0,0 +1,189 @@ +/turf/open/floor/concrete + name = "concrete floor" + desc = "Cold, bare concrete flooring." + icon = 'icons/turf/floors/concrete.dmi' + damaged_dmi = 'icons/turf/floors/concrete.dmi' + icon_state = "conc_smooth_1" + base_icon_state = "conc_smooth" + baseturfs = /turf/open/floor/plating + floor_tile = null + footstep = FOOTSTEP_CONCRETE + barefootstep = FOOTSTEP_HARD_BAREFOOT + clawfootstep = FOOTSTEP_HARD_CLAW + heavyfootstep = FOOTSTEP_GENERIC_HEAVY + leave_footprints = TRUE + + var/smash_time = 3 SECONDS + /// Time to fully cure + var/time_to_harden = 60 SECONDS + /// fraction ranging from 0 to 1 -- 0 is fully soft, 1 is fully hardened + /// don't change this in subtypes unless you want them to spawn in soft on maps + var/harden_lvl = 1 + var/has_variation = TRUE + + var/shape_types = list( + /turf/open/floor/concrete, + /turf/open/floor/concrete/slab_1, + /turf/open/floor/concrete/slab_2, + /turf/open/floor/concrete/slab_3, + /turf/open/floor/concrete/slab_4, + /turf/open/floor/concrete/tiles + ) + +/turf/open/floor/concrete/Initialize(mapload) + . = ..() + if(has_variation) + icon_state = "[base_icon_state]_[rand(1,4)]" + check_harden() + update_icon(UPDATE_ICON_STATE) + +/turf/open/floor/concrete/broken_states() + return list("concdam_1", "concdam_2", "concdam_3", "concdam_4") + +/turf/open/floor/concrete/examine(mob/user) + . = ..() + . += span_notice("[p_They()] look[p_s()] like you could smash [p_them()] with [tool_behaviour_name(TOOL_MINING)].") + switch(harden_lvl) + if(0.8 to 0.99) + . += "[p_They()] look[p_s()] nearly dry." + if(0.4 to 0.8) + . += "[p_They()] look[p_s()] a little wet." + if(0 to 0.4) + . += "[p_They()] look[p_s()] freshly poured." + return + +/turf/open/floor/concrete/add_footprint(mob/living/carbon/human/walker, movement_direction) + if (harden_lvl <= 0.8) + ..() + +/turf/open/floor/concrete/clear_footprints() + if (harden_lvl <= 0.8) + ..() + else + update_appearance() + +/turf/open/floor/concrete/attackby(obj/item/I, mob/living/user, list/modifiers) + . = ..() + if(.) + return + if(I.tool_behaviour == TOOL_MINING) + to_chat(user, span_notice("You start smashing [src]...")) + var/adj_time = (broken || burnt) ? smash_time/2 : smash_time + if(I.use_tool(src, user, adj_time, volume=30)) + new /obj/effect/decal/cleanable/concrete_dust(src) + to_chat(user, span_notice("You break [src].")) + playsound(src, 'sound/effects/break_stone.ogg', 30, TRUE) + remove_tile(silent=TRUE) + return TRUE + return FALSE + +/turf/open/floor/concrete/try_replace_tile(obj/item/stack/tile/T, mob/user, list/modifiers) + return FALSE + +/turf/open/floor/concrete/pry_tile(obj/item/I, mob/user, silent) + return FALSE + +/turf/open/floor/concrete/update_icon(updates=ALL) + . = ..() + if(!.) + return + remove_filter("harden") + if(harden_lvl == 1) + return + + var/offset = (1 - harden_lvl) * 0.3 + var/base = 1 - offset + var/col_filter = list(base,0,0, 0,base,0, 0,0,base, offset, offset, offset) + add_filter("harden", 1, color_matrix_filter(col_filter, FILTER_COLOR_RGB)) + return + +/turf/open/floor/concrete/proc/check_harden() + harden_lvl = clamp(harden_lvl, 0, 1) + if(harden_lvl < 1) + START_PROCESSING(SSobj, src) + update_icon(UPDATE_ICON_STATE) + +/turf/open/floor/concrete/process(seconds_per_tick) + var/time_per_tick = seconds_per_tick SECONDS + harden_lvl = min(harden_lvl + (time_per_tick / time_to_harden), 1) + if(harden_lvl == 1) + STOP_PROCESSING(SSobj, src) + update_icon(UPDATE_ICON_STATE) + +/turf/open/floor/concrete/proc/handle_shape(mob/user) + if(harden_lvl >= 0.8) + return FALSE + var/list/opts = list() + + for(var/turf/open/floor/concrete/conc as anything in shape_types) + opts[conc] = image(icon = initial(conc.icon), icon_state = initial(conc.icon_state)) + + var/choice = show_radial_menu( + user, + src, + opts, + uniqueid = "concmenu_[REF(user)]", + radius = 48, + custom_check = CALLBACK(src, PROC_REF(check_menu), user), + require_near = TRUE + ) + if(!choice) + return FALSE + to_chat(user, "You reshape [src].") + var/old_harden = harden_lvl + var/turf/open/floor/concrete/newconc = ChangeTurf(choice, flags = CHANGETURF_INHERIT_AIR) + newconc.harden_lvl = old_harden + newconc.check_harden() + newconc.update_appearance() + return TRUE + +/turf/open/floor/concrete/proc/check_menu(mob/living/user) + if(!istype(user)) + return FALSE + if(user.incapacitated || !user.Adjacent(src)) + return FALSE + return TRUE + +/turf/open/floor/concrete/slab_1 + icon_state = "conc_slab_1" + has_variation = FALSE + +/turf/open/floor/concrete/slab_2 + icon_state = "conc_slab_2" + has_variation = FALSE + +/turf/open/floor/concrete/slab_3 + icon_state = "conc_slab_3" + has_variation = FALSE + +/turf/open/floor/concrete/slab_4 + icon_state = "conc_slab_4" + has_variation = FALSE + +/turf/open/floor/concrete/tiles + icon_state = "conc_tiles" + has_variation = FALSE + +/turf/open/floor/concrete/reinforced + name = "hexacrete floor" + desc = "Reinforced hexacrete tiling." + icon = 'icons/turf/floors/hexacrete.dmi' + icon_state = "hexacrete-0" + base_icon_state = "hexacrete" + has_variation = FALSE + smoothing_flags = SMOOTH_BITMASK + smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_OPEN_FLOOR + SMOOTH_GROUP_FLOOR_HEXACRETE + canSmoothWith = SMOOTH_GROUP_FLOOR_HEXACRETE + + smash_time = 8 SECONDS + time_to_harden = 90 SECONDS + shape_types = list(/turf/open/floor/concrete/reinforced) + slowdown = -0.3 + +/turf/open/floor/concrete/pavement + name = "strip of pavement" + desc = "Hot, coarse pavement. Preferred for roadways, as vehicles are generally quieter driven on this than on traditional concrete." + icon_state = "pavement_1" + base_icon_state = "pavement" + shape_types = list(/turf/open/floor/concrete/pavement) + slowdown = -0.2 diff --git a/code/modules/art/statues.dm b/code/modules/art/statues.dm index a4b2807ff6c1..9f9dae774cbc 100644 --- a/code/modules/art/statues.dm +++ b/code/modules/art/statues.dm @@ -375,6 +375,10 @@ Moving interrupts prepared_block.set_target(interacting_with, user) return ITEM_INTERACT_SUCCESS + else if(istype(interacting_with, /turf/open/floor/concrete)) + var/turf/open/floor/concrete/concrete_floor = interacting_with + return concrete_floor.handle_shape(user) + return NONE // We aim at something distant. diff --git a/code/modules/cargo/markets/market_items/misc.dm b/code/modules/cargo/markets/market_items/misc.dm index 538d40bf24a9..323b235263fe 100644 --- a/code/modules/cargo/markets/market_items/misc.dm +++ b/code/modules/cargo/markets/market_items/misc.dm @@ -239,3 +239,14 @@ item = /obj/item/organ/cyberimp/chest/nutriment/black_market stock_max = 2 availability_prob = 60 + +/datum/market_item/misc/hexacrete + name = "Jerrycan of Hexacrete" + desc = "Need to make a blacksite in a jiffy? Skip the fuss with this 200u jerrycan of hexacrete!" + item = /obj/item/reagent_containers/cup/jerrycan/hexacrete + + price_min = CARGO_CRATE_VALUE * 3 + price_max = CARGO_CRATE_VALUE * 5 + stock_min = 2 + stock_max = 5 + availability_prob = 100 diff --git a/code/modules/cargo/packs/engineering.dm b/code/modules/cargo/packs/engineering.dm index eccb93172daf..dc12656aaeb5 100644 --- a/code/modules/cargo/packs/engineering.dm +++ b/code/modules/cargo/packs/engineering.dm @@ -344,3 +344,15 @@ contains = list(/obj/item/golfcart_kit = 1, /obj/item/key/golfcart = 2, /obj/item/stock_parts/power_store/cell/lead = 1) crate_name = "golf cart parts kit" crate_type = /obj/structure/closet/crate/engineering + +/datum/supply_pack/engineering/concrete_mix + name = "Concrete Mix" + desc = "Feeling lazy? Need a structure and quick? Use near-instant concrete mix! Just add water." + cost = CARGO_CRATE_VALUE * 5 + access_view = ACCESS_ENGINEERING + contains = list( + /obj/item/reagent_containers/cup/jerrycan/concrete_mix = 2, + /obj/item/reagent_containers/cup/bucket = 1, + ) + crate_name = "concrete mix crate" + crate_type = /obj/structure/closet/crate/engineering diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_guide.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_guide.dm index 5cbfa532e965..53f1c876b5a4 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_guide.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_guide.dm @@ -29,6 +29,107 @@ crafting_flags = parent_type::crafting_flags | CRAFT_MUST_BE_LEARNED steps = list("Use Twisted Construction on iron") +// Things related to cement and concrete + +/datum/crafting_recipe/concrete_wall + result = /datum/reagent/concrete::wall_type + reqs = list( + /obj/structure/grille = 1, + /datum/reagent/concrete = /datum/reagent/concrete::units_per_wall, + ) + category = CAT_CONSTRUCTION + non_craftable = TRUE + steps = list("Pour concrete over a grille") + +/datum/crafting_recipe/concrete_wall/hexacrete + result = /datum/reagent/concrete/hexacrete::wall_type + reqs = list( + /obj/structure/girder = 1, + /datum/reagent/concrete/hexacrete = /datum/reagent/concrete/hexacrete::units_per_wall, + ) + category = CAT_CONSTRUCTION + non_craftable = TRUE + steps = list("Pour hexacrete over a girder") + +/datum/crafting_recipe/concrete_tile + result = /turf/open/floor/concrete + reqs = list( + /turf/open/floor/catwalk_floor = 1, + /datum/reagent/concrete = /datum/reagent/concrete::units_per_floor, + ) + category = CAT_CONSTRUCTION + non_craftable = TRUE + steps = list("Pour concrete onto a catwalk floor") + +/datum/crafting_recipe/concrete_tile/tiles + result = /turf/open/floor/concrete/tiles + reqs = list( + /turf/open/floor/concrete = 1, + /obj/item/chisel = 1 + ) + category = CAT_CONSTRUCTION + non_craftable = TRUE + steps = list("Form the pattern on a wet tile") + +/datum/crafting_recipe/concrete_tile/reinforced + result = /turf/open/floor/concrete/reinforced + reqs = list( + /turf/open/floor/catwalk_floor = 1, + /datum/reagent/concrete/hexacrete = /datum/reagent/concrete/hexacrete::units_per_floor, + ) + category = CAT_CONSTRUCTION + non_craftable = TRUE + steps = list("Pour hexacrete onto a catwalk floor") + +/datum/crafting_recipe/concrete_tile/pavement + result = /turf/open/floor/concrete/pavement + reqs = list( + /turf/open/floor/catwalk_floor = 1, + /datum/reagent/concrete/pavement = /datum/reagent/concrete/pavement::units_per_floor, + ) + category = CAT_CONSTRUCTION + non_craftable = TRUE + steps = list("Pour pavement concrete onto a catwalk floor") + +/datum/crafting_recipe/concrete + result = /datum/reagent/concrete + reqs = list( + /datum/reagent/cement = /datum/reagent/cement::units_per_aggregate, + /obj/item/stack/ore/glass = 1 + ) + category = CAT_CONSTRUCTION + non_craftable = TRUE + steps = list("Add sand to the cement") + +/datum/crafting_recipe/concrete/hexacrete + result = /datum/reagent/concrete/hexacrete + reqs = list( + /datum/reagent/cement/hexement = /datum/reagent/cement/hexement::units_per_aggregate, + /obj/item/stack/ore/glass = 1 + ) + category = CAT_CONSTRUCTION + non_craftable = TRUE + steps = list("Add sand to the hexement") + +/datum/crafting_recipe/concrete/pavement + result = /datum/reagent/concrete/pavement + reqs = list( + /datum/reagent/cement/pavement = /datum/reagent/cement/pavement::units_per_aggregate, + /obj/item/stack/ore/glass = 1 + ) + category = CAT_CONSTRUCTION + non_craftable = TRUE + steps = list("Add sand to the road mix") + +/datum/crafting_recipe/wall_support + result = /obj/structure/wall_support + reqs = list( + /obj/structure/wall_support::rods_type = /obj/structure/wall_support::rods_amount + ) + category = CAT_CONSTRUCTION + non_craftable = TRUE + steps = list("Use rods on the wall") + // Food reactions /datum/crafting_recipe/food/reaction diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index bb74d27ab139..3e3d879a7a6a 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -172,6 +172,29 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ return TRUE return FALSE +/obj/item/stack/ore/glass/interact_with_atom(atom/target, mob/living/user, list/modifiers) + . = ..() + if (.) + return + + if(!target.is_dunkable()) + return NONE + + var/datum/reagent/cement/cement = target.reagents?.has_reagent(/datum/reagent/cement, check_subtypes=TRUE) + if (!cement) + return NONE + + var/sand_to_use = min(floor(cement.volume / cement.units_per_aggregate), amount) + if (!sand_to_use) + to_chat(user, span_warning("You don't have enough [cement.name] to mix with [src].")) + return ITEM_INTERACT_BLOCKING + + use(sand_to_use) + target.reagents.convert_reagent(cement.type, cement.concrete_type, conversion_volume = sand_to_use * cement.units_per_aggregate) + user.visible_message(span_warning("[user] slips something into [target]!"), span_notice("You dissolve [src] in [target]."), null, 2) + playsound(target, 'sound/effects/bubbles/bubbles.ogg', 50, TRUE) + return ITEM_INTERACT_SUCCESS + /obj/item/stack/ore/glass/thirty amount = 30 @@ -189,6 +212,13 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ mine_experience = 0 merge_type = /obj/item/stack/ore/glass/siderite +/obj/item/stack/ore/glass/concrete_powder + name = "concrete powder" + icon_state = "concrete_powder" + singular_name = "concrete powder pile" + mine_experience = 0 + merge_type = /obj/item/stack/ore/glass/concrete_powder + /obj/item/stack/ore/plasma name = "plasma ore" icon_state = "plasma" diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index f8dfc05b3b36..0e4a9618b386 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -3612,3 +3612,121 @@ exposed_mob.Stun(4 SECONDS) exposed_mob.petrify(reac_volume * reagent_to_time_conversion) + +/datum/reagent/cement + name = "Cement" + description = "A sophisticated binding agent used to produce concrete." + color = "#c4c0bc" + taste_description = "cement" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + randomized_spawns = REAGENT_SPAWN_ALL_RANDOM_SPAWNS + var/potency = 2 + var/concrete_type = /datum/reagent/concrete + var/units_per_aggregate = 5 + +/datum/reagent/cement/expose_mob(mob/living/exposed_mob, methods, reac_volume, show_message, touch_protection) + . = ..() + if (!(methods & INGEST)) + return + exposed_mob.add_mood_event("cement", /datum/mood_event/cement) + +/datum/reagent/cement/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, metabolization_ratio) + . = ..() + var/rate = potency * seconds_per_tick * metabolization_ratio + var/need_mob_update = 0 + + if(prob(min(current_cycle/2, 5))) + need_mob_update += affected_mob.adjust_tox_loss(rate) + + if(prob(min(current_cycle/4, 10))) + need_mob_update += affected_mob.adjust_organ_loss(ORGAN_SLOT_BRAIN, rate) + + if(need_mob_update) + return UPDATE_MOB_HEALTH + +/datum/reagent/cement/hexement + name = "Hexement" + description = "An advanced, space-age binding agent used to produce reinforced concrete." + color = "#969390" + potency = 4 + concrete_type = /datum/reagent/concrete/hexacrete + units_per_aggregate = 2 + +/datum/reagent/cement/pavement + name = "Pavement Cement" + description = "A mix of cement and asphalt. Looks less tasty than normal cement." + color = "#5c6361" + potency = 3 + concrete_type = /datum/reagent/concrete/pavement + +/datum/reagent/concrete + name = "Concrete" + description = "A mix of cement and aggregate, commonly used as a bulk building material." + color = "#a8988a" + taste_description = "rocks" + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + randomized_spawns = REAGENT_SPAWN_ALL_RANDOM_SPAWNS + var/units_per_wall = 10 + var/units_per_floor = 2 + var/turf/closed/wall/concrete/wall_type = /turf/closed/wall/concrete + var/turf/open/floor/concrete/floor_type = /turf/open/floor/concrete + +/datum/reagent/concrete/expose_atom(atom/exposed_atom, reac_volume, methods) + . = ..() + // VAPOR (foam, spray), TOUCH (splashing) allowed, SMOKE_MACHINE (TOUCH|INHALE) not allowed + if(!((methods & (TOUCH|INHALE)) == TOUCH) && !(methods & VAPOR)) + return + var/girder_type = initial(wall_type.girder_type) + if(istype(exposed_atom, girder_type)) + return concify_girder(exposed_atom, volume) + if(istype(exposed_atom, /turf/open/floor/catwalk_floor)) + return concify_catwalk(exposed_atom, volume) + return + +/datum/reagent/concrete/proc/pour_amount(atom/target, mob/user) + if(istype(target, initial(wall_type.girder_type))) + return units_per_wall + else if(istype(target, /turf/open/floor/catwalk_floor)) + return units_per_floor + return 0 + +/datum/reagent/concrete/proc/concify_girder(obj/exposed_obj, volume) + if(volume < units_per_wall) + return FALSE + var/turf/open/wall_turf = get_turf(exposed_obj) + if(!istype(wall_turf)) + return FALSE + var/turf/closed/wall/concrete/wall = wall_turf.place_on_top(wall_type) + exposed_obj.transfer_fingerprints_to(wall) + wall.harden_lvl = 0 + wall.check_harden() + qdel(exposed_obj) + return TRUE + +/datum/reagent/concrete/proc/concify_catwalk(turf/open/floor/catwalk_floor/catwalk, volume) + if(volume < units_per_floor) + return FALSE + var/turf/open/floor/plating = catwalk.make_plating() + var/turf/open/floor/concrete/concrete_floor = plating.place_on_top(floor_type, flags=CHANGETURF_INHERIT_AIR) + concrete_floor.harden_lvl = 0 + concrete_floor.check_harden() + return TRUE + +/datum/reagent/concrete/hexacrete + name = "Hexacrete" + description = "Made with fortified cement, this mix of binder and aggregate is a useful, sturdy building material." + color = "#7b6e60" + wall_type = /turf/closed/wall/concrete/reinforced + floor_type = /turf/open/floor/concrete/reinforced + +/datum/reagent/concrete/pavement + name = "Pavement Concrete" + description = "Road surface, blacktop, asphalt concrete - whatever you call it, it's the most common material used in constructing runways for ships and roadways for vehicles." + color = "#3f4543" + floor_type = /turf/open/floor/concrete/pavement + +/datum/reagent/concrete_mix + name = "Concrete Mix" + description = "Pre-made concrete mix. Ideal for lazy engineers." + color = "#c4c0bc" + taste_description = "chalky concrete" diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm index 5a0f98c41180..fd6a778237d3 100644 --- a/code/modules/reagents/chemistry/recipes/others.dm +++ b/code/modules/reagents/chemistry/recipes/others.dm @@ -1164,3 +1164,28 @@ required_reagents = list(/datum/reagent/consumable/nutriment/protein = 1) required_catalysts = list(/datum/reagent/toxin/carnivorousblood = 1) reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE + +/datum/chemical_reaction/cement + results = list(/datum/reagent/cement = 6) + required_reagents = list(/datum/reagent/carbon = 2, /datum/reagent/hydrogen = 2, /datum/reagent/oxygen = 2, /datum/reagent/water = 1) + required_temp = 400 + mix_message = "The mixture boils off a grey vapor..."//The water boils off, leaving the cement + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE | REACTION_TAG_OTHER + +/datum/chemical_reaction/hexement + results = list(/datum/reagent/cement/hexement = 1) + required_reagents = list(/datum/reagent/cement = 6, /datum/reagent/stable_plasma = 1) + required_temp = 400 + mix_message = "The mixture rapidly condenses and darkens in color..." + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE | REACTION_TAG_OTHER + +/datum/chemical_reaction/pavement + results = list(/datum/reagent/cement/pavement = 2) + required_reagents = list(/datum/reagent/cement = 1, /datum/reagent/fuel/oil = 1) + mix_message = "The mixture mixing suddenly reminds you of cramped urban worlds." + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE | REACTION_TAG_OTHER + +/datum/chemical_reaction/quick_concrete + results = list(/datum/reagent/concrete = 5) + required_reagents = list(/datum/reagent/concrete_mix = 5, /datum/reagent/water = 5) + reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE | REACTION_TAG_OTHER diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm index 8b2cf23b9d5c..adef7f686dd2 100644 --- a/code/modules/reagents/reagent_containers/cups/_cup.dm +++ b/code/modules/reagents/reagent_containers/cups/_cup.dm @@ -189,8 +189,39 @@ if(isliving(target)) return try_drink(target, user) + if(is_drainable()) + return pour_concrete(target, user) + return NONE +/// QoL helper for pouring concrete directly from container just by clicking. +/// Easier than splashing onto turf or object, but requires a little delay. +/obj/item/reagent_containers/cup/proc/pour_concrete(atom/target, mob/living/user) + var/datum/reagent/concrete/concrete = reagents.has_reagent(/datum/reagent/concrete, check_subtypes=TRUE) + if (!concrete) + return NONE + var/pour_amount = concrete.pour_amount(target, user) + if (!pour_amount) + return NONE + if (concrete.volume < pour_amount) + user.balloon_alert(user, "not enough to pour") + return ITEM_INTERACT_FAILURE + user.balloon_alert(user, "pouring...") + user.visible_message( + span_notice("[user] starts pouring concrete onto \a [target]."), + span_notice("You start pouring concrete onto \a [target]..."), + ) + if (!do_after(user, 1 SECONDS, target=target)) + user.balloon_alert(user, "interrupted") + return ITEM_INTERACT_FAILURE + if(concrete.volume < pour_amount) // check if volume has changed during do_after + user.balloon_alert(user, "not enough to pour") + return ITEM_INTERACT_FAILURE + playsound(src, 'sound/effects/slosh.ogg', 25, TRUE) + concrete.expose_atom(target, pour_amount, TOUCH) + reagents.remove_reagent(concrete.type, pour_amount) + return ITEM_INTERACT_SUCCESS + /obj/item/reagent_containers/cup/interact_with_atom_secondary(atom/target, mob/living/user, list/modifiers) . = ..() if(. & ITEM_INTERACT_ANY_BLOCKER) diff --git a/code/modules/reagents/reagent_containers/jerrycan.dm b/code/modules/reagents/reagent_containers/jerrycan.dm index b92dc7ec85c9..c2975e6ebe26 100644 --- a/code/modules/reagents/reagent_containers/jerrycan.dm +++ b/code/modules/reagents/reagent_containers/jerrycan.dm @@ -11,6 +11,8 @@ #define LABEL_ROBUST_HARVEST "robust" #define LABEL_LEFT_4_ZED "l4z" #define LABEL_SPACE_CLEANER "space_cleaner" +#define LABEL_CONCRETE "concrete" +#define LABEL_HEXACRETE "hexacrete" #define CAP_BLACK "black" #define CAP_WHITE "white" @@ -168,6 +170,19 @@ desc = "A jug of most wholesome milk." list_reagents = list(/datum/reagent/consumable/milk = 200) +/obj/item/reagent_containers/cup/jerrycan/concrete_mix + name = "concrete mix can" + label_type = LABEL_CONCRETE + cap_type = CAP_BLACK + desc = "A large can of ready-to-use concrete mix. Just add water." + list_reagents = list(/datum/reagent/concrete_mix = 200) + +/obj/item/reagent_containers/cup/jerrycan/hexacrete + name = "hexacrete can" + label_type = LABEL_HEXACRETE + cap_type = CAP_RED + desc = "A large canister of hexacrete." + list_reagents = list(/datum/reagent/concrete/hexacrete = 200) #undef LABEL_TEXT #undef LABEL_TEXT_OLD @@ -182,6 +197,8 @@ #undef LABEL_ROBUST_HARVEST #undef LABEL_LEFT_4_ZED #undef LABEL_SPACE_CLEANER +#undef LABEL_CONCRETE +#undef LABEL_HEXACRETE #undef CAP_BLACK #undef CAP_WHITE #undef CAP_RED diff --git a/icons/obj/debris.dmi b/icons/obj/debris.dmi index 3f7836f5762d705533f38792ae00e92b19515b4b..53f25915028acb06e3b89f39562296e95944ac96 100644 GIT binary patch literal 20191 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 aHzHj<*hEBqNBvQZ&+h>d*w40rt*hj)rAv1ip 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 diff --git a/icons/obj/ore.dmi b/icons/obj/ore.dmi index 8cb07a5312d0b1087570581891b7619e40659781..98911053b8bfece5686c8745798cd333df9f11b1 100644 GIT binary patch literal 5976 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 literal 12825 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 A8vp~bU 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�W_W&sXJpsT9^t#rM)pbd}`uISY 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�gGFks=Z*^{Qun14X+) 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 literal 0 HcmV?d00001 diff --git a/icons/turf/walls/concrete.dmi b/icons/turf/walls/concrete.dmi new file mode 100644 index 0000000000000000000000000000000000000000..e4a3ea8f83cac4e59cd5a9fb7332d28ef31d7cf9 GIT binary patch literal 7883 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 lwDYEKQ%%og0~Sn4`QWmnu{{|^o!5KI67 literal 0 HcmV?d00001 diff --git a/icons/turf/walls/concrete.png b/icons/turf/walls/concrete.png new file mode 100644 index 0000000000000000000000000000000000000000..4aa00213a36c15d3df1ee99c85f4eb87b4124d9b GIT binary patch literal 1076 zcmeAS@N?(olHy`uVBq!ia0vp^3xHUGg&9bu2j(3EagsfPd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4NyWTz$e7j-`~wZPdzdsxS_5jEiLx{|Now?J|aL_ z#w2fd7uFy7FOLH`>?NMQuI#Vag*Z*5sun$P0t%^?xJHzuB$lLFB^RXvDF!10BQsqC zb6rD=5Ccmq6LTvQBW(i%D+2?z#q++RXvob^$xN%nt)cv%nkP_$2Hb{{%-q!ClEmBs zG(Dz9Rz_wJOH6a@+JJflk`w*PfV2V>11ScEq@C}80l{?N)5S5wM{0GdF8gjc|=HEV`B6Lb? z(dWrN0*@>}H180P zzmA!Ym-5df39~gqU8=wHmT1*}HADD1P*ec7mY3{_)?BQVyR~zS~xD>4Q|* z)YpYK88TVtG^%r2C#-7Nc09-8h1`ys-*qc~yqaQSk`(-NPLABR@AE$5yO&-gBNiMv2=n#|C_2d}+lAq@pKbZc0?f%l}@_Yt825#T^4DXZ~PK3(+ zTg)FaZ;sPd&5%1MmZ}tXNyW}}Nvr*N^YEOeU25kteox)DA#t7Lt@U!Es}dC&eRa(P zdQ(?PaooSW;oQ#qZk%r{cehC0SbAMV{YR+zEY3Nb&(85VDpZJ}-cYSn~%c&Ik(w)q40!&$}Ck8de=6cMN@1EKs&?Hjv zu)cjsa{Fd)kH`nJc{3TDysl-jWHjg=jn-~jE4p#x;T;^z1sfk;;>rE~?)5z#$JhJ5 z{A`*2gmJ6yFVdQ&MBb@03?pN ArvLx| literal 0 HcmV?d00001 diff --git a/icons/turf/walls/concrete.png.toml b/icons/turf/walls/concrete.png.toml new file mode 100644 index 000000000000..3107f0c3216a --- /dev/null +++ b/icons/turf/walls/concrete.png.toml @@ -0,0 +1,2 @@ +output_name = "concrete" +template = "bitmask/diagonal_32x32.toml" diff --git a/icons/turf/walls/hexacrete.dmi b/icons/turf/walls/hexacrete.dmi new file mode 100644 index 0000000000000000000000000000000000000000..eec5eed647a9d3a80483a0b967cb9dfc46cca257 GIT binary patch literal 7391 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 Xf19^4>Y+_xpd{z%>gTe~DWM4fqxq3; literal 0 HcmV?d00001 diff --git a/icons/turf/walls/hexacrete.png.toml b/icons/turf/walls/hexacrete.png.toml new file mode 100644 index 000000000000..ee4ca98ba7b6 --- /dev/null +++ b/icons/turf/walls/hexacrete.png.toml @@ -0,0 +1,2 @@ +output_name = "hexacrete" +template = "bitmask/diagonal_32x32.toml" diff --git a/sound/effects/footstep/concrete1.ogg b/sound/effects/footstep/concrete1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..1df1955bb338f521bebc825499e734ad42e30859 GIT binary patch literal 12976 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 literal 0 HcmV?d00001 diff --git a/sound/effects/footstep/concrete2.ogg b/sound/effects/footstep/concrete2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..941e140941b8117598a5f3b871a300e51a59f5da GIT binary patch literal 10136 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 BOHlv- literal 0 HcmV?d00001 diff --git a/sound/effects/footstep/concrete4.ogg b/sound/effects/footstep/concrete4.ogg new file mode 100644 index 0000000000000000000000000000000000000000..0319e910e2e67dc8a73138ced4843c466026ad7e GIT binary patch literal 10528 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 literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index bb2ea6f5fc56..5cb0714a9f11 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3156,6 +3156,7 @@ #include "code\game\objects\structures\traps.dm" #include "code\game\objects\structures\tripwire.dm" #include "code\game\objects\structures\votingbox.dm" +#include "code\game\objects\structures\wall_support.dm" #include "code\game\objects\structures\windoor_assembly.dm" #include "code\game\objects\structures\window.dm" #include "code\game\objects\structures\beds_chairs\alien_nest.dm" @@ -3242,6 +3243,7 @@ #include "code\game\turfs\closed\indestructible.dm" #include "code\game\turfs\closed\minerals.dm" #include "code\game\turfs\closed\walls.dm" +#include "code\game\turfs\closed\wall\concrete_walls.dm" #include "code\game\turfs\closed\wall\material_walls.dm" #include "code\game\turfs\closed\wall\mineral_walls.dm" #include "code\game\turfs\closed\wall\misc_walls.dm" @@ -3266,6 +3268,7 @@ #include "code\game\turfs\open\snow.dm" #include "code\game\turfs\open\water.dm" #include "code\game\turfs\open\floor\catwalk_plating.dm" +#include "code\game\turfs\open\floor\concrete_floor.dm" #include "code\game\turfs\open\floor\fancy_floor.dm" #include "code\game\turfs\open\floor\glass.dm" #include "code\game\turfs\open\floor\hull.dm" diff --git a/tgui/packages/tgui/interfaces/PersonalCrafting/constants.ts b/tgui/packages/tgui/interfaces/PersonalCrafting/constants.ts index ad0f6a29437e..97aec28520b4 100644 --- a/tgui/packages/tgui/interfaces/PersonalCrafting/constants.ts +++ b/tgui/packages/tgui/interfaces/PersonalCrafting/constants.ts @@ -48,6 +48,7 @@ export const CATEGORY_ICONS_CRAFTING = { Tools: 'screwdriver-wrench', Entertainment: 'masks-theater', 'Blood Cult': 'users', + Construction: 'trowel-bricks', } as const; export const CATEGORY_ICONS_COOKING = { From 1f23af4a2468e59c40c0b4b08db34899f3cc9e91 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 05:50:06 +0000 Subject: [PATCH 329/445] Automatic changelog for PR #97063 [ci skip] --- html/changelogs/AutoChangeLog-pr-97063.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97063.yml diff --git a/html/changelogs/AutoChangeLog-pr-97063.yml b/html/changelogs/AutoChangeLog-pr-97063.yml new file mode 100644 index 000000000000..4ee788bb5078 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97063.yml @@ -0,0 +1,4 @@ +author: "antropod" +delete-after: True +changes: + - rscadd: "Added cement and concrete, walls, floors and pavements. Check your crafting guide under category Construction." \ No newline at end of file From 64169c3d69cc6c8d88f4cc5925a0c0e902335fb4 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 06:00:23 +0000 Subject: [PATCH 330/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97063.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97224.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97238.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97250.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97251.yml | 4 ---- html/changelogs/archive/2026-07.yml | 12 ++++++++++++ 6 files changed, 12 insertions(+), 20 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97063.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97224.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97238.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97250.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97251.yml diff --git a/html/changelogs/AutoChangeLog-pr-97063.yml b/html/changelogs/AutoChangeLog-pr-97063.yml deleted file mode 100644 index 4ee788bb5078..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97063.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "antropod" -delete-after: True -changes: - - rscadd: "Added cement and concrete, walls, floors and pavements. Check your crafting guide under category Construction." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97224.yml b/html/changelogs/AutoChangeLog-pr-97224.yml deleted file mode 100644 index c1a1191ba334..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97224.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Fghj240" -delete-after: True -changes: - - qol: "IDC-27 contains symptom levels" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97238.yml b/html/changelogs/AutoChangeLog-pr-97238.yml deleted file mode 100644 index 2abcf17530dc..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97238.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "Fix heretic transmutation fail text" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97250.yml b/html/changelogs/AutoChangeLog-pr-97250.yml deleted file mode 100644 index 18f725b8aa48..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97250.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Absolucy" -delete-after: True -changes: - - bugfix: "Jellypeople can speak Slime language again." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97251.yml b/html/changelogs/AutoChangeLog-pr-97251.yml deleted file mode 100644 index e4fe70f7dcbc..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97251.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "QuiteLiterallyAnything" -delete-after: True -changes: - - qol: "Bluespace Artillery Cannons now indicate the space they will occupy prior to deployment." \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index 39bfc139a3a6..b8a9cd31c62a 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -925,12 +925,24 @@ - code_imp: Optimized revenant abilities - code_imp: Improves gas tank window performance 2026-07-30: + Absolucy: + - bugfix: Jellypeople can speak Slime language again. Aliceee2ch: - bugfix: Bardrobe is now using service budget instead of medical. + Fghj240: + - qol: IDC-27 contains symptom levels + Melbert: + - bugfix: Fix heretic transmutation fail text + QuiteLiterallyAnything: + - qol: Bluespace Artillery Cannons now indicate the space they will occupy prior + to deployment. SmArtKar: - code_imp: Optimized particle lighting Thunder12345: - bugfix: High frequency blades kept in the suit storage slot no longer look outdated. + antropod: + - rscadd: Added cement and concrete, walls, floors and pavements. Check your crafting + guide under category Construction. kyunkyunkyun: - bugfix: You can once again scan pod people's health with advanced/debug health analyzers. From 71430519fd72fe5ce2738f4fbb54e9f74a5378a6 Mon Sep 17 00:00:00 2001 From: Roxy <75404941+TealSeer@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:41:05 -0400 Subject: [PATCH 331/445] Fix vscode marking rspack compilation message as an error (#97261) --- tgui/rspack.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tgui/rspack.config.ts b/tgui/rspack.config.ts index 7de38f18a420..9b62b28f10d1 100644 --- a/tgui/rspack.config.ts +++ b/tgui/rspack.config.ts @@ -6,7 +6,7 @@ import rspack, { type StatsOptions } from '@rspack/core'; export function createStats(verbose: boolean): StatsOptions { return { assets: verbose, - builtAt: verbose, + builtAt: false, cached: false, children: false, chunks: false, From b27659871433260911cd9741795a4854209e6a7e Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:41:53 -0400 Subject: [PATCH 332/445] Fixes atmospherics_sanity test race condition (#97225) --- code/modules/unit_tests/atmospherics_sanity.dm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/modules/unit_tests/atmospherics_sanity.dm b/code/modules/unit_tests/atmospherics_sanity.dm index f3efb4d0a137..3ffc2cd21806 100644 --- a/code/modules/unit_tests/atmospherics_sanity.dm +++ b/code/modules/unit_tests/atmospherics_sanity.dm @@ -27,6 +27,11 @@ crawled_areas = list() remaining_areas = list() + // early-init events can destroy pipes, queuing the surviving neighbors for a pipenet rebuild. the rebuild normally happens + // on the next SSair.fire(), but this test can run before that fire cycle occurs, racing against it. + while(length(SSair.rebuild_queue) || length(SSair.expansion_queue)) + SSair.process_rebuilds() + for(var/obj/effect/landmark/atmospheric_sanity/start_area/start_marker in GLOB.landmarks_list) var/area/starting_area = get_area(start_marker) if(starting_area in starting_areas) From f4314e33e834be25e434ae11ee4808ecf314f1c5 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:42:48 -0400 Subject: [PATCH 333/445] Fixes blood decal hard dels, part 3 (#97219) --- code/modules/unit_tests/create_and_destroy.dm | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/code/modules/unit_tests/create_and_destroy.dm b/code/modules/unit_tests/create_and_destroy.dm index f6c444123824..fd3df9786e5a 100644 --- a/code/modules/unit_tests/create_and_destroy.dm +++ b/code/modules/unit_tests/create_and_destroy.dm @@ -58,12 +58,9 @@ GLOBAL_VAR_INIT(running_create_and_destroy, FALSE) original_baseturf_count = length(original_baseturfs) else var/atom/creation = new type_path(spawn_at) - if(QDELETED(creation)) - // Same as below - creation = null - continue - //Go all in - qdel(creation, force = TRUE) + if(!QDELETED(creation)) + //Go all in + qdel(creation, force = TRUE) //This will hold a ref to the last thing we process unless we set it to null //Yes byond is fucking sinful creation = null @@ -72,7 +69,7 @@ GLOBAL_VAR_INIT(running_create_and_destroy, FALSE) var/list/to_del = spawn_at.contents - cached_contents if(length(to_del)) for(var/atom/to_kill in to_del) - qdel(to_kill) + qdel(to_kill, force = TRUE) GLOB.running_create_and_destroy = FALSE From e198f35c2d0e143131ea54fc96dfe2dc575ca998 Mon Sep 17 00:00:00 2001 From: harry Date: Thu, 30 Jul 2026 14:43:36 +0100 Subject: [PATCH 334/445] map feedback links, stop layout reflowing in escape menu [no gbp] (#97201) Co-authored-by: harryob <55142896+harryob@users.noreply.github.com> --- code/modules/escape_menu/escape_menu.dm | 3 ++- tgui/packages/tgui-escape-menu/EscapeMenu.tsx | 17 ++++++++++++++++- tgui/packages/tgui-escape-menu/index.tsx | 12 +----------- tgui/packages/tgui-escape-menu/scaling.ts | 11 +++++++++++ tgui/packages/tgui-escape-menu/styles/main.scss | 9 +++++++++ 5 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 tgui/packages/tgui-escape-menu/scaling.ts diff --git a/code/modules/escape_menu/escape_menu.dm b/code/modules/escape_menu/escape_menu.dm index b3fb55d204b2..a710a64537d0 100644 --- a/code/modules/escape_menu/escape_menu.dm +++ b/code/modules/escape_menu/escape_menu.dm @@ -138,7 +138,8 @@ GAME_VERB_HIDDEN(/client, reset_held_keys_verb, "Reset Held Keys") "serverTime" = server_timestamp(format = "hh:mm:ss"), "shiftTime" = (SSticker.round_start_time == 0) ? "Pre-Game" : round_timestamp(), "timeDilation" = "[round(SStime_track.time_dilation_current, 1)]", - "mapName" = SSmapping.current_map?.return_map_name(webmap_included = TRUE) || "Loading...", + "mapName" = SSmapping.current_map?.map_name || "Loading...", + "mapFeedbackLink" = SSmapping.current_map?.feedback_link, "canLeaveBody" = isliving(client?.mob), "canAdminHelp" = (/client/verb/adminhelp in client?.verbs), "canSeeNotes" = CONFIG_GET(flag/see_own_notes), diff --git a/tgui/packages/tgui-escape-menu/EscapeMenu.tsx b/tgui/packages/tgui-escape-menu/EscapeMenu.tsx index 5c31d6df793e..717dfa614527 100644 --- a/tgui/packages/tgui-escape-menu/EscapeMenu.tsx +++ b/tgui/packages/tgui-escape-menu/EscapeMenu.tsx @@ -8,6 +8,7 @@ import { HomePage } from './pages/HomePage'; import { LeaveBodyPage } from './pages/LeaveBodyPage'; import { PlayersPage } from './pages/PlayersPage'; import { QuitPage } from './pages/QuitPage'; +import { updateScaling } from './scaling'; type Page = 'home' | 'admin' | 'players' | 'leave_body' | 'quit'; @@ -25,6 +26,7 @@ export type ServerState = { stationName: string; roundId: string; mapName: string; + mapFeedbackLink: string | null; serverTime: string; shiftTime: string; timeDilation: string; @@ -96,6 +98,7 @@ export function isResizeFrozen() { function openMenu(dispatch: React.Dispatch) { setTimeout(() => { resizeFrozen = false; + updateScaling(); }, 100); playOpenSounds(); sendAction('opened'); @@ -202,7 +205,19 @@ function Details({ serverState }: { serverState: ServerState }) {

    Round ID: {serverState.roundId || 'Unset'}
    Server Time: {serverState.serverTime}
    Shift Time: {serverState.shiftTime}
    -
    Map: {serverState.mapName || 'Loading...'}
    +
    + Map:{' '} + {serverState.mapFeedbackLink ? ( + Byond.command(`.url ${serverState.mapFeedbackLink}`)} + > + {serverState.mapName || 'Loading...'} + + ) : ( + serverState.mapName || 'Loading...' + )} +
    Time Dilation: {serverState.timeDilation}%
    ); diff --git a/tgui/packages/tgui-escape-menu/index.tsx b/tgui/packages/tgui-escape-menu/index.tsx index f84f1ad43606..e15acf5e86da 100644 --- a/tgui/packages/tgui-escape-menu/index.tsx +++ b/tgui/packages/tgui-escape-menu/index.tsx @@ -4,23 +4,13 @@ import { loadMappings, loadStyleSheet } from 'common/assets'; import { createRoot, type Root } from 'react-dom/client'; import { assetMap } from './assets'; import { EscapeMenu, isResizeFrozen } from './EscapeMenu'; - -const ICON_SCALE_BASE_WIDTH = 800; -const REM_BASE_DIVISOR = 100; +import { updateScaling } from './scaling'; let reactRoot: Root | null = null; document.onreadystatechange = () => { if (document.readyState !== 'complete') return; - function updateScaling() { - const width = window.innerWidth; - document.documentElement.style.fontSize = `${width / REM_BASE_DIVISOR}px`; - document.documentElement.style.setProperty( - '--icon-scale', - `${width / ICON_SCALE_BASE_WIDTH}`, - ); - } updateScaling(); window.addEventListener('resize', () => { diff --git a/tgui/packages/tgui-escape-menu/scaling.ts b/tgui/packages/tgui-escape-menu/scaling.ts new file mode 100644 index 000000000000..fa6131dcd701 --- /dev/null +++ b/tgui/packages/tgui-escape-menu/scaling.ts @@ -0,0 +1,11 @@ +const ICON_SCALE_BASE_WIDTH = 800; +const REM_BASE_DIVISOR = 100; + +export function updateScaling() { + const width = window.innerWidth; + document.documentElement.style.fontSize = `${width / REM_BASE_DIVISOR}px`; + document.documentElement.style.setProperty( + '--icon-scale', + `${width / ICON_SCALE_BASE_WIDTH}`, + ); +} diff --git a/tgui/packages/tgui-escape-menu/styles/main.scss b/tgui/packages/tgui-escape-menu/styles/main.scss index 68ca84478177..88c92b2e7846 100644 --- a/tgui/packages/tgui-escape-menu/styles/main.scss +++ b/tgui/packages/tgui-escape-menu/styles/main.scss @@ -98,6 +98,15 @@ body, z-index: 2; } + &__details-link { + color: #5599ff; + + &:hover { + text-decoration: underline; + -webkit-text-stroke: 0; + } + } + &__buttons { display: flex; flex-direction: column; From 8c76e59d107fcd6b0cfa8fdbe7b598bcfa9321ec Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:43:56 +0000 Subject: [PATCH 335/445] Automatic changelog for PR #97201 [ci skip] --- html/changelogs/AutoChangeLog-pr-97201.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97201.yml diff --git a/html/changelogs/AutoChangeLog-pr-97201.yml b/html/changelogs/AutoChangeLog-pr-97201.yml new file mode 100644 index 000000000000..a93d128bd123 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97201.yml @@ -0,0 +1,5 @@ +author: "hry-gh" +delete-after: True +changes: + - bugfix: "escape menu has map feedback links again" + - bugfix: "escape menu does not jump around sometimes when re-opening" \ No newline at end of file From ed640f154bf4cf9f0203a9be5af549763869cd29 Mon Sep 17 00:00:00 2001 From: Chen Marisa <18307183+flleeppyy@users.noreply.github.com> Date: Thu, 30 Jul 2026 06:45:12 -0700 Subject: [PATCH 336/445] TGUI unlimited windows preference (#97226) --- code/__DEFINES/tgui.dm | 2 ++ code/controllers/subsystem/tgui.dm | 6 ++++-- code/modules/client/preferences/tgui.dm | 7 +++++++ code/modules/tgui/tgui_window.dm | 3 ++- .../preferences/features/game_preferences/tgui.tsx | 8 ++++++++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/code/__DEFINES/tgui.dm b/code/__DEFINES/tgui.dm index ca90b2940d6a..f5786c75a5dd 100644 --- a/code/__DEFINES/tgui.dm +++ b/code/__DEFINES/tgui.dm @@ -11,6 +11,8 @@ #define TGUI_WINDOW_SOFT_LIMIT 5 /// Maximum number of open windows #define TGUI_WINDOW_HARD_LIMIT 9 +/// Maximum number of open windows when user has prefs set to unlimited, basically infinite. You dont need 200 windows; you'll run out of memory by then +#define TGUI_WINDOW_UNLIMITED_LIMIT 200 /// Maximum ping timeout allowed to detect zombie windows #define TGUI_PING_TIMEOUT (4 SECONDS) diff --git a/code/controllers/subsystem/tgui.dm b/code/controllers/subsystem/tgui.dm index df33cf68c8c7..c548b4a5b14a 100644 --- a/code/controllers/subsystem/tgui.dm +++ b/code/controllers/subsystem/tgui.dm @@ -96,10 +96,11 @@ SUBSYSTEM_DEF(tgui) return null var/list/windows = user.client.tgui_windows var/window_id + var/window_limit = user.client.prefs?.read_preference(/datum/preference/toggle/tgui_unlimited_windows) ? TGUI_WINDOW_UNLIMITED_LIMIT : TGUI_WINDOW_HARD_LIMIT var/datum/tgui_window/window var/window_found = FALSE // Find a usable window - for(var/i in 1 to TGUI_WINDOW_HARD_LIMIT) + for(var/i in 1 to window_limit) window_id = TGUI_WINDOW_ID(i) window = windows[window_id] // As we are looping, create missing window datums @@ -131,7 +132,8 @@ SUBSYSTEM_DEF(tgui) log_tgui(user, context = "SStgui/force_close_all_windows") if(user.client) user.client.tgui_windows = list() - for(var/i in 1 to TGUI_WINDOW_HARD_LIMIT) + var/window_limit = user.client.prefs.read_preference(/datum/preference/toggle/tgui_unlimited_windows) ? TGUI_WINDOW_UNLIMITED_LIMIT : TGUI_WINDOW_HARD_LIMIT + for(var/i in 1 to window_limit) var/window_id = TGUI_WINDOW_ID(i) user << browse(null, "window=[window_id]") diff --git a/code/modules/client/preferences/tgui.dm b/code/modules/client/preferences/tgui.dm index a42e047be49d..39a501cfcf9f 100644 --- a/code/modules/client/preferences/tgui.dm +++ b/code/modules/client/preferences/tgui.dm @@ -63,6 +63,13 @@ // Force it to reload either way tgui.update_static_data(client.mob) +// But no games. +/datum/preference/toggle/tgui_unlimited_windows + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "tgui_unlimited_windows" + savefile_identifier = PREFERENCE_PLAYER + default_value = FALSE + /// Light mode for tgui say /datum/preference/toggle/tgui_say_light_mode category = PREFERENCE_CATEGORY_GAME_PREFERENCES diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index f82457a8ba81..84f0c14fb592 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -147,10 +147,11 @@ * return bool */ /datum/tgui_window/proc/can_be_suspended() + var/unlimited_windows = client?.prefs?.read_preference(/datum/preference/toggle/tgui_unlimited_windows) return !fatally_errored \ && pooled \ && pool_index > 0 \ - && pool_index <= TGUI_WINDOW_SOFT_LIMIT \ + && (unlimited_windows || pool_index <= TGUI_WINDOW_SOFT_LIMIT) \ && status == TGUI_WINDOW_READY /** diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/tgui.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/tgui.tsx index 3fe5ac5e3ae8..299771c64c2e 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/tgui.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/tgui.tsx @@ -28,6 +28,14 @@ export const tgui_lock: FeatureToggle = { component: CheckboxInput, }; +export const tgui_unlimited_windows: FeatureToggle = { + name: 'Uncap TGUI window limit', + category: 'UI', + description: + 'Disables the window limit for TGUI windows. Having more than 5 windows open can tank performance on lower end computers.', + component: CheckboxInput, +}; + export const ui_scale: FeatureToggle = { name: 'Toggle UI scaling', category: 'UI', From 6a069d1718dc99fe12db8be6b17974c178abf123 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:45:33 +0000 Subject: [PATCH 337/445] Automatic changelog for PR #97226 [ci skip] --- html/changelogs/AutoChangeLog-pr-97226.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97226.yml diff --git a/html/changelogs/AutoChangeLog-pr-97226.yml b/html/changelogs/AutoChangeLog-pr-97226.yml new file mode 100644 index 000000000000..7538b1304af1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97226.yml @@ -0,0 +1,4 @@ +author: "flleeppyy" +delete-after: True +changes: + - rscadd: "you can now have unlimited TGUI windows in preferences" \ No newline at end of file From fcc46c3decde1a8f3c9a02a37e586861b6348fd8 Mon Sep 17 00:00:00 2001 From: Thunder12345 Date: Thu, 30 Jul 2026 14:46:59 +0100 Subject: [PATCH 338/445] Fixes point-based domain resetting (#97210) --- code/modules/bitrunning/server/map_handling.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/modules/bitrunning/server/map_handling.dm b/code/modules/bitrunning/server/map_handling.dm index 9076f30a73a1..ff7e06fb4f36 100644 --- a/code/modules/bitrunning/server/map_handling.dm +++ b/code/modules/bitrunning/server/map_handling.dm @@ -237,6 +237,7 @@ qdel(creature) generated_domain.secondary_loot_generated = 0 + generated_domain.main_crate_points = 0 avatar_connection_refs.Cut() exit_turfs = list() From 174b2a3dba7dc88eaf615af60bc8fa7bbfd3907c Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:47:27 +0000 Subject: [PATCH 339/445] Automatic changelog for PR #97210 [ci skip] --- html/changelogs/AutoChangeLog-pr-97210.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97210.yml diff --git a/html/changelogs/AutoChangeLog-pr-97210.yml b/html/changelogs/AutoChangeLog-pr-97210.yml new file mode 100644 index 000000000000..c16eaae1d1ac --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97210.yml @@ -0,0 +1,4 @@ +author: "Thunder12345" +delete-after: True +changes: + - bugfix: "Point-based bitrunning domains will no longer mysteriously complete themselves on reruns." \ No newline at end of file From efc51b51c33667f82b21627c5277019021326f8c Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Thu, 30 Jul 2026 08:50:29 -0500 Subject: [PATCH 340/445] Fix lesser magic mirror and friend race selection (#97236) --- code/game/objects/structures/mirror.dm | 43 +++++++++++++------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 165232371345..6d4d7d64fd64 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -37,17 +37,22 @@ ///Flags this race must have to be selectable with this type of mirror. var/race_flags = MIRROR_MAGIC ///List of all Races that can be chosen, decided by its Initialize. - var/list/selectable_races = list() - -/obj/structure/mirror/Destroy() - mirror_options = null - selectable_races = null - return ..() + var/list/selectable_races /obj/structure/mirror/proc/update_choices() for(var/i in mirror_options) mirror_options[i] = icon('icons/hud/radial.dmi', i) + if(CHANGE_RACE in mirror_options) + selectable_races = list() + for(var/datum/species/species_type as anything in get_species_pool()) + if(!race_flags || (species_type::changesource_flags & race_flags)) + selectable_races[species_type::name] = species_type + selectable_races = sort_list(selectable_races) + + else + selectable_races = null + /obj/structure/mirror/Initialize(mapload) . = ..() var/static/list/reflection_filter = alpha_mask_filter(icon = icon('icons/obj/watercloset.dmi', "mirror_mask")) @@ -74,6 +79,10 @@ return FALSE return TRUE +/// Returns a list species typepaths to use for the mirror. This pool is then filtered by flags. +/obj/structure/mirror/proc/get_species_pool() + return subtypesof(/datum/species) + MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror, 28) /obj/structure/mirror/broken @@ -380,16 +389,6 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) deconstructable = FALSE cursable = FALSE -/obj/structure/mirror/magic/Initialize(mapload) - . = ..() - - if(length(selectable_races)) - return - for(var/datum/species/species_type as anything in subtypesof(/datum/species)) - if(initial(species_type.changesource_flags) & race_flags) - selectable_races[initial(species_type.name)] = species_type - selectable_races = sort_list(selectable_races) - /obj/structure/mirror/magic/change_beard(mob/living/carbon/human/beard_dresser) // magical mirrors do nothing but give you the damn beard var/new_style = tgui_input_list(beard_dresser, "Select a facial hairstyle", "Grooming", SSaccessories.facial_hairstyles_list) if(isnull(new_style) || !can_use_mirror(beard_dresser)) @@ -428,11 +427,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) race_changer.add_traits(list(TRAIT_LITERATE, TRAIT_ADVANCEDTOOLUSER), SPECIES_TRAIT) /obj/structure/mirror/magic/lesser + race_flags = NONE -/obj/structure/mirror/magic/lesser/Initialize(mapload) - // Roundstart species don't have a flag, so it has to be set on Initialize. - selectable_races = get_selectable_species().Copy() - return ..() +/obj/structure/mirror/magic/lesser/get_species_pool() + var/list/race_list = list() + for(var/species_id in get_selectable_species()) + race_list += GLOB.species_list[species_id] + return race_list /obj/structure/mirror/magic/lesser/heretic name = "miraculous mirror" @@ -480,7 +481,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) user.set_facial_hairstyle(random_facial_hairstyle(user.gender)) if(CHANGE_RACE) to_chat(user, span_hypnophrase("The body - no, the body is all wrong - [pick("I must become anew", "I need to be different", "I want to be something else")]...")) - var/list/options = selectable_races.Copy() - user.dna.species.type + var/list/options = assoc_to_values(selectable_races) - user.dna.species.type var/datum/species/newrace = GLOB.species_prototypes[pick(options)] on_species_change(user, newrace) user.set_species(newrace.type, icon_update = FALSE) From 8369aba83d546a680ab2491642c5dbcadb245498 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:50:48 +0000 Subject: [PATCH 341/445] Automatic changelog for PR #97236 [ci skip] --- html/changelogs/AutoChangeLog-pr-97236.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97236.yml diff --git a/html/changelogs/AutoChangeLog-pr-97236.yml b/html/changelogs/AutoChangeLog-pr-97236.yml new file mode 100644 index 000000000000..72bbc7dd05de --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97236.yml @@ -0,0 +1,4 @@ +author: "Melbert" +delete-after: True +changes: + - bugfix: "Fix lesser magic mirror (and friends) race selection" \ No newline at end of file From b3042a1efe5f54ffb6a7462fe7cdba0bf7270d50 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:00:24 +0000 Subject: [PATCH 342/445] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-97201.yml | 5 ----- html/changelogs/AutoChangeLog-pr-97210.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97226.yml | 4 ---- html/changelogs/AutoChangeLog-pr-97236.yml | 4 ---- html/changelogs/archive/2026-07.yml | 8 ++++++++ 5 files changed, 8 insertions(+), 17 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-97201.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97210.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97226.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-97236.yml diff --git a/html/changelogs/AutoChangeLog-pr-97201.yml b/html/changelogs/AutoChangeLog-pr-97201.yml deleted file mode 100644 index a93d128bd123..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97201.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "hry-gh" -delete-after: True -changes: - - bugfix: "escape menu has map feedback links again" - - bugfix: "escape menu does not jump around sometimes when re-opening" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97210.yml b/html/changelogs/AutoChangeLog-pr-97210.yml deleted file mode 100644 index c16eaae1d1ac..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97210.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Thunder12345" -delete-after: True -changes: - - bugfix: "Point-based bitrunning domains will no longer mysteriously complete themselves on reruns." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97226.yml b/html/changelogs/AutoChangeLog-pr-97226.yml deleted file mode 100644 index 7538b1304af1..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97226.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "flleeppyy" -delete-after: True -changes: - - rscadd: "you can now have unlimited TGUI windows in preferences" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-97236.yml b/html/changelogs/AutoChangeLog-pr-97236.yml deleted file mode 100644 index 72bbc7dd05de..000000000000 --- a/html/changelogs/AutoChangeLog-pr-97236.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "Fix lesser magic mirror (and friends) race selection" \ No newline at end of file diff --git a/html/changelogs/archive/2026-07.yml b/html/changelogs/archive/2026-07.yml index b8a9cd31c62a..da1d1af1aab9 100644 --- a/html/changelogs/archive/2026-07.yml +++ b/html/changelogs/archive/2026-07.yml @@ -933,6 +933,7 @@ - qol: IDC-27 contains symptom levels Melbert: - bugfix: Fix heretic transmutation fail text + - bugfix: Fix lesser magic mirror (and friends) race selection QuiteLiterallyAnything: - qol: Bluespace Artillery Cannons now indicate the space they will occupy prior to deployment. @@ -940,9 +941,16 @@ - code_imp: Optimized particle lighting Thunder12345: - bugfix: High frequency blades kept in the suit storage slot no longer look outdated. + - bugfix: Point-based bitrunning domains will no longer mysteriously complete themselves + on reruns. antropod: - rscadd: Added cement and concrete, walls, floors and pavements. Check your crafting guide under category Construction. + flleeppyy: + - rscadd: you can now have unlimited TGUI windows in preferences + hry-gh: + - bugfix: escape menu has map feedback links again + - bugfix: escape menu does not jump around sometimes when re-opening kyunkyunkyun: - bugfix: You can once again scan pod people's health with advanced/debug health analyzers. From 77e0fc0028c56037a82d63fa79ddfaa9ff18411e Mon Sep 17 00:00:00 2001 From: san7890 Date: Thu, 30 Jul 2026 15:44:51 -0600 Subject: [PATCH 343/445] Jellypeople can only cannibalize jelly limbs (#97252) ## About The Pull Request Fixes a weird bug I experienced where my jellyperson slorped up wooden peg legs to regenerate it's own body, like what the fuck? This just creates a small patch to make it so that only limbs that match the species type are eaten up. I was considering patching this so that it could be any `LIMB_ORGANIC`, but it really felt weird to implement this given the broader implications of this species feature needing to behave like slimes. I think feeding should be an active process (like it is in humans), while the body trying to section off parts of itself to keep the main cluster alive is a passive process that can only be done in times of extreme need. This should match all four subtypes of jellyperson (no mixing and matching, let me know if I should reconsider that). ## Why It's Good For The Game Fixes a weird confusing bug I saw yesterday and makes it more expected. I also cleaned up the code to be more readable and use modern procs, as well as fix a grammar issue I also saw come up. ## Changelog :cl: fix: Jellypeople are now no longer able to cannibalize limbs that were not originally generated by them (AKA belonging to their set species). /:cl: --- code/__DEFINES/wounds.dm | 2 ++ code/_globalvars/bitfields.dm | 11 +++--- .../carbon/human/species_types/jellypeople.dm | 35 ++++++++++++++----- .../bodyparts/species_parts/misc_bodyparts.dm | 12 +++---- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm index 7e64c9f35f9e..36f4a65da1f1 100644 --- a/code/__DEFINES/wounds.dm +++ b/code/__DEFINES/wounds.dm @@ -90,6 +90,8 @@ GLOBAL_LIST_INIT(wound_severities_chronological, list( #define BIO_JOINTED (1<<6) /// Skin is covered in thick chitin and is resistant to cutting #define BIO_CHITIN (1<<7) +/// Limb is comprised entirely of slime jelly - can be cannibalized by any jelly person with the ability to eat their own limbs to replenish their own slime (blood) +#define BIO_JELLY (1<<8) /// Robotic - can suffer all metal/wired wounds, such as: UNIMPLEMENTED PLEASE UPDATE ONCE SYNTH WOUNDS 9/5/2023 ~Niko #define BIO_ROBOTIC (BIO_METAL|BIO_WIRED) /// Has flesh and bone - See BIO_BONE and BIO_FLESH diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index fbc08c4a7bbb..57842773159e 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -558,15 +558,16 @@ DEFINE_BITFIELD(bodypart_flags, list( )) DEFINE_BITFIELD(biological_state, list( - "BIO_INORGANIC" = BIO_INORGANIC, + "BIO_BLOODED" = BIO_BLOODED, "BIO_BONE" = BIO_BONE, + "BIO_CHITIN" = BIO_CHITIN, "BIO_FLESH" = BIO_FLESH, + "BIO_INORGANIC" = BIO_INORGANIC, + "BIO_JELLY" = BIO_JELLY, + "BIO_JOINTED" = BIO_JOINTED, "BIO_METAL" = BIO_METAL, - "BIO_WOOD" = BIO_WOOD, "BIO_WIRED" = BIO_WIRED, - "BIO_BLOODED" = BIO_BLOODED, - "BIO_JOINTED" = BIO_JOINTED, - "BIO_CHITIN" = BIO_CHITIN, + "BIO_WOOD" = BIO_WOOD, )) DEFINE_BITFIELD(change_exempt_flags, list( diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index ee6bb1c7727d..1d244f4731d3 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -86,24 +86,41 @@ // Saline can prevent you from cannibalizing yourself. if(slime.get_blood_volume(apply_modifiers = TRUE) < BLOOD_VOLUME_BAD) - Cannibalize_Body(slime) + cannibalize_body(slime) regenerate_limbs?.build_all_button_icons(UPDATE_BUTTON_STATUS) return HANDLE_BLOOD_NO_NUTRITION_DRAIN|HANDLE_BLOOD_NO_OXYLOSS -/datum/species/jelly/proc/Cannibalize_Body(mob/living/carbon/human/H) - var/list/limbs_to_consume = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - H.get_missing_limbs() - var/obj/item/bodypart/consumed_limb +/datum/species/jelly/proc/cannibalize_body(mob/living/carbon/human/target) + var/list/limbs_to_consume = list() + + for(var/body_zone, untyped_limb in target.get_bodyparts_by_zones()) + var/obj/item/bodypart/limb = untyped_limb + if(isnull(limb)) + continue + + var/limb_zone = limb.body_zone + + if(IS_STUMP(limb) || limb_zone == BODY_ZONE_CHEST || limb_zone == BODY_ZONE_HEAD) + continue + + if(limb.biological_state & BIO_JELLY) // can only cannibalize limbs that are comprised of jelly (this is a last gasp to keep going, let's assume we can't digest out non-jelly limbs) + limbs_to_consume += limb_zone + if(!length(limbs_to_consume)) - H.losebreath++ + target.losebreath++ return - if(H.num_legs) //Legs go before arms + + if(target.num_legs > 0) //Legs go before arms limbs_to_consume -= list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM) - consumed_limb = H.get_bodypart(pick(limbs_to_consume)) + + var/obj/item/bodypart/consumed_limb = target.get_bodypart(pick(limbs_to_consume)) consumed_limb.drop_limb() - to_chat(H, span_userdanger("Your [consumed_limb] is drawn back into your body, unable to maintain its shape!")) + + to_chat(target, span_userdanger("Your [consumed_limb.name] is drawn back into your body, unable to maintain its shape!")) qdel(consumed_limb) - H.adjust_blood_volume(20 * H.physiology.blood_regen_mod) + + target.adjust_blood_volume(20 * target.physiology.blood_regen_mod) /datum/species/jelly/get_species_description() return "Jellypeople are a strange and alien species with three eyes, made entirely out of gel." diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm index 225db1d12efb..efa39c3591da 100644 --- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm +++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm @@ -85,7 +85,7 @@ ///JELLY /obj/item/bodypart/head/jelly - biological_state = (BIO_FLESH|BIO_BLOODED) + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JELLY) limb_id = SPECIES_JELLYPERSON is_dimorphic = TRUE dmg_overlay_type = null @@ -94,7 +94,7 @@ butcher_replacement = null /obj/item/bodypart/chest/jelly - biological_state = (BIO_FLESH|BIO_BLOODED) + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JELLY) limb_id = SPECIES_JELLYPERSON is_dimorphic = TRUE dmg_overlay_type = null @@ -106,28 +106,28 @@ return icon('icons/mob/butts.dmi', BUTT_SPRITE_SLIME) /obj/item/bodypart/arm/left/jelly - biological_state = (BIO_FLESH|BIO_BLOODED) + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JELLY) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage butcher_replacement = null /obj/item/bodypart/arm/right/jelly - biological_state = (BIO_FLESH|BIO_BLOODED) + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JELLY) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage butcher_replacement = null /obj/item/bodypart/leg/left/jelly - biological_state = (BIO_FLESH|BIO_BLOODED) + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JELLY) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage butcher_replacement = null /obj/item/bodypart/leg/right/jelly - biological_state = (BIO_FLESH|BIO_BLOODED) + biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JELLY) limb_id = SPECIES_JELLYPERSON dmg_overlay_type = null burn_modifier = 0.5 // = 1/2x generic burn damage From 8db0b130d864714cbc4c41dd0c4285f81deaeab8 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:45:12 +0000 Subject: [PATCH 344/445] Automatic changelog for PR #97252 [ci skip] --- html/changelogs/AutoChangeLog-pr-97252.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-97252.yml diff --git a/html/changelogs/AutoChangeLog-pr-97252.yml b/html/changelogs/AutoChangeLog-pr-97252.yml new file mode 100644 index 000000000000..99b6934a2638 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-97252.yml @@ -0,0 +1,4 @@ +author: "san7890" +delete-after: True +changes: + - bugfix: "Jellypeople are now no longer able to cannibalize limbs that were not originally generated by them (AKA belonging to their set species)." \ No newline at end of file From 05efa1103eaa9250a90b6bb21eca94dabf6b7a46 Mon Sep 17 00:00:00 2001 From: mcbalaam <104003807+mcbalaam@users.noreply.github.com> Date: Fri, 31 Jul 2026 04:46:28 +0700 Subject: [PATCH 345/445] fix: cleaner Big Manipulator logic; new "Move" and "Stop" tasks; new "turf" interaction priority (#97102) ## About The Pull Request The Big Manipulator now uses interaction priorities instead of pickup types: image A new Move task is now available: image

    A new Stop task is now available: image

    Post-interaction actions deprecated: it's now up to the operator to put used tools somewhere. The "Continue" one is also deprecated. Worker interaction cycling also deprecated. Fixed an issue when checking stock parts tier reset the priorities when upgrading/writing from disk. "Interact with empty hand" flag on the "Use" task was also removed since it was mirroring the "Interact" task. Ups the amount of tasks for each tier. The Manipulator now "spoofs" the monkeys location when interacting to bypass adjacency checks. ## Why It's Good For The Game Cleaner and less confusing action parameters, new action to prepare the manipulator to pick up something on the tile as soon as it appears, fixes interactions with some machines that require being adjacent to it. ## Changelog :cl: fix: The Big Manipulator now uses filters more correctly and spoofs the monkeys location to interact with some machines. fix: The Big Manipulator now doesn't reset the priorities when upgraded. add: The Big Manipulator now knows how to Move (TM) and Stop (TM). add: The Big Manipulator can now interact with turfs properly. /:cl: --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- .../machinery/big_manipulator/_defines.dm | 28 +--- .../big_manipulator/big_manipulator.dm | 141 +++++++----------- .../big_manipulator_interactions.dm | 137 ++++++++--------- .../big_manipulator/interaction_priorities.dm | 16 ++ .../big_manipulator/manipulator_tasks.dm | 122 ++++++++++----- .../tgui/interfaces/BigManipulator/index.tsx | 53 ++----- .../tgui/interfaces/BigManipulator/types.ts | 8 +- 7 files changed, 248 insertions(+), 257 deletions(-) diff --git a/code/game/machinery/big_manipulator/_defines.dm b/code/game/machinery/big_manipulator/_defines.dm index b04d1b90bb60..c040a9bee256 100644 --- a/code/game/machinery/big_manipulator/_defines.dm +++ b/code/game/machinery/big_manipulator/_defines.dm @@ -3,11 +3,6 @@ #define INTERACT_USE "USE" #define INTERACT_THROW "THROW" -// What should be picked up from the point -#define TAKE_ITEMS 1 -#define TAKE_CLOSETS 2 -#define TAKE_HUMANS 3 - #define MIN_SPEED_MULTIPLIER_TIER_1 0.5 #define MIN_SPEED_MULTIPLIER_TIER_2 0.4 #define MIN_SPEED_MULTIPLIER_TIER_3 0.3 @@ -18,19 +13,15 @@ #define MAX_SPEED_MULTIPLIER_TIER_3 5 #define MAX_SPEED_MULTIPLIER_TIER_4 6 -#define MAX_TASKS_TIER_1 6 -#define MAX_TASKS_TIER_2 12 -#define MAX_TASKS_TIER_3 24 -#define MAX_TASKS_TIER_4 32 - +#define MAX_TASKS_TIER_1 8 +#define MAX_TASKS_TIER_2 16 +#define MAX_TASKS_TIER_3 32 +#define MAX_TASKS_TIER_4 48 -// How should the worker interact with the point -#define WORKER_SINGLE_USE "SINGLE TIME" -#define WORKER_EMPTY_USE "EMPTY HAND" -#define WORKER_NORMAL_USE "NORMAL" #define BASE_POWER_USAGE 0.2 #define BASE_INTERACTION_TIME 0.3 SECONDS +#define BASE_TASK_DELAY 0.3 SECONDS /// How long will the manipulator wait if there's nothing to do #define CYCLE_SKIP_TIMEOUT 1 SECONDS @@ -41,13 +32,6 @@ #define POINT_OVERFLOW_HELD "TO HELD" #define POINT_OVERFLOW_FORBIDDEN "FORBID" -// What should the manipulator do after there's nothing else to interact with on this point anymore -#define POST_INTERACTION_DROP_AT_POINT "AT DROPOFF" -#define POST_INTERACTION_DROP_AT_MACHINE "AT MACHINE" -#define POST_INTERACTION_DROP_NEXT_FITTING "AT ANY FITTING" -#define POST_INTERACTION_WAIT "CONTINUE" - - #define PICKUP_EAGER "Always Pick Up" #define PICKUP_CAN_WAIT "Wait For Suiting" @@ -56,7 +40,9 @@ #define TASK_TYPE_THROW "throw" #define TASK_TYPE_USE "use" #define TASK_TYPE_INTERACT "interact" +#define TASK_TYPE_MOVE "move" #define TASK_TYPE_WAIT "wait" +#define TASK_TYPE_STOP "stop" #define TASKING_SEQUENTIAL "Sequential" #define TASKING_STRICT "Strict order" diff --git a/code/game/machinery/big_manipulator/big_manipulator.dm b/code/game/machinery/big_manipulator/big_manipulator.dm index 68cb85387f50..433c845fd9de 100644 --- a/code/game/machinery/big_manipulator/big_manipulator.dm +++ b/code/game/machinery/big_manipulator/big_manipulator.dm @@ -42,6 +42,8 @@ /// Is the manipulator in the process of stopping? var/stopping = FALSE + /// Is a use interaction currently in progress? Prevents timer recursion. + var/use_in_progress = FALSE /// Is the manipulator waiting for a turf signal to retry? var/waiting_for_signal = FALSE /// Turfs we registered enter/exit signals on while waiting. @@ -70,7 +72,7 @@ var/manipulator_tier = locate_servo ? locate_servo.tier : 1 var/datum/manipulator_task/new_task - var/needs_turf = task_type in list(TASK_TYPE_PICKUP, TASK_TYPE_DROP, TASK_TYPE_THROW, TASK_TYPE_USE, TASK_TYPE_INTERACT) + var/needs_turf = task_type in list(TASK_TYPE_PICKUP, TASK_TYPE_DROP, TASK_TYPE_THROW, TASK_TYPE_USE, TASK_TYPE_INTERACT, TASK_TYPE_MOVE) if(needs_turf) if(!new_turf) @@ -89,8 +91,12 @@ new_task = new /datum/manipulator_task/cargo/dropoff_base/use(new_turf, manipulator_tier) if(TASK_TYPE_INTERACT) new_task = new /datum/manipulator_task/cargo/interact(new_turf, manipulator_tier) + if(TASK_TYPE_MOVE) + new_task = new /datum/manipulator_task/cargo/move(new_turf, manipulator_tier) if(TASK_TYPE_WAIT) new_task = new /datum/manipulator_task/simple/wait() + if(TASK_TYPE_STOP) + new_task = new /datum/manipulator_task/simple/stop() if(QDELETED(new_task)) return FALSE @@ -104,7 +110,8 @@ if((obj_flags & EMAGGED) && istype(new_task, /datum/manipulator_task/cargo)) var/datum/manipulator_task/cargo/cargo_task = new_task - cargo_task.type_filters += /mob/living + if(istype(cargo_task, /datum/manipulator_task/cargo/pickup)) + cargo_task.interaction_priorities += new /datum/manipulator_priority/pickup/humans return new_task @@ -154,9 +161,6 @@ active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * BASE_POWER_USAGE * manipulator_tier - for(var/datum/manipulator_task/cargo/cargo_task in tasks) - cargo_task.interaction_priorities = cargo_task.fill_priority_list(manipulator_tier) - /obj/machinery/big_manipulator/examine(mob/user) . = ..() var/mob/monkey_resolve = monkey_worker?.resolve() @@ -242,8 +246,8 @@ balloon_alert(user, "overloaded") obj_flags |= EMAGGED - for(var/datum/manipulator_task/cargo/cargo_task in tasks) - cargo_task.type_filters += /mob/living + for(var/datum/manipulator_task/cargo/pickup/pickup_task in tasks) + pickup_task.interaction_priorities += new /datum/manipulator_priority/pickup/humans return TRUE @@ -462,64 +466,67 @@ if(istype(task, /datum/manipulator_task/cargo/pickup)) td["task_type"] = TASK_TYPE_PICKUP - var/datum/manipulator_task/cargo/pickup/t = task - td["turf"] = "[t.offset_dx],[t.offset_dy]" - td["filters_status"] = t.should_use_filters - td["filtering_mode"] = t.filtering_mode - td["item_filters"] = _collect_filter_names(t.atom_filters) - td["settings_list"] = _collect_priorities(t.interaction_priorities) - td["pickup_eagerness"] = t.pickup_eagerness + var/datum/manipulator_task/cargo/pickup/pickup_task = task + td["turf"] = "[pickup_task.offset_dx],[pickup_task.offset_dy]" + td["filters_status"] = pickup_task.should_use_filters + td["item_filters"] = _collect_filter_names(pickup_task.atom_filters) + td["settings_list"] = _collect_priorities(pickup_task.interaction_priorities) + td["pickup_eagerness"] = pickup_task.pickup_eagerness else if(istype(task, /datum/manipulator_task/cargo/dropoff_base/drop)) td["task_type"] = TASK_TYPE_DROP - var/datum/manipulator_task/cargo/dropoff_base/drop/t = task - td["turf"] = "[t.offset_dx],[t.offset_dy]" - td["filters_status"] = t.should_use_filters - td["filtering_mode"] = t.filtering_mode - td["item_filters"] = _collect_filter_names(t.atom_filters) - td["settings_list"] = _collect_priorities(t.interaction_priorities) - td["overflow_status"] = t.overflow_status + var/datum/manipulator_task/cargo/dropoff_base/drop/drop_task = task + td["turf"] = "[drop_task.offset_dx],[drop_task.offset_dy]" + td["filters_status"] = drop_task.should_use_filters + td["item_filters"] = _collect_filter_names(drop_task.atom_filters) + td["settings_list"] = _collect_priorities(drop_task.interaction_priorities) + td["overflow_status"] = drop_task.overflow_status else if(istype(task, /datum/manipulator_task/cargo/dropoff_base/throw)) td["task_type"] = TASK_TYPE_THROW - var/datum/manipulator_task/cargo/dropoff_base/throw/t = task - td["turf"] = "[t.offset_dx],[t.offset_dy]" - td["filters_status"] = t.should_use_filters - td["filtering_mode"] = t.filtering_mode - td["item_filters"] = _collect_filter_names(t.atom_filters) - td["settings_list"] = _collect_priorities(t.interaction_priorities) - td["throw_range"] = t.throw_range + var/datum/manipulator_task/cargo/dropoff_base/throw/throw_task = task + td["turf"] = "[throw_task.offset_dx],[throw_task.offset_dy]" + td["filters_status"] = throw_task.should_use_filters + td["item_filters"] = _collect_filter_names(throw_task.atom_filters) + td["settings_list"] = _collect_priorities(throw_task.interaction_priorities) + td["throw_range"] = throw_task.throw_range else if(istype(task, /datum/manipulator_task/cargo/dropoff_base/use)) td["task_type"] = TASK_TYPE_USE - var/datum/manipulator_task/cargo/dropoff_base/use/t = task - td["turf"] = "[t.offset_dx],[t.offset_dy]" - td["filters_status"] = t.should_use_filters - td["item_filters"] = _collect_filter_names(t.atom_filters) - td["settings_list"] = _collect_priorities(t.interaction_priorities) - td["worker_interaction"] = t.worker_interaction - td["use_post_interaction"] = t.use_post_interaction - td["worker_use_rmb"] = t.worker_use_rmb - td["worker_combat_mode"] = t.worker_combat_mode - td["skip_anchored"] = t.skip_anchored + var/datum/manipulator_task/cargo/dropoff_base/use/use_task = task + td["turf"] = "[use_task.offset_dx],[use_task.offset_dy]" + td["filters_status"] = use_task.should_use_filters + td["item_filters"] = _collect_filter_names(use_task.atom_filters) + td["settings_list"] = _collect_priorities(use_task.interaction_priorities) + td["worker_use_rmb"] = use_task.worker_use_rmb + td["worker_combat_mode"] = use_task.worker_combat_mode + td["skip_anchored"] = use_task.skip_anchored else if(istype(task, /datum/manipulator_task/cargo/interact)) td["task_type"] = TASK_TYPE_INTERACT - var/datum/manipulator_task/cargo/interact/t = task - td["turf"] = "[t.offset_dx],[t.offset_dy]" - td["filters_status"] = t.should_use_filters - td["item_filters"] = _collect_filter_names(t.atom_filters) - td["settings_list"] = _collect_priorities(t.interaction_priorities) - td["worker_interaction"] = t.worker_interaction - td["use_post_interaction"] = t.use_post_interaction - td["worker_use_rmb"] = t.worker_use_rmb - td["worker_combat_mode"] = t.worker_combat_mode - td["skip_anchored"] = t.skip_anchored + var/datum/manipulator_task/cargo/interact/interact_task = task + td["turf"] = "[interact_task.offset_dx],[interact_task.offset_dy]" + td["filters_status"] = interact_task.should_use_filters + td["item_filters"] = _collect_filter_names(interact_task.atom_filters) + td["settings_list"] = _collect_priorities(interact_task.interaction_priorities) + td["worker_use_rmb"] = interact_task.worker_use_rmb + td["worker_combat_mode"] = interact_task.worker_combat_mode + td["skip_anchored"] = interact_task.skip_anchored else if(istype(task, /datum/manipulator_task/simple/wait)) td["task_type"] = TASK_TYPE_WAIT - var/datum/manipulator_task/simple/wait/t = task - td["time"] = t.time_seconds + var/datum/manipulator_task/simple/wait/wait_task = task + td["time"] = wait_task.time_seconds + + else if(istype(task, /datum/manipulator_task/simple/stop)) + td["task_type"] = TASK_TYPE_STOP + var/datum/manipulator_task/simple/stop/stop_task = task + td["sub_name"] = stop_task.sub_name + + else if(istype(task, /datum/manipulator_task/cargo/move)) + td["task_type"] = TASK_TYPE_MOVE + var/datum/manipulator_task/cargo/move/move_task = task + td["turf"] = "[move_task.offset_dx],[move_task.offset_dy]" tasks_data += list(td) @@ -711,7 +718,6 @@ continue tasks += new_task - process_upgrades() validate_all_tasks() balloon_alert(user, "loaded") SStgui.update_uis(src) @@ -809,15 +815,6 @@ ct.atom_filters.Cut(value, value + 1) return TRUE - if("cycle_filtering_mode") - if(istype(target_task, /datum/manipulator_task/cargo/dropoff_base/use) || istype(target_task, /datum/manipulator_task/cargo/interact)) - return FALSE - if(!istype(target_task, /datum/manipulator_task/cargo)) - return FALSE - var/datum/manipulator_task/cargo/ct = target_task - ct.filtering_mode = cycle_value(ct.filtering_mode, obj_flags & EMAGGED ? list(TAKE_ITEMS, TAKE_CLOSETS, TAKE_HUMANS) : list(TAKE_ITEMS, TAKE_CLOSETS)) - return TRUE - if("toggle_priority") if(!istype(target_task, /datum/manipulator_task/cargo)) return FALSE @@ -851,30 +848,6 @@ cycle_target_task.throw_range = cycle_value(cycle_target_task.throw_range, list(1, 2, 3, 4, 5, 6, 7)) return TRUE - if("cycle_worker_interaction") - var/list/vals = list(WORKER_NORMAL_USE, WORKER_SINGLE_USE, WORKER_EMPTY_USE) - if(istype(target_task, /datum/manipulator_task/cargo/dropoff_base/use)) - var/datum/manipulator_task/cargo/dropoff_base/use/cycle_target_task = target_task - cycle_target_task.worker_interaction = cycle_value(cycle_target_task.worker_interaction, vals) - return TRUE - if(istype(target_task, /datum/manipulator_task/cargo/interact)) - var/datum/manipulator_task/cargo/interact/cycle_target_task = target_task - cycle_target_task.worker_interaction = cycle_value(cycle_target_task.worker_interaction, vals) - return TRUE - return FALSE - - if("cycle_post_interaction") - var/list/vals = list(POST_INTERACTION_DROP_AT_POINT, POST_INTERACTION_DROP_AT_MACHINE, POST_INTERACTION_DROP_NEXT_FITTING, POST_INTERACTION_WAIT) - if(istype(target_task, /datum/manipulator_task/cargo/dropoff_base/use)) - var/datum/manipulator_task/cargo/dropoff_base/use/cycle_target_task = target_task - cycle_target_task.use_post_interaction = cycle_value(cycle_target_task.use_post_interaction, vals) - return TRUE - if(istype(target_task, /datum/manipulator_task/cargo/interact)) - var/datum/manipulator_task/cargo/interact/cycle_target_task = target_task - cycle_target_task.use_post_interaction = cycle_value(cycle_target_task.use_post_interaction, vals) - return TRUE - return FALSE - if("toggle_worker_rmb") if(istype(target_task, /datum/manipulator_task/cargo/dropoff_base/use)) var/datum/manipulator_task/cargo/dropoff_base/use/cycle_target_task = target_task diff --git a/code/game/machinery/big_manipulator/big_manipulator_interactions.dm b/code/game/machinery/big_manipulator/big_manipulator_interactions.dm index e5f7c361330d..a29f44a5d522 100644 --- a/code/game/machinery/big_manipulator/big_manipulator_interactions.dm +++ b/code/game/machinery/big_manipulator/big_manipulator_interactions.dm @@ -48,7 +48,7 @@ step_tasks() /// Safely schedules the next step to prevent overlapping. -/obj/machinery/big_manipulator/proc/schedule_next_cycle(time_seconds = BASE_INTERACTION_TIME) +/obj/machinery/big_manipulator/proc/schedule_next_cycle(time_seconds = BASE_TASK_DELAY) if(next_cycle_scheduled || stopping) return @@ -116,42 +116,57 @@ finish_manipulation() return TRUE -/obj/machinery/big_manipulator/proc/try_use_thing(datum/manipulator_task/cargo/interact/destination_task, work_done_at_point = FALSE) - if(stopping) +/obj/machinery/big_manipulator/proc/try_use_thing(datum/manipulator_task/cargo/interact/destination_task) + if(stopping || use_in_progress) return + use_in_progress = TRUE + var/obj/obj_resolve = held_object?.resolve() var/mob/living/carbon/human/species/monkey/monkey_resolve = monkey_worker?.resolve() var/destination_turf = destination_task.interaction_turf if(!obj_resolve || QDELETED(obj_resolve) || obj_resolve.loc != src) + use_in_progress = FALSE finish_manipulation() - return FALSE + return if(!monkey_resolve || !destination_turf) + use_in_progress = FALSE finish_manipulation() - return FALSE + return if(monkey_resolve.loc != src) + use_in_progress = FALSE finish_manipulation() - return FALSE + return var/obj/item/held_item = obj_resolve var/atom/type_to_use = destination_task.find_type_priority(destination_task.skip_anchored) if(isnull(type_to_use)) - drop_held_after_use(destination_task) - return FALSE + use_in_progress = FALSE + finish_manipulation() + return if(isitem(type_to_use) && !destination_task.check_filters_for_atom(type_to_use)) - drop_held_after_use(destination_task) - return FALSE + use_in_progress = FALSE + finish_manipulation() + return if(istype(destination_task, /datum/manipulator_task/cargo/interact) && destination_task.should_use_filters && isitem(type_to_use) && !destination_task.check_filters_for_atom(type_to_use)) - drop_held_after_use(destination_task) - return FALSE + use_in_progress = FALSE + finish_manipulation() + return var/original_loc = held_item.loc + var/turf/target_turf = get_turf(type_to_use) + + // Temporarily move monkey adjacent to target for Adjacency() checks + if(target_turf && get_turf(src) != target_turf) + var/turf/adjacent_turf = get_step(target_turf, get_dir(target_turf, get_turf(src))) + if(adjacent_turf && !isclosedturf(adjacent_turf)) + monkey_resolve.forceMove(adjacent_turf) monkey_resolve.put_in_active_hand(held_item) if(held_item.GetComponent(/datum/component/two_handed)) @@ -164,65 +179,25 @@ do_attack_animation(destination_turf) manipulator_arm.do_attack_animation(destination_turf) + restore_monkey_tracking(monkey_resolve) + if(QDELETED(held_item) || !held_item || (held_item.loc != monkey_resolve && held_item.loc != original_loc)) held_object = null manipulator_arm.update_claw(null) + use_in_progress = FALSE finish_manipulation() - return TRUE + return if(held_item.loc == monkey_resolve) held_item.forceMove(original_loc) - check_for_cycle_end_drop(destination_task, TRUE, TRUE) - -/obj/machinery/big_manipulator/proc/check_for_cycle_end_drop(datum/manipulator_task/cargo/interact/destination_task, item_used_this_iteration, work_done_at_point = FALSE) - var/obj/obj_resolve = held_object?.resolve() - var/turf/drop_turf = destination_task.interaction_turf - - if(!obj_resolve || obj_resolve.loc != src || QDELETED(obj_resolve)) - finish_manipulation() - return - - if(destination_task.worker_interaction == WORKER_SINGLE_USE && item_used_this_iteration) - obj_resolve.forceMove(drop_turf) - obj_resolve.dir = get_dir(get_turf(obj_resolve), get_turf(src)) - finish_manipulation() - return - - if(!on) - finish_manipulation() - return - - if(item_used_this_iteration) - addtimer(CALLBACK(src, PROC_REF(try_use_thing), destination_task, TRUE), BASE_INTERACTION_TIME * 2) + use_in_progress = FALSE + current_task = null + SStgui.update_uis(src) + if(stopping) + complete_stopping_task() return - - drop_held_after_use(destination_task) - -/obj/machinery/big_manipulator/proc/drop_held_after_use(datum/manipulator_task/cargo/interact/destination_task) - var/obj/obj_resolve = held_object?.resolve() - var/turf/drop_turf = destination_task.interaction_turf - - switch(destination_task.use_post_interaction) - if(POST_INTERACTION_DROP_AT_POINT) - obj_resolve.forceMove(drop_turf) - obj_resolve.dir = get_dir(get_turf(obj_resolve), get_turf(src)) - finish_manipulation() - - if(POST_INTERACTION_DROP_AT_MACHINE) - obj_resolve.forceMove(get_turf(src)) - finish_manipulation() - - if(POST_INTERACTION_DROP_NEXT_FITTING) - var/datum/manipulator_task/next = master_tasking.get_next_task(tasks, src) - if(istype(next, /datum/manipulator_task/cargo/dropoff_base)) - rotate_to_point(next, next, TYPE_PROC_REF(/datum/manipulator_task/cargo/dropoff_base, try_dropoff)) - return - obj_resolve.forceMove(drop_turf) - obj_resolve.dir = get_dir(get_turf(obj_resolve), get_turf(src)) - finish_manipulation() - else - addtimer(CALLBACK(src, PROC_REF(try_use_thing), destination_task, TRUE), BASE_INTERACTION_TIME * 2) + schedule_next_cycle() /obj/machinery/big_manipulator/proc/throw_thing(datum/manipulator_task/cargo/dropoff_base/throw/throw_task) var/drop_turf = throw_task.interaction_turf @@ -248,13 +223,20 @@ var/atom/type_to_use = destination_task.find_type_priority(destination_task.skip_anchored) if(isnull(type_to_use)) - check_end_of_use_for_use_with_empty_hand(destination_task, FALSE) + finish_manipulation() return if(destination_task.should_use_filters && isitem(type_to_use) && !destination_task.check_filters_for_atom(type_to_use)) - check_end_of_use_for_use_with_empty_hand(destination_task, FALSE) + finish_manipulation() return + // temporarily move monkey adjacent to target for Adjacency() checks + var/turf/target_turf = get_turf(type_to_use) + if(target_turf && get_turf(src) != target_turf) + var/turf/adjacent_turf = get_step(target_turf, get_dir(target_turf, get_turf(src))) + if(adjacent_turf && !isclosedturf(adjacent_turf)) + monkey_resolve.forceMove(adjacent_turf) + var/old_combat_mode = monkey_resolve.combat_mode monkey_resolve.combat_mode = destination_task.worker_combat_mode monkey_resolve.UnarmedAttack(type_to_use, modifiers = list(RIGHT_CLICK = destination_task.worker_use_rmb ? TRUE : FALSE)) @@ -265,25 +247,26 @@ do_attack_animation(dest_turf) manipulator_arm.do_attack_animation(dest_turf) - check_end_of_use_for_use_with_empty_hand(destination_task, TRUE) + restore_monkey_tracking(monkey_resolve) -/obj/machinery/big_manipulator/proc/check_end_of_use_for_use_with_empty_hand(datum/manipulator_task/cargo/interact/destination_task, item_was_used = TRUE) - if(!on || destination_task.worker_interaction != WORKER_EMPTY_USE) - finish_manipulation() - return + finish_manipulation() - if(!item_was_used) - finish_manipulation() +/// Restores monkey tracking after a temporary move outside src cleared it via Exited. +/obj/machinery/big_manipulator/proc/restore_monkey_tracking(mob/living/carbon/human/species/monkey/monkey) + if(monkey.loc != src) + monkey.forceMove(src) + if(monkey_worker?.resolve()) return - - addtimer(CALLBACK(src, PROC_REF(use_thing_with_empty_hand), destination_task), BASE_INTERACTION_TIME) + monkey_worker = WEAKREF(monkey) + manipulator_arm.vis_contents += monkey + monkey.add_offsets( + type, + x_add = 32 + manipulator_arm.calculate_item_offset(TRUE, pixels_to_offset = 16), + y_add = 32 + manipulator_arm.calculate_item_offset(FALSE, pixels_to_offset = 16) + ) /// Completes the current manipulation action and schedules the next step. /obj/machinery/big_manipulator/proc/finish_manipulation() - if(held_object) - var/obj/resolved = held_object.resolve() - if(resolved && resolved.loc == src) - resolved.forceMove(drop_location()) held_object = null manipulator_arm.update_claw(null) current_task = null diff --git a/code/game/machinery/big_manipulator/interaction_priorities.dm b/code/game/machinery/big_manipulator/interaction_priorities.dm index 0540c1f59275..ac79d2b151ea 100644 --- a/code/game/machinery/big_manipulator/interaction_priorities.dm +++ b/code/game/machinery/big_manipulator/interaction_priorities.dm @@ -35,3 +35,19 @@ /datum/manipulator_priority/interact/with_vehicles name = "USE ON VEHICLES" atom_typepath = /obj/vehicle + +/datum/manipulator_priority/interact/with_turf + name = "USE ON FLOOR" + atom_typepath = /turf + +/datum/manipulator_priority/pickup/items + name = "PICK UP ITEMS" + atom_typepath = /obj/item + +/datum/manipulator_priority/pickup/closets + name = "PICK UP CLOSETS" + atom_typepath = /obj/structure/closet + +/datum/manipulator_priority/pickup/humans + name = "PICK UP HUMANS" + atom_typepath = /mob/living/carbon/human diff --git a/code/game/machinery/big_manipulator/manipulator_tasks.dm b/code/game/machinery/big_manipulator/manipulator_tasks.dm index 00fe12715770..18ce051c9933 100644 --- a/code/game/machinery/big_manipulator/manipulator_tasks.dm +++ b/code/game/machinery/big_manipulator/manipulator_tasks.dm @@ -40,6 +40,53 @@ time_seconds = serialized_data["time_seconds"] return +// ===== STOP ===== + +/datum/manipulator_task/simple/stop + name = "stop" + var/sub_name = "" + +/datum/manipulator_task/simple/stop/New(..., serialized_data) + var/static/list/preset_sub_names = list( + "while at it.", + "and step away.", + "in the name of common sense.", + "just stop.", + "enough of this.", + ) + sub_name = pick(preset_sub_names) + if(serialized_data) + sub_name = serialized_data["sub_name"] || "" + return ..() + +/datum/manipulator_task/simple/stop/can_run(obj/machinery/big_manipulator/manipulator) + return TRUE + +/datum/manipulator_task/simple/stop/run_task(obj/machinery/big_manipulator/manipulator) + manipulator.complete_stopping_task() + +/datum/manipulator_task/simple/stop/serialize() + var/list/data = ..() + data["sub_name"] = sub_name + return data + +// ===== MOVE ===== + +/datum/manipulator_task/cargo/move + name = "move" + +/datum/manipulator_task/cargo/move/fill_priority_list(manipulator_tier) + return list() + +/datum/manipulator_task/cargo/move/can_run(obj/machinery/big_manipulator/manipulator) + return is_valid() + +/datum/manipulator_task/cargo/move/run_task(obj/machinery/big_manipulator/manipulator) + manipulator.rotate_to_point(src, src, PROC_REF(try_move)) + +/datum/manipulator_task/cargo/move/proc/try_move(obj/machinery/big_manipulator/manipulator) + manipulator.schedule_next_cycle() + // ===== BASE CARGO ===== /datum/manipulator_task/cargo @@ -48,11 +95,6 @@ var/offset_dy var/should_use_filters = FALSE var/list/atom_filters = list() - var/filtering_mode = TAKE_ITEMS - var/list/type_filters = list( - /obj/item, - /obj/structure/closet, - ) var/list/interaction_priorities = list() /datum/manipulator_task/cargo/New(turf/new_turf, manipulator_tier, serialized_data) @@ -66,8 +108,6 @@ should_use_filters = !!serialized_data["should_use_filters"] atom_filters = serialized_data["atom_filters"] || list() - filtering_mode = serialized_data["filtering_mode"] - type_filters = serialized_data["type_filters"] || list() var/list/prios_data = serialized_data["interaction_priorities"] if(islist(prios_data)) @@ -166,19 +206,12 @@ if(!target || target.anchored || HAS_TRAIT(target, TRAIT_NODROP)) return FALSE - switch(filtering_mode) - if(TAKE_CLOSETS) - return iscloset(target) - if(TAKE_HUMANS) - return ishuman(target) - if(TAKE_ITEMS) - if(!should_use_filters) - return isitem(target) - for(var/filter_path in atom_filters) - if(istype(target, filter_path)) - return TRUE - return FALSE + if(!should_use_filters) + return isitem(target) + for(var/filter_path in atom_filters) + if(istype(target, filter_path)) + return TRUE return FALSE /datum/manipulator_task/cargo/can_run(obj/machinery/big_manipulator/manipulator) @@ -192,8 +225,6 @@ ) data["should_use_filters"] = should_use_filters data["atom_filters"] = atom_filters - data["filtering_mode"] = filtering_mode - data["type_filters"] = type_filters data["interaction_priorities"] = list() for(var/datum/manipulator_priority/prio as anything in interaction_priorities) data["interaction_priorities"] += list(list( @@ -215,7 +246,40 @@ var/pickup_eagerness = PICKUP_CAN_WAIT /datum/manipulator_task/cargo/pickup/fill_priority_list(manipulator_tier) - return list() + var/list/priorities = list( + new /datum/manipulator_priority/pickup/items, + new /datum/manipulator_priority/pickup/closets, + ) + if(manipulator_tier >= 4) + priorities += new /datum/manipulator_priority/pickup/humans + return priorities + +/datum/manipulator_task/cargo/pickup/check_filters_for_atom(atom/movable/target) + if(!target || target.anchored || HAS_TRAIT(target, TRAIT_NODROP)) + return FALSE + + var/matched_priority = FALSE + for(var/datum/manipulator_priority/pickup/prio in interaction_priorities) + if(!prio.active) + continue + if(istype(target, prio.atom_typepath)) + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + if(human_target.stat == DEAD) + continue + matched_priority = TRUE + break + + if(!matched_priority) + return FALSE + + if(should_use_filters && length(atom_filters)) + for(var/filter_path in atom_filters) + if(istype(target, filter_path)) + return TRUE + return FALSE + + return TRUE /datum/manipulator_task/cargo/pickup/can_run(obj/machinery/big_manipulator/manipulator) if(!..()) @@ -412,8 +476,6 @@ /datum/manipulator_task/cargo/dropoff_base/use name = "use" - var/worker_interaction = WORKER_NORMAL_USE - var/use_post_interaction = POST_INTERACTION_DROP_AT_POINT var/worker_combat_mode = FALSE var/worker_use_rmb = FALSE var/skip_anchored = FALSE @@ -424,6 +486,7 @@ new /datum/manipulator_priority/interact/with_structure, new /datum/manipulator_priority/interact/with_machinery, new /datum/manipulator_priority/interact/with_items, + new /datum/manipulator_priority/interact/with_turf, ) if(manipulator_tier == 4) priorities += new /datum/manipulator_priority/interact/with_vehicles @@ -438,8 +501,6 @@ /datum/manipulator_task/cargo/dropoff_base/use/serialize() var/list/data = ..() - data["worker_interaction"] = worker_interaction - data["use_post_interaction"] = use_post_interaction data["worker_combat_mode"] = worker_combat_mode data["worker_use_rmb"] = worker_use_rmb data["skip_anchored"] = skip_anchored @@ -448,8 +509,6 @@ /datum/manipulator_task/cargo/dropoff_base/use/New(turf/new_turf, manipulator_tier, serialized_data) ..(new_turf, manipulator_tier, serialized_data) if(serialized_data) - worker_interaction = serialized_data["worker_interaction"] - use_post_interaction = serialized_data["use_post_interaction"] worker_combat_mode = !!serialized_data["worker_combat_mode"] worker_use_rmb = !!serialized_data["worker_use_rmb"] skip_anchored = !!serialized_data["skip_anchored"] @@ -462,8 +521,6 @@ /datum/manipulator_task/cargo/interact name = "interact" - var/worker_interaction = WORKER_EMPTY_USE - var/use_post_interaction = POST_INTERACTION_DROP_AT_POINT var/worker_combat_mode = FALSE var/worker_use_rmb = FALSE var/skip_anchored = FALSE @@ -474,6 +531,7 @@ new /datum/manipulator_priority/interact/with_structure, new /datum/manipulator_priority/interact/with_machinery, new /datum/manipulator_priority/interact/with_items, + new /datum/manipulator_priority/interact/with_turf, ) if(manipulator_tier == 4) priorities += new /datum/manipulator_priority/interact/with_vehicles @@ -489,8 +547,6 @@ /datum/manipulator_task/cargo/interact/serialize() var/list/data = ..() - data["worker_interaction"] = worker_interaction - data["use_post_interaction"] = use_post_interaction data["worker_combat_mode"] = worker_combat_mode data["worker_use_rmb"] = worker_use_rmb data["skip_anchored"] = skip_anchored @@ -499,8 +555,6 @@ /datum/manipulator_task/cargo/interact/New(turf/new_turf, manipulator_tier, serialized_data) ..(new_turf, manipulator_tier, serialized_data) if(serialized_data) - worker_interaction = serialized_data["worker_interaction"] - use_post_interaction = serialized_data["use_post_interaction"] worker_combat_mode = !!serialized_data["worker_combat_mode"] worker_use_rmb = !!serialized_data["worker_use_rmb"] skip_anchored = !!serialized_data["skip_anchored"] diff --git a/tgui/packages/tgui/interfaces/BigManipulator/index.tsx b/tgui/packages/tgui/interfaces/BigManipulator/index.tsx index e56d1dd431f8..6595f2afc2f1 100644 --- a/tgui/packages/tgui/interfaces/BigManipulator/index.tsx +++ b/tgui/packages/tgui/interfaces/BigManipulator/index.tsx @@ -23,14 +23,20 @@ const TASK_TYPE_LABELS: Record = { throw: 'Throw...', use: 'Use held...', interact: 'Interact...', + move: 'Move...', wait: 'Wait...', + stop: 'Stop...', }; const TASK_TYPE_ICONS: Record = { pickup: 'hand', drop: 'box-open', + throw: 'paper-plane', + use: 'mouse-pointer', interact: 'bolt', + move: 'arrows-alt', wait: 'hourglass-half', + stop: 'stop-circle', }; const TASKING_STRATEGY_ICONS: Record = { @@ -150,15 +156,6 @@ const getPointButtonNumber = (offset: string): number | null => { return yIndex * 3 + xIndex + 1; }; -const getFilteringModeText = (mode: number) => { - switch (mode) { - case 1: return 'Items'; - case 2: return 'Closets'; - case 3: return 'Humans'; - default: return 'Unknown'; - } -}; - type TaskEditModalProps = { task: ManipulatorTask; onClose: () => void; @@ -175,6 +172,7 @@ function TaskEditModal(props: TaskEditModalProps) { const isPickup = task.task_type.includes('pickup'); const isDropoff = task.task_type === 'drop' || task.task_type === 'throw' || task.task_type === 'use'; const isInteract = task.task_type.includes('interact'); + const isMove = task.task_type === 'move'; const currentButton = task.turf ? getPointButtonNumber(task.turf) @@ -208,7 +206,7 @@ function TaskEditModal(props: TaskEditModalProps) { )} - {isCargo && ( + {isCargo && ( - {(task.task_type === 'pickup' || - task.task_type === 'drop' || - task.task_type === 'throw') && ( + {!isMove && ( adjust('cycle_filtering_mode')} - tooltip="Cycle object category" + label="Use Filters" + content={task.filters_status ? 'TRUE' : 'FALSE'} + onClick={() => adjust('toggle_filter_skip')} + tooltip="Toggle filter usage" /> )} - adjust('toggle_filter_skip')} - tooltip="Toggle filter usage" - /> {isPickup && ( - adjust('cycle_worker_interaction')} - tooltip="Normal / Single use / Empty hand" - /> adjust('toggle_skip_anchored')} tooltip="Skip anchored objects when looking for interaction targets" /> - adjust('cycle_post_interaction')} - tooltip="What to do when nothing left to interact with" - /> )}
    @@ -320,7 +298,7 @@ function TaskEditModal(props: TaskEditModalProps) { )}

    Ot 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