From 0f5d14e68b19111592301efe52a03de80aced61e Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Wed, 11 Oct 2023 07:41:32 +0300 Subject: [PATCH] Mook village and basic mook refactor (#78789) ## About The Pull Request refactors mooks into basic mooks and re-adds them to the game ## Why It's Good For The Game this refactors mooks into basic mobs and re adds them to the game. mooks are now a part of lavaland. they come as a part of a random ruin which consist of a entire village of friendly mooks. Mooks will aid players but they will still attack ashwalkers because of some troubled history between them. ![mookvillage](https://github.com/tgstation/tgstation/assets/138636438/ad1c5d63-c168-475a-a85d-b727dff43e7b) mooks are a very diseased specie. nanotrasen discovered a small tribe of mooks and cut a deal with their tribal chief to aid ss13 miners in exchange for medical supplies. tribal chief in his decked out home ![tribalchief](https://github.com/tgstation/tgstation/assets/138636438/cc0e0a11-9bf0-4322-b3ae-c7be43092ee8) male mooks go out and mine and haul ore off back to their village. they will deposit ores into a stand which is managed by another mook. they will all return to their village to rest once a lavastorm comes. ![mookstand](https://github.com/tgstation/tgstation/assets/138636438/c7adbf4e-6322-4347-acfc-4e8d45aff798) players can use this stand to withdraw any ore they like ![mookui](https://github.com/tgstation/tgstation/assets/138636438/a1318be8-50f7-49b2-827c-97bafdb2488a) female mooks will stay behind in the village to guard it from ashwalkers. they will also heal male mooks when they come back from a long day of work. the tribal chief is a bum and chooses not to go out and mine. he will stay behind in the village and issue commands to his people rather than work the village also has its own bard! he follows player visitors and plays nice music for them while they are in the village (although he is not very talented). ![mookbard](https://github.com/tgstation/tgstation/assets/138636438/5123e492-6657-4755-9dc7-ab94d4beb554) he is still a warrior at heart tho so he will be smashing his guitar over ashwalker skull ![mookshmash](https://github.com/tgstation/tgstation/assets/138636438/bf211bf0-e963-4dbb-b004-e653e06e3974) ## Changelog :cl: refactor: mooks are now basic mobs. please report any bugs feature: added mook village to lavaland ruins! /:cl: --- .../lavaland_surface_mookvillage.dmm | 643 ++++++++++++++++++ code/__DEFINES/ai/monsters.dm | 20 + code/__DEFINES/basic_mobs.dm | 7 + code/__DEFINES/is_helpers.dm | 1 + code/__DEFINES/song.dm | 2 + .../basic_ai_behaviors/find_mineable_wall.dm | 33 - .../basic_mobs/basic_subtrees/mine_walls.dm | 73 ++ .../ai/idle_behaviors/idle_random_walk.dm | 41 ++ code/datums/components/pet_commands/fetch.dm | 2 + code/datums/ruins/lavaland.dm | 8 + .../basic/lavaland/goldgrub/goldgrub_ai.dm | 59 +- .../mob/living/basic/lavaland/mook/mook.dm | 273 ++++++++ .../basic/lavaland/mook/mook_abilities.dm | 140 ++++ .../mob/living/basic/lavaland/mook/mook_ai.dm | 426 ++++++++++++ .../basic/lavaland/mook/mook_village.dm | 85 +++ .../mob/living/basic/minebots/minebot_ai.dm | 6 +- .../simple_animal/hostile/jungle/mook.dm | 229 ------- .../unit_tests/simple_animal_freeze.dm | 1 - config/lavaruinblacklist.txt | 26 +- icons/mob/simple/jungle/mook.dmi | Bin 23590 -> 36875 bytes tgstation.dme | 7 +- .../tgui/interfaces/MaterialStand.tsx | 120 ++++ 22 files changed, 1872 insertions(+), 330 deletions(-) create mode 100644 _maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/find_mineable_wall.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm create mode 100644 code/modules/mob/living/basic/lavaland/mook/mook.dm create mode 100644 code/modules/mob/living/basic/lavaland/mook/mook_abilities.dm create mode 100644 code/modules/mob/living/basic/lavaland/mook/mook_ai.dm create mode 100644 code/modules/mob/living/basic/lavaland/mook/mook_village.dm delete mode 100644 code/modules/mob/living/simple_animal/hostile/jungle/mook.dm create mode 100644 tgui/packages/tgui/interfaces/MaterialStand.tsx diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm new file mode 100644 index 00000000000..5db8dd37a24 --- /dev/null +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm @@ -0,0 +1,643 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"b" = ( +/obj/structure/bonfire/prelit, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"c" = ( +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"d" = ( +/obj/structure/railing{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"e" = ( +/obj/structure/flora/ash/fireblossom, +/mob/living/basic/mining/mook/worker/tribal_chief, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"f" = ( +/obj/structure/chair/wood{ + dir = 1 + }, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"g" = ( +/obj/structure/bed/maint, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"h" = ( +/turf/closed/wall/mineral/wood, +/area/lavaland/surface/outdoors) +"i" = ( +/obj/structure/chair/wood, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"j" = ( +/obj/structure/closet/cabinet, +/obj/item/food/grown/banana, +/obj/item/food/meat/slab/goliath, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"k" = ( +/obj/structure/table/wood/shuttle_bar, +/obj/item/flashlight/flare/candle/infinite, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"l" = ( +/obj/structure/flora/ash/fireblossom, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"m" = ( +/obj/item/flashlight/lantern{ + on = 1 + }, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"n" = ( +/obj/structure/flora/ash/fireblossom, +/mob/living/basic/mining/mook/worker, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"o" = ( +/obj/structure/railing/corner, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"p" = ( +/obj/structure/material_stand, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"q" = ( +/obj/item/fishing_rod, +/obj/structure/bed/maint, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"r" = ( +/turf/open/floor/carpet/lone, +/area/lavaland/surface/outdoors) +"s" = ( +/obj/effect/landmark/mook_village, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"t" = ( +/obj/structure/chair/pew/left, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"u" = ( +/obj/structure/railing, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"v" = ( +/obj/structure/railing/corner{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"w" = ( +/obj/structure/railing{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"x" = ( +/obj/structure/table/wood/shuttle_bar, +/obj/item/clothing/head/costume/garland/poppy, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"y" = ( +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"z" = ( +/mob/living/basic/mining/mook/worker/bard, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"A" = ( +/obj/structure/chair/pew/right, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"C" = ( +/obj/structure/sign/nanotrasen, +/turf/closed/wall/mineral/wood, +/area/lavaland/surface/outdoors) +"E" = ( +/obj/structure/flora/grass/jungle/b, +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"F" = ( +/obj/structure/chair/pew, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"G" = ( +/obj/structure/bed/pod, +/obj/item/bedsheet/nanotrasen, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"H" = ( +/obj/structure/railing/corner{ + dir = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"I" = ( +/mob/living/basic/mining/mook, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"J" = ( +/obj/structure/railing{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"M" = ( +/obj/structure/chair/pew/right{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"O" = ( +/obj/structure/table/wood/shuttle_bar, +/obj/item/hatchet/wooden, +/obj/item/storage/belt/champion/wrestling, +/turf/open/floor/holofloor/wood, +/area/lavaland/surface/outdoors) +"P" = ( +/mob/living/basic/mining/mook/worker, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"Q" = ( +/obj/structure/flora/tree/jungle/style_5, +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"S" = ( +/obj/item/flashlight/lantern{ + on = 1 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"V" = ( +/obj/structure/chair/pew{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"W" = ( +/obj/structure/flora/tree/jungle/style_3, +/turf/open/misc/grass/jungle, +/area/lavaland/surface/outdoors) +"Y" = ( +/obj/structure/chair/pew/left{ + dir = 8 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) +"Z" = ( +/obj/structure/railing/corner{ + dir = 4 + }, +/turf/open/misc/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) + +(1,1,1) = {" +a +a +a +a +a +a +a +l +a +a +a +a +a +a +a +a +a +a +a +a +"} +(2,1,1) = {" +a +a +a +a +a +a +l +S +a +a +a +a +a +a +a +t +a +a +a +a +"} +(3,1,1) = {" +a +a +a +a +a +a +a +a +a +a +p +a +a +a +a +F +a +a +a +a +"} +(4,1,1) = {" +h +h +h +h +h +h +a +s +a +a +a +a +a +a +a +F +a +b +a +a +"} +(5,1,1) = {" +c +c +j +g +c +c +a +S +a +a +a +a +P +a +S +F +a +a +a +a +"} +(6,1,1) = {" +c +m +c +c +c +c +r +n +a +P +a +a +a +a +a +A +a +a +a +a +"} +(7,1,1) = {" +c +c +c +c +c +c +r +a +I +a +a +a +a +a +l +e +Y +V +V +M +"} +(8,1,1) = {" +c +i +k +f +c +c +a +a +a +o +w +w +w +Z +I +a +a +a +a +a +"} +(9,1,1) = {" +h +h +h +h +h +h +a +a +a +u +y +y +Q +d +a +a +a +P +a +a +"} +(10,1,1) = {" +a +a +a +a +a +a +a +I +a +u +y +y +E +d +a +a +l +a +a +a +"} +(11,1,1) = {" +a +a +l +l +a +a +a +a +a +u +y +W +E +d +a +a +a +a +a +a +"} +(12,1,1) = {" +a +a +a +a +a +P +a +a +a +u +y +y +Q +d +a +a +a +a +a +a +"} +(13,1,1) = {" +a +a +a +a +a +a +a +a +a +v +J +J +J +H +a +a +P +a +a +a +"} +(14,1,1) = {" +h +h +h +h +h +h +h +z +S +l +a +a +I +l +l +a +I +a +a +l +"} +(15,1,1) = {" +g +g +g +g +q +g +g +l +a +a +a +a +a +a +a +a +a +a +a +a +"} +(16,1,1) = {" +c +c +c +c +c +c +c +r +a +a +a +a +h +c +c +c +h +a +a +a +"} +(17,1,1) = {" +c +c +c +m +c +c +c +r +a +P +a +a +h +O +c +c +h +a +a +a +"} +(18,1,1) = {" +g +g +g +g +g +g +g +a +a +a +a +a +C +x +c +c +h +a +a +a +"} +(19,1,1) = {" +h +h +h +h +h +h +h +a +a +l +a +S +h +m +c +G +h +a +a +a +"} +(20,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +h +c +c +c +h +a +a +a +"} diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 89670d4f346..2867ba4a6fc 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -169,3 +169,23 @@ #define BB_LEGION_RECENT_LINES "legion_recent_lines" /// The creator of our legion skull #define BB_LEGION_BROOD_CREATOR "legion_brood_creator" + +//mook keys +/// our home landmark +#define BB_HOME_VILLAGE "home_village" +/// maximum distance we can be from home during a storm +#define BB_MAXIMUM_DISTANCE_TO_VILLAGE "maximum_distance_to_village" +/// stand where we deposit our ores +#define BB_MATERIAL_STAND_TARGET "material_stand_target" +/// our jump ability +#define BB_MOOK_JUMP_ABILITY "mook_jump_ability" +/// our leap ability +#define BB_MOOK_LEAP_ABILITY "mook_leap_ability" +/// the chief we must obey +#define BB_MOOK_TRIBAL_CHIEF "mook_tribal_chief" +/// the injured mook we must heal +#define BB_INJURED_MOOK "injured_mook" +/// the player we will follow and play music for +#define BB_MOOK_MUSIC_AUDIENCE "music_audience" +/// the bonfire we will light up +#define BB_MOOK_BONFIRE_TARGET "bonfire_target" diff --git a/code/__DEFINES/basic_mobs.dm b/code/__DEFINES/basic_mobs.dm index 5a4aebaee23..6c8a3022e8f 100644 --- a/code/__DEFINES/basic_mobs.dm +++ b/code/__DEFINES/basic_mobs.dm @@ -19,3 +19,10 @@ /// Above this speed we stop gliding because it looks silly #define END_GLIDE_SPEED 10 + +///mook attack status flags +#define MOOK_ATTACK_NEUTRAL 0 +#define MOOK_ATTACK_WARMUP 1 +#define MOOK_ATTACK_ACTIVE 2 +#define MOOK_ATTACK_STRIKE 3 + diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index f6ef0a45c80..136e0b922ce 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -92,6 +92,7 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define isabductor(A) (is_species(A, /datum/species/abductor)) #define isgolem(A) (is_species(A, /datum/species/golem)) #define islizard(A) (is_species(A, /datum/species/lizard)) +#define isashwalker(A) (is_species(A, /datum/species/lizard/ashwalker)) #define isplasmaman(A) (is_species(A, /datum/species/plasmaman)) #define ispodperson(A) (is_species(A, /datum/species/pod)) #define isflyperson(A) (is_species(A, /datum/species/fly)) diff --git a/code/__DEFINES/song.dm b/code/__DEFINES/song.dm index 7af269fbe21..782a7923ea1 100644 --- a/code/__DEFINES/song.dm +++ b/code/__DEFINES/song.dm @@ -13,3 +13,5 @@ ///it's what monkeys play! #define MONKEY_SONG "BPM: 200\nC4/0,14,C,A4-F2,F3,A3,F-F2,A-F,F4,G4,F,D4-Bb2-G2\nD3,G3,D-G2,G3-G2,D,D4-G3,D,B4-B2,G,B3,G-B2,B3-B2\nG4,A4,G,E4-C3,E3,G3,E-C,G-C,E,E4-G,E,C5-E-A3,C4\nA-E3,C,E4-C3,A4-C4,B4-A3-A2,C5-C4,D5-F-B3,D4,B-F3\nD,F4-D3,D4,F-B-B2,G4-D,A4-C-F3,F,C/2,B3/2,A3-C3/2\nB/2,C4,E-C3,F4,G-C,F-F3,F-C,C4/2,B/2,A-A2/2,G3/2\nF/I" +///song played by the mook bard +#define MOOK_SONG "BPM: 240\nA5,B5,C#6,D6,E6/0.17,A/0.5,A/0.25,A3/0.25\nA4/0.25,C#5/0.25,E5/0.25,A/0.25,C#/0.25,E/0.12\nC#6/0.25,C#/0.25,E6/0.25,A3/0.25,A4/0.25\nC#5/0.25,E5/0.25,A/0.25,C#/0.25,E/0.25,D/0.25\nG6/0.25,D/0.17,F6/0.17,C#6/0.5,E6/0.5,D4/0.25\nA/0.25,D5/0.25,F5/0.25,A/0.25,D/0.25,F/0.25\nD6/0.08,F6/0.08,D4/0.25,A/0.25,D5/0.25,F5/0.25\nCn4/0.2,B/0.17,D6/0.17,G5/0.5,G/0.25,B3/0.25\nD4/0.25,G4/0.25,B4/0.25,D/0.25,G/0.25,B/0.12\nB5/0.25,B/0.25,D6/0.25,G3/0.25,G4/0.25,B4/0.25\nF/0.25,G/0.25,B/0.25,F/0.25,D/0.25,F6/0.25\nC6/0.17,E/0.17,B5/0.5,D#/0.5,C4/0.25,G/0.25\nC5/0.25,E5/0.25,G/0.25,C/0.25,E/0.25,C6/0.08\nE6/0.08,C4/0.25,Dn4/0.25,E4/0.25,A5/0.17,B/0.5\nC6/0.25,F5/0.08,F4/0.08,C5/0.08,E5/0.12,G5/0.12\nC6/0.25,E6/0.25,E4/0.08,C5/0.08,B/0.17,F6/0.17\nE6/0.5,B/0.25,E4/0.08,G#4/0.08,C6/0.17,D6/0.5\nE6/0.25,A3/0.25,E4/0.25,C5/0.25,Gn3/0.25\nF5/0.12,A5/0.12,A6/0.25,F3/0.25,F4/0.12,A4/0.12\nC/0.12,F6/0.17,A6/0.17,G#6/0.5,A/0.25,F3/0.25\nF4/0.12,A4/0.12,D#5/0.12,B/0.17,G#/0.17,B6/0.5\nB5/0.25,G#/0.25,E3/0.25,E4/0.12,G#4/0.12\nDn/0.12,E6/0.08,E3/0.25,F#3/0.25,G#3/0.25\nE5/0.17,A5/0.17,E/0.5,E/0.25,A3/0.25,C#4/0.25\nE4/0.25,C#/0.25,E/0.12,A5/0.5,B/0.5,C#6/0.5\nD6/0.5,A3/0.25,C#4/0.25,E/0.25,C#/0.25,E/0.25\nE6/0.08,Gn/0.25,E4/0.25,A4/0.25,C#5/0.25,E/0.25\nA/0.25,C#/0.25,E6/0.17,E/0.5,Fn6/0.5,G6/0.5\nG3/0.25,E4/0.25,A/0.25,C#/0.25,E/0.25,A/0.25\nC#/0.25,F/0.08,A6/0.08,F3/0.25,F4/0.25,A4/0.25\nCn/0.25,F/0.25,A/0.25,C/0.25,G6/0.12,A6/0.12\nG A G F6 G3/0.25 D4/0.25 G4/0.25 B4/0.25 D/0.25\nG/0.25 B/0.25 E6/0.12 G6/0.12 F/0.71 G/0.71 F/0.71\nE3/0.25 E4/0.25 G4/0.25 B/0.25 E/0.25 G/0.25 B/0.25\nA5/0.08 E6/0.08 A3/0.25 E4/0.25 A4/0.25 C#/0.25 E/0.25 A/0.25 C#/0.25 D6/0.17 E6/0.5 F/0.25 B3/0.25 D4/0.12 F4/0.12 B4/0.12 F6/0.25 E/0.25 D6/0.25 G#3/0.25 E4/0.12 G#4/0.12 B/0.12 Cn6/0.12 D/0.25 A3/0.25 A4/0.25 C5/0.25 E5/0.25 G#3/0.25 Gn/0.25 C4/0.25 E4/0.25 A/" diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/find_mineable_wall.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_mineable_wall.dm deleted file mode 100644 index ad5749c9161..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/find_mineable_wall.dm +++ /dev/null @@ -1,33 +0,0 @@ -//behavior to find mineable mineral walls -/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! - finish_action(controller, TRUE) - return - - finish_action(controller, FALSE) - -/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/mine_walls.dm b/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm new file mode 100644 index 00000000000..3c03702b699 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm @@ -0,0 +1,73 @@ +//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(QDELETED(target)) + finish_action(controller, FALSE, target_key) + return + living_pawn.melee_attack(target) + if(is_gibtonite_turf) + living_pawn.manual_emote("sighs...") //accept whats about to happen to us + + finish_action(controller, TRUE, target_key) + return + +/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! + finish_action(controller, TRUE) + return + + finish_action(controller, FALSE) + +/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/idle_behaviors/idle_random_walk.dm b/code/datums/ai/idle_behaviors/idle_random_walk.dm index d5a3972a3d0..d99957f419b 100644 --- a/code/datums/ai/idle_behaviors/idle_random_walk.dm +++ b/code/datums/ai/idle_behaviors/idle_random_walk.dm @@ -24,3 +24,44 @@ if (!controller.blackboard_key_exists(target_key)) 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()) + 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/components/pet_commands/fetch.dm b/code/datums/components/pet_commands/fetch.dm index a8723f8bd4b..ae33983e1d0 100644 --- a/code/datums/components/pet_commands/fetch.dm +++ b/code/datums/components/pet_commands/fetch.dm @@ -18,6 +18,8 @@ /datum/pet_command/point_targetting/fetch/New(mob/living/parent) . = ..() + if(isnull(parent)) + return parent.AddElement(/datum/element/ai_held_item) // We don't remove this on destroy because they might still be holding something /datum/pet_command/point_targetting/fetch/add_new_friend(mob/living/tamer) diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm index e88456fe60a..7d9af70fb57 100644 --- a/code/datums/ruins/lavaland.dm +++ b/code/datums/ruins/lavaland.dm @@ -283,3 +283,11 @@ suffix = "lavaland_battle_site.dmm" allow_duplicates = TRUE cost = 3 + +/datum/map_template/ruin/lavaland/mook_village + name = "Mook Village" + id = "mook_village" + description = "A village hosting a community of friendly mooks!" + suffix = "lavaland_surface_mookvillage.dmm" + allow_duplicates = FALSE + cost = 5 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 3c652f888c2..fe1c4150315 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm @@ -13,9 +13,9 @@ /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/dig_away_from_danger, /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_and_hunt_target/consume_ores, + /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores, /datum/ai_planning_subtree/find_and_hunt_target/baby_egg, - /datum/ai_planning_subtree/grub_mine, + /datum/ai_planning_subtree/mine_walls, ) /datum/ai_controller/basic_controller/babygrub @@ -32,29 +32,29 @@ planning_subtrees = list( /datum/ai_planning_subtree/simple_find_target, /datum/ai_planning_subtree/dig_away_from_danger, - /datum/ai_planning_subtree/find_and_hunt_target/consume_ores, + /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores, /datum/ai_planning_subtree/flee_target, /datum/ai_planning_subtree/look_for_adult, ) ///consume food! -/datum/ai_planning_subtree/find_and_hunt_target/consume_ores +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores target_key = BB_ORE_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores - finding_behavior = /datum/ai_behavior/find_hunt_target/consume_ores + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/hunt_ores + finding_behavior = /datum/ai_behavior/find_hunt_target/hunt_ores hunt_targets = list(/obj/item/stack/ore) hunt_chance = 75 hunt_range = 9 -/datum/ai_behavior/find_hunt_target/consume_ores +/datum/ai_behavior/find_hunt_target/hunt_ores -/datum/ai_behavior/find_hunt_target/consume_ores/valid_dinner(mob/living/basic/source, obj/item/stack/ore/target, radius) +/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(target in source) + if(!isturf(target.loc)) return FALSE var/obj/item/pet_target = source.ai_controller.blackboard[BB_CURRENT_PET_TARGET] @@ -63,7 +63,7 @@ return can_see(source, target, radius) -/datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores +/datum/ai_behavior/hunt_target/unarmed_attack_target/hunt_ores always_reset_target = TRUE ///find our child's egg and pull it! @@ -121,45 +121,6 @@ /datum/ai_behavior/use_mob_ability/burrow behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION -///mine walls to look for food! -/datum/ai_planning_subtree/grub_mine - -/datum/ai_planning_subtree/grub_mine/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(/datum/ai_behavior/find_mineral_wall, 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(isnull(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(QDELETED(target)) - finish_action(controller, FALSE, target_key) - return - living_pawn.melee_attack(target) - if(is_gibtonite_turf) - living_pawn.manual_emote("sighs...") //accept whats about to happen to us - - finish_action(controller, TRUE, target_key) - return - -/datum/ai_behavior/mine_wall/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - /datum/pet_command/grub_spit command_name = "Spit" command_desc = "Ask your grub pet to spit out its ores." diff --git a/code/modules/mob/living/basic/lavaland/mook/mook.dm b/code/modules/mob/living/basic/lavaland/mook/mook.dm new file mode 100644 index 00000000000..da833437715 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook.dm @@ -0,0 +1,273 @@ +//Fragile but highly aggressive wanderers that pose a large threat in numbers. +//They'll attempt to leap at their target from afar using their hatchets. +/mob/living/basic/mining/mook + name = "wanderer" + desc = "This unhealthy looking primitive seems to be talented at administiring health care." + icon = 'icons/mob/simple/jungle/mook.dmi' + icon_state = "mook" + icon_living = "mook" + icon_dead = "mook_dead" + mob_biotypes = MOB_ORGANIC|MOB_HUMANOID + gender = FEMALE + maxHealth = 150 + faction = list(FACTION_MINING, FACTION_NEUTRAL) + health = 150 + move_resist = MOVE_FORCE_OVERPOWERING + melee_damage_lower = 8 + melee_damage_upper = 8 + pass_flags_self = LETPASSTHROW + attack_sound = 'sound/weapons/rapierhit.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + death_sound = 'sound/voice/mook_death.ogg' + ai_controller = /datum/ai_controller/basic_controller/mook/support + speed = 5 + + pixel_x = -16 + base_pixel_x = -16 + pixel_y = -16 + base_pixel_y = -16 + + ///the state of combat we are in + var/attack_state = MOOK_ATTACK_NEUTRAL + ///are we a healer? + var/is_healer = TRUE + ///the ore we are holding if any + var/obj/held_ore + ///overlay for neutral stance + var/mutable_appearance/neutral_stance + ///overlay for attacking stance + var/mutable_appearance/attack_stance + ///overlay when we hold an ore + var/mutable_appearance/ore_overlay + ///commands we obey + var/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/point_targetting/attack, + /datum/pet_command/point_targetting/fetch, + ) + +/mob/living/basic/mining/mook/Initialize(mapload) + . = ..() + AddComponent(/datum/component/ai_retaliate_advanced, CALLBACK(src, PROC_REF(attack_intruder))) + var/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/jump = new(src) + jump.Grant(src) + ai_controller.set_blackboard_key(BB_MOOK_JUMP_ABILITY, jump) + + ore_overlay = mutable_appearance(icon, "mook_ore_overlay") + + AddComponent(/datum/component/ai_listen_to_weather) + AddElement(/datum/element/wall_smasher) + RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + RegisterSignal(src, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(drop_ore)) + + if(is_healer) + grant_healer_abilities() + + AddComponent(/datum/component/obeys_commands, pet_commands) + +/mob/living/basic/mining/mook/proc/grant_healer_abilities() + AddComponent(\ + /datum/component/healing_touch,\ + heal_brute = melee_damage_upper,\ + heal_burn = melee_damage_upper,\ + heal_time = 0,\ + valid_targets_typecache = typecacheof(list(/mob/living/basic/mining/mook)),\ + ) + +/mob/living/basic/mining/mook/Entered(atom/movable/mover) + if(istype(mover, /obj/item/stack/ore)) + held_ore = mover + update_appearance(UPDATE_OVERLAYS) + + return ..() + +/mob/living/basic/mining/mook/Exited(atom/movable/mover) + . = ..() + if(held_ore != mover) + return + held_ore = null + update_appearance(UPDATE_OVERLAYS) + +/mob/living/basic/mining/mook/proc/pre_attack(mob/living/attacker, atom/target) + SIGNAL_HANDLER + + return attack_sequence(target) + +/mob/living/basic/mining/mook/proc/attack_sequence(atom/target) + if(istype(target, /obj/item/stack/ore) && isnull(held_ore)) + var/obj/item/ore_target = target + ore_target.forceMove(src) + return COMPONENT_HOSTILE_NO_ATTACK + + if(istype(target, /obj/structure/material_stand)) + if(held_ore) + held_ore.forceMove(target) + return COMPONENT_HOSTILE_NO_ATTACK + + if(istype(target, /obj/structure/bonfire)) + var/obj/structure/bonfire/fire_target = target + if(!fire_target.burning) + fire_target.start_burning() + return COMPONENT_HOSTILE_NO_ATTACK + +/mob/living/basic/mining/mook/proc/change_combatant_state(state) + attack_state = state + update_appearance() + +/mob/living/basic/mining/mook/Destroy() + QDEL_NULL(held_ore) + return ..() + +/mob/living/basic/mining/mook/update_icon_state() + . = ..() + if(stat == DEAD) + return + switch(attack_state) + if(MOOK_ATTACK_NEUTRAL) + icon_state = "mook" + if(MOOK_ATTACK_WARMUP) + icon_state = "mook_warmup" + if(MOOK_ATTACK_ACTIVE) + icon_state = "mook_leap" + if(MOOK_ATTACK_STRIKE) + icon_state = "mook_strike" + +/mob/living/basic/mining/mook/update_overlays() + . = ..() + if(stat == DEAD) + return + + if(attack_state != MOOK_ATTACK_NEUTRAL || isnull(held_ore)) + return + + . += ore_overlay + +/mob/living/basic/mining/mook/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) + change_combatant_state(state = MOOK_ATTACK_ACTIVE) + return ..() + +/mob/living/basic/mining/mook/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + change_combatant_state(state = MOOK_ATTACK_NEUTRAL) + +/mob/living/basic/mining/mook/CanAllowThrough(atom/movable/mover, border_dir) + . = ..() + + if(!istype(mover, /mob/living/basic/mining/mook)) + return FALSE + + var/mob/living/basic/mining/mook/mook_moover = mover + if(mook_moover.attack_state == MOOK_ATTACK_ACTIVE) + return TRUE + +/mob/living/basic/mining/mook/proc/drop_ore(mob/living/user) + SIGNAL_HANDLER + + if(isnull(held_ore)) + return + dropItemToGround(held_ore) + return COMSIG_KB_ACTIVATED + +/mob/living/basic/mining/mook/death() + desc = "A deceased primitive. Upon closer inspection, it was suffering from severe cellular degeneration and its garments are machine made..." //Can you guess the twist + return ..() + +/mob/living/basic/mining/mook/proc/attack_intruder(mob/living/intruder) + if(istype(intruder, /mob/living/basic/mining/mook)) + return + for(var/mob/living/basic/mining/mook/villager in oview(src, 9)) + villager.ai_controller?.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, intruder) + + +/mob/living/basic/mining/mook/worker + desc = "This unhealthy looking primitive is wielding a rudimentary hatchet, swinging it with wild abandon. One isn't much of a threat, but in numbers they can quickly overwhelm a superior opponent." + gender = MALE + melee_damage_lower = 15 + melee_damage_upper = 15 + ai_controller = /datum/ai_controller/basic_controller/mook + is_healer = FALSE + +/mob/living/basic/mining/mook/worker/Initialize(mapload) + . = ..() + neutral_stance = mutable_appearance(icon, "mook_axe_overlay") + attack_stance = mutable_appearance(icon, "axe_strike_overlay") + update_appearance() + var/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/leap = new(src) + leap.Grant(src) + ai_controller.set_blackboard_key(BB_MOOK_LEAP_ABILITY, leap) + +/mob/living/basic/mining/mook/worker/attack_sequence(atom/target) + . = ..() + if(. & COMPONENT_HOSTILE_NO_ATTACK) + return + + if(attack_state == MOOK_ATTACK_STRIKE) + return COMPONENT_HOSTILE_NO_ATTACK + + change_combatant_state(state = MOOK_ATTACK_STRIKE) + addtimer(CALLBACK(src, PROC_REF(change_combatant_state), MOOK_ATTACK_NEUTRAL), 0.3 SECONDS) + +/mob/living/basic/mining/mook/worker/update_overlays() + . = ..() + if(stat == DEAD) + return + + switch(attack_state) + if(MOOK_ATTACK_STRIKE) + . += attack_stance + if(MOOK_ATTACK_NEUTRAL) + . += neutral_stance + +/mob/living/basic/mining/mook/worker/bard + desc = "It's holding a guitar?" + melee_damage_lower = 10 + melee_damage_upper = 10 + gender = MALE + attack_sound = 'sound/weapons/stringsmash.ogg' + death_sound = 'sound/voice/mook_death.ogg' + ai_controller = /datum/ai_controller/basic_controller/mook/bard + ///our guitar + var/obj/item/instrument/guitar/held_guitar + +/mob/living/basic/mining/mook/worker/bard/Initialize(mapload) + . = ..() + neutral_stance = mutable_appearance(icon, "bard_overlay") + attack_stance = mutable_appearance(icon, "bard_strike") + held_guitar = new(src) + ai_controller.set_blackboard_key(BB_SONG_INSTRUMENT, held_guitar) + update_appearance() + +/mob/living/basic/mining/mook/worker/tribal_chief + name = "tribal chief" + desc = "Acknowledge him!" + gender = MALE + melee_damage_lower = 20 + melee_damage_upper = 20 + ai_controller = /datum/ai_controller/basic_controller/mook/tribal_chief + ///overlay in our neutral state + var/static/mutable_appearance/chief_neutral = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief") + ///overlay in our striking state + var/static/mutable_appearance/chief_strike = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_strike") + ///overlay in our active state + 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") + +/mob/living/basic/mining/mook/worker/tribal_chief/Initialize(mapload) + . = ..() + update_appearance() + +/mob/living/basic/mining/mook/worker/tribal_chief/update_overlays() + . = ..() + if(stat == DEAD) + return + switch(attack_state) + if(MOOK_ATTACK_NEUTRAL) + . += chief_neutral + if(MOOK_ATTACK_WARMUP) + . += chief_warmup + if(MOOK_ATTACK_ACTIVE) + . += chief_active + if(MOOK_ATTACK_STRIKE) + . += chief_strike diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_abilities.dm b/code/modules/mob/living/basic/lavaland/mook/mook_abilities.dm new file mode 100644 index 00000000000..cfc359bd54f --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_abilities.dm @@ -0,0 +1,140 @@ +/datum/action/cooldown/mob_cooldown/mook_ability + ///are we a mook? + var/is_mook = FALSE + +/datum/action/cooldown/mob_cooldown/mook_ability/Grant(mob/grant_to) + . = ..() + if(isnull(owner)) + return + is_mook = istype(owner, /mob/living/basic/mining/mook) + +/datum/action/cooldown/mob_cooldown/mook_ability/IsAvailable(feedback) + . = ..() + + if(!.) + return FALSE + + if(!is_mook) + return TRUE + + var/mob/living/basic/mining/mook/mook_owner = owner + if(mook_owner.attack_state != MOOK_ATTACK_NEUTRAL) + if(feedback) + mook_owner.balloon_alert(mook_owner, "still recovering!") + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap + name = "Mook leap" + desc = "Leap towards the enemy!" + cooldown_time = 7 SECONDS + shared_cooldown = NONE + melee_cooldown_time = 0 SECONDS + ///telegraph time before jumping + var/wind_up_time = 2 SECONDS + ///intervals between each of our attacks + var/attack_interval = 0.4 SECONDS + ///how many times do we attack if we reach the target? + var/times_to_attack = 4 + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/Activate(atom/target) + if(owner.CanReach(target)) + attack_combo(target) + StartCooldown() + return TRUE + + if(is_mook) + var/mob/living/basic/mining/mook/mook_owner = owner + mook_owner.change_combatant_state(state = MOOK_ATTACK_WARMUP) + + addtimer(CALLBACK(src, PROC_REF(launch_towards_target), target), wind_up_time) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/proc/launch_towards_target(atom/target) + new /obj/effect/temp_visual/mook_dust(get_turf(owner)) + playsound(get_turf(owner), 'sound/weapons/thudswoosh.ogg', 25, TRUE) + playsound(owner, 'sound/voice/mook_leap_yell.ogg', 100, TRUE) + var/turf/target_turf = get_turf(target) + + if(!target_turf.is_blocked_turf()) + owner.throw_at(target = target_turf, range = 7, speed = 1, spin = FALSE, callback = CALLBACK(src, PROC_REF(attack_combo), target)) + return + + var/list/open_turfs = list() + + for(var/turf/possible_turf in get_adjacent_open_turfs(target)) + if(possible_turf.is_blocked_turf()) + continue + open_turfs += possible_turf + + if(!length(open_turfs)) + return + + var/turf/final_turf = get_closest_atom(/turf, open_turfs, owner) + owner.throw_at(target = final_turf, range = 7, speed = 1, spin = FALSE, callback = CALLBACK(src, PROC_REF(attack_combo), target)) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/proc/attack_combo(atom/target) + if(!owner.CanReach(target)) + return FALSE + + for(var/i in 0 to (times_to_attack - 1)) + addtimer(CALLBACK(src, PROC_REF(attack_target), target), i * attack_interval) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_leap/proc/attack_target(atom/target) + if(!owner.CanReach(target) || owner.stat == DEAD) + return + var/mob/living/basic/basic_owner = owner + basic_owner.melee_attack(target, ignore_cooldown = TRUE) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump + name = "Mook Jump" + desc = "Soar high in the air!" + cooldown_time = 14 SECONDS + shared_cooldown = NONE + melee_cooldown_time = 0 SECONDS + click_to_activate = FALSE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/Activate(atom/target) + var/obj/effect/landmark/drop_zone = locate(/obj/effect/landmark/mook_village) in GLOB.landmarks_list + if(drop_zone?.z == owner.z) + var/turf/jump_destination = get_turf(drop_zone) + jump_to_turf(jump_destination) + StartCooldown() + return TRUE + var/list/potential_turfs = list() + for(var/turf/open_turf in oview(9, owner)) + if(!open_turf.is_blocked_turf()) + potential_turfs += open_turf + if(!length(potential_turfs)) + return FALSE + jump_to_turf(pick(potential_turfs)) + StartCooldown() + return TRUE + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/proc/jump_to_turf(turf/target) + if(is_mook) + var/mob/living/basic/mining/mook/mook_owner = owner + mook_owner.change_combatant_state(state = MOOK_ATTACK_ACTIVE) + new /obj/effect/temp_visual/mook_dust(get_turf(owner)) + playsound(get_turf(owner), 'sound/weapons/thudswoosh.ogg', 50, TRUE) + animate(owner, pixel_y = owner.base_pixel_y + 146, time = 0.5 SECONDS) + addtimer(CALLBACK(src, PROC_REF(land_on_turf), target), 0.5 SECONDS) + +/datum/action/cooldown/mob_cooldown/mook_ability/mook_jump/proc/land_on_turf(turf/target) + do_teleport(owner, target, precision = 3, no_effects = TRUE) + animate(owner, pixel_y = owner.base_pixel_y, time = 0.5 SECONDS) + new /obj/effect/temp_visual/mook_dust(get_turf(owner)) + if(is_mook) + addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living/basic/mining/mook, change_combatant_state), MOOK_ATTACK_NEUTRAL), 0.5 SECONDS) + +/obj/effect/temp_visual/mook_dust + name = "dust" + desc = "It's just a dust cloud!" + icon = 'icons/mob/simple/jungle/mook.dmi' + icon_state = "mook_leap_cloud" + layer = BELOW_MOB_LAYER + plane = GAME_PLANE + base_pixel_y = -16 + base_pixel_x = -16 + duration = 1 SECONDS diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm new file mode 100644 index 00000000000..14ed9eb2982 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm @@ -0,0 +1,426 @@ +///commands the chief can pick from +GLOBAL_LIST_INIT(mook_commands, list( + new /datum/pet_command/point_targetting/attack, + new /datum/pet_command/point_targetting/fetch, +)) + +/datum/ai_controller/basic_controller/mook + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mook, + BB_BLACKLIST_MINERAL_TURFS = list(/turf/closed/mineral/gibtonite, /turf/closed/mineral/strong), + BB_MAXIMUM_DISTANCE_TO_VILLAGE = 7, + BB_STORM_APPROACHING = FALSE, + ) + + 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/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, + ) + +///check for faction if not a ash walker, otherwise just attack +/datum/targetting_datum/basic/mook/faction_check(mob/living/living_mob, mob/living/the_target) + if(FACTION_ASHWALKER in living_mob.faction) + return FALSE + + 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/unarmed_attack_target/material_stand + finding_behavior = /datum/ai_behavior/find_hunt_target + hunt_targets = list(/obj/structure/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/unarmed_attack_target/material_stand + required_distance = 0 + always_reset_target = TRUE + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + +///try to face the counter when depositing ores +/datum/ai_behavior/hunt_target/unarmed_attack_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)) + finish_action(controller, FALSE) + return + + controller.set_blackboard_key(village_key, home_marker) + finish_action(controller, TRUE) + +///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) + . = ..() + finish_action(controller, TRUE) + +/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 + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mook, + BB_MAXIMUM_DISTANCE_TO_VILLAGE = 10, + BB_STORM_APPROACHING = FALSE, + BB_SONG_LINES = MOOK_SONG, + ) + idle_behavior = /datum/idle_behavior/walk_near_target/mook_village + planning_subtrees = list( + /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) + 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 + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mook, + BB_MAXIMUM_DISTANCE_TO_VILLAGE = 10, + BB_STORM_APPROACHING = FALSE, + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/not_friends, + ) + idle_behavior = /datum/idle_behavior/walk_near_target/mook_village + planning_subtrees = list( + /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) + 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/unarmed_attack_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/unarmed_attack_target/injured_mooks + +/datum/ai_behavior/hunt_target/unarmed_attack_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 + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/mook, + BB_STORM_APPROACHING = FALSE, + ) + idle_behavior = /datum/idle_behavior/walk_near_target/mook_village + planning_subtrees = list( + /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/point_targetting/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/point_targetting/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)) + finish_action(controller, FALSE) + return + + var/datum/pet_command/to_command = locate(command_path) in GLOB.mook_commands + if(isnull(to_command)) + finish_action(controller, FALSE) + return + + var/issue_command = pick(to_command.speech_commands) + living_pawn.say(issue_command, forced = "controller") + living_pawn._pointed(target) + finish_action(controller, TRUE) + + +///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/unarmed_attack_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/unarmed_attack_target/bonfire + always_reset_target = TRUE diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_village.dm b/code/modules/mob/living/basic/lavaland/mook/mook_village.dm new file mode 100644 index 00000000000..e3a091f6f0e --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_village.dm @@ -0,0 +1,85 @@ +///unique items that spawn at the mook village +/obj/structure/material_stand + name = "material stand" + desc = "Is everyone free to use this thing?" + icon = 'icons/mob/simple/jungle/mook.dmi' + icon_state = "material_stand" + density = TRUE + anchored = TRUE + resistance_flags = INDESTRUCTIBLE + bound_width = 64 + bound_height = 64 + +/obj/structure/material_stand/attackby(obj/item/ore, mob/living/carbon/human/user, list/modifiers) + if(istype(ore, /obj/item/stack/ore)) + ore.forceMove(src) + return + return ..() + +/obj/structure/material_stand/Entered(atom/movable/mover) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/structure/material_stand/Exited(atom/movable/mover) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +///put ore icons on the counter! +/obj/structure/material_stand/update_overlays() + . = ..() + for(var/obj/item/stack/ore/ore_item in contents) + var/image/ore_icon = image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state), layer = LOW_ITEM_LAYER) + ore_icon.transform = ore_icon.transform.Scale(0.6, 0.6) + ore_icon.pixel_x = rand(9, 17) + ore_icon.pixel_y = rand(2, 4) + . += ore_icon + +/obj/structure/material_stand/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MaterialStand") + ui.open() + +/obj/structure/material_stand/ui_data(mob/user) + var/list/data = list() + data["ores"] = list() + for(var/obj/item/stack/ore/ore_item in contents) + data["ores"] += list(list( + "id" = REF(ore_item), + "name" = ore_item.name, + "amount" = ore_item.amount, + )) + return data + +/obj/structure/material_stand/ui_static_data(mob/user) + var/list/data = list() + data["ore_images"] = list() + for(var/obj/item/stack/ore_item as anything in subtypesof(/obj/item/stack/ore)) + data["ore_images"] += list(list( + "name" = initial(ore_item.name), + "icon" = icon2base64(getFlatIcon(image(icon = initial(ore_item.icon), icon_state = initial(ore_item.icon_state)), no_anim=TRUE)) + )) + return data + +/obj/structure/material_stand/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + + if(. || !isliving(usr)) + return TRUE + + var/mob/living/customer = usr + var/obj/item/stack_to_move + switch(action) + if("withdraw") + if(isnull(params["reference"])) + return TRUE + stack_to_move = locate(params["reference"]) in contents + if(isnull(stack_to_move)) + return TRUE + stack_to_move.forceMove(get_turf(customer)) + return TRUE + +/obj/effect/landmark/mook_village + name = "mook village landmark" + icon_state = "x" diff --git a/code/modules/mob/living/basic/minebots/minebot_ai.dm b/code/modules/mob/living/basic/minebots/minebot_ai.dm index a4b082f5dd1..33e9821dbc4 100644 --- a/code/modules/mob/living/basic/minebots/minebot_ai.dm +++ b/code/modules/mob/living/basic/minebots/minebot_ai.dm @@ -12,7 +12,7 @@ /datum/ai_planning_subtree/simple_find_target, /datum/ai_planning_subtree/pet_planning, /datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot, - /datum/ai_planning_subtree/find_and_hunt_target/consume_ores/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, ) @@ -133,11 +133,11 @@ controller.clear_blackboard_key(target_key) ///store ores in our body -/datum/ai_planning_subtree/find_and_hunt_target/consume_ores/minebot +/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/consume_ores/minebot hunt_chance = 100 -/datum/ai_planning_subtree/find_and_hunt_target/consume_ores/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) +/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] var/mob/living/living_pawn = controller.pawn diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm deleted file mode 100644 index 444635f2dc3..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm +++ /dev/null @@ -1,229 +0,0 @@ -#define MOOK_ATTACK_NEUTRAL 0 -#define MOOK_ATTACK_WARMUP 1 -#define MOOK_ATTACK_ACTIVE 2 -#define MOOK_ATTACK_RECOVERY 3 -#define ATTACK_INTERMISSION_TIME 5 - -//Fragile but highly aggressive wanderers that pose a large threat in numbers. -//They'll attempt to leap at their target from afar using their hatchets. -/mob/living/simple_animal/hostile/jungle/mook - name = "wanderer" - desc = "This unhealthy looking primitive is wielding a rudimentary hatchet, swinging it with wild abandon. One isn't much of a threat, but in numbers they can quickly overwhelm a superior opponent." - icon = 'icons/mob/simple/jungle/mook.dmi' - icon_state = "mook" - icon_living = "mook" - icon_dead = "mook_dead" - mob_biotypes = MOB_ORGANIC|MOB_HUMANOID - SET_BASE_PIXEL(-16, -8) - - maxHealth = 45 - health = 45 - melee_damage_lower = 30 - melee_damage_upper = 30 - ranged = TRUE - ranged_cooldown_time = 10 - pass_flags_self = LETPASSTHROW - robust_searching = TRUE - stat_attack = HARD_CRIT - attack_sound = 'sound/weapons/rapierhit.ogg' - attack_vis_effect = ATTACK_EFFECT_SLASH - death_sound = 'sound/voice/mook_death.ogg' - aggro_vision_range = 15 //A little more aggressive once in combat to balance out their really low HP - var/attack_state = MOOK_ATTACK_NEUTRAL - var/struck_target_leap = FALSE - - footstep_type = FOOTSTEP_MOB_BAREFOOT - -/mob/living/simple_animal/hostile/jungle/mook/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(istype(mover, /mob/living/simple_animal/hostile/jungle/mook)) - var/mob/living/simple_animal/hostile/jungle/mook/mook_moover = mover - if(mook_moover.attack_state == MOOK_ATTACK_ACTIVE && mook_moover.throwing) - return TRUE - -/mob/living/simple_animal/hostile/jungle/mook/death() - desc = "A deceased primitive. Upon closer inspection, it was suffering from severe cellular degeneration and its garments are machine made..."//Can you guess the twist - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/AttackingTarget() - if(isliving(target)) - if(ranged_cooldown <= world.time && attack_state == MOOK_ATTACK_NEUTRAL) - var/mob/living/L = target - if(L.incapacitated()) - WarmupAttack(forced_slash_combo = TRUE) - return - WarmupAttack() - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/Goto() - if(attack_state != MOOK_ATTACK_NEUTRAL) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/Move() - if(attack_state == MOOK_ATTACK_WARMUP || attack_state == MOOK_ATTACK_RECOVERY) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/proc/WarmupAttack(forced_slash_combo = FALSE) - if(attack_state == MOOK_ATTACK_NEUTRAL && target) - attack_state = MOOK_ATTACK_WARMUP - SSmove_manager.stop_looping(src) - update_icons() - if(prob(50) && get_dist(src,target) <= 3 || forced_slash_combo) - addtimer(CALLBACK(src, PROC_REF(SlashCombo)), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, PROC_REF(LeapAttack)), ATTACK_INTERMISSION_TIME + rand(0,3)) - return - attack_state = MOOK_ATTACK_RECOVERY - ResetNeutral() - -/mob/living/simple_animal/hostile/jungle/mook/proc/SlashCombo() - if(attack_state == MOOK_ATTACK_WARMUP && !stat) - attack_state = MOOK_ATTACK_ACTIVE - update_icons() - SlashAttack() - addtimer(CALLBACK(src, PROC_REF(SlashAttack)), 3) - addtimer(CALLBACK(src, PROC_REF(SlashAttack)), 6) - addtimer(CALLBACK(src, PROC_REF(AttackRecovery)), 9) - -/mob/living/simple_animal/hostile/jungle/mook/proc/SlashAttack() - if(target && !stat && attack_state == MOOK_ATTACK_ACTIVE) - melee_damage_lower = 15 - melee_damage_upper = 15 - var/mob_direction = get_dir(src,target) - var/atom/target_from = GET_TARGETS_FROM(src) - if(get_dist(src,target) > 1) - step(src,mob_direction) - if(isturf(target_from.loc) && target.Adjacent(target_from) && isliving(target)) - var/mob/living/L = target - L.attack_animal(src) - return - var/swing_turf = get_step(src,mob_direction) - new /obj/effect/temp_visual/kinetic_blast(swing_turf) - playsound(src, 'sound/weapons/slashmiss.ogg', 50, TRUE) - -/mob/living/simple_animal/hostile/jungle/mook/proc/LeapAttack() - if(target && !stat && attack_state == MOOK_ATTACK_WARMUP) - attack_state = MOOK_ATTACK_ACTIVE - ADD_TRAIT(src, TRAIT_UNDENSE, LEAPING_TRAIT) - melee_damage_lower = 30 - melee_damage_upper = 30 - update_icons() - new /obj/effect/temp_visual/mook_dust(get_turf(src)) - playsound(src, 'sound/weapons/thudswoosh.ogg', 25, TRUE) - playsound(src, 'sound/voice/mook_leap_yell.ogg', 100, TRUE) - var/target_turf = get_turf(target) - throw_at(target_turf, 7, 1, src, FALSE, callback = CALLBACK(src, PROC_REF(AttackRecovery))) - return - attack_state = MOOK_ATTACK_RECOVERY - ResetNeutral() - -/mob/living/simple_animal/hostile/jungle/mook/proc/AttackRecovery() - if(attack_state == MOOK_ATTACK_ACTIVE && !stat) - attack_state = MOOK_ATTACK_RECOVERY - REMOVE_TRAIT(src, TRAIT_UNDENSE, LEAPING_TRAIT) - face_atom(target) - if(!struck_target_leap) - update_icons() - struck_target_leap = FALSE - if(prob(40)) - attack_state = MOOK_ATTACK_NEUTRAL - if(target) - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated() && L.stat != DEAD) - addtimer(CALLBACK(src, PROC_REF(WarmupAttack), TRUE), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, PROC_REF(WarmupAttack)), ATTACK_INTERMISSION_TIME) - return - addtimer(CALLBACK(src, PROC_REF(ResetNeutral)), ATTACK_INTERMISSION_TIME) - -/mob/living/simple_animal/hostile/jungle/mook/proc/ResetNeutral() - if(attack_state == MOOK_ATTACK_RECOVERY) - attack_state = MOOK_ATTACK_NEUTRAL - ranged_cooldown = world.time + ranged_cooldown_time - update_icons() - if(target && !stat) - update_icons() - Goto(target, move_to_delay, minimum_distance) - -/mob/living/simple_animal/hostile/jungle/mook/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - . = ..() - if(isliving(hit_atom) && attack_state == MOOK_ATTACK_ACTIVE) - var/mob/living/L = hit_atom - if(CanAttack(L)) - L.attack_animal(src) - struck_target_leap = TRUE - REMOVE_TRAIT(src, TRAIT_UNDENSE, LEAPING_TRAIT) - update_icons() - var/mook_under_us = FALSE - for(var/A in get_turf(src)) - if(struck_target_leap && mook_under_us) - break - if(A == src) - continue - if(isliving(A)) - var/mob/living/ML = A - if(!struck_target_leap && CanAttack(ML))//Check if some joker is attempting to use rest to evade us - struck_target_leap = TRUE - ML.attack_animal(src) - REMOVE_TRAIT(src, TRAIT_UNDENSE, LEAPING_TRAIT) - struck_target_leap = TRUE - update_icons() - continue - if(istype(ML, /mob/living/simple_animal/hostile/jungle/mook) && !mook_under_us)//If we land on the same tile as another mook, spread out so we don't stack our sprite on the same tile - var/mob/living/simple_animal/hostile/jungle/mook/M = ML - if(!M.stat) - mook_under_us = TRUE - var/anydir = pick(GLOB.cardinals) - Move(get_step(src, anydir), anydir) - continue - -/mob/living/simple_animal/hostile/jungle/mook/handle_automated_action() - if(attack_state) - return - return ..() - -/mob/living/simple_animal/hostile/jungle/mook/OpenFire() - if(isliving(target)) - var/mob/living/L = target - if(L.incapacitated()) - return - WarmupAttack() - -/mob/living/simple_animal/hostile/jungle/mook/update_icons() - . = ..() - if(!stat) - switch(attack_state) - if(MOOK_ATTACK_NEUTRAL) - icon_state = "mook" - if(MOOK_ATTACK_WARMUP) - icon_state = "mook_warmup" - if(MOOK_ATTACK_ACTIVE) - if(!density) - icon_state = "mook_leap" - return - if(struck_target_leap) - icon_state = "mook_strike" - return - icon_state = "mook_slash_combo" - if(MOOK_ATTACK_RECOVERY) - icon_state = "mook" - -/obj/effect/temp_visual/mook_dust - name = "dust" - desc = "It's just a dust cloud!" - icon = 'icons/mob/simple/jungle/mook.dmi' - icon_state = "mook_leap_cloud" - layer = BELOW_MOB_LAYER - plane = GAME_PLANE - SET_BASE_PIXEL(-16, -16) - duration = 10 - -#undef MOOK_ATTACK_NEUTRAL -#undef MOOK_ATTACK_WARMUP -#undef MOOK_ATTACK_ACTIVE -#undef MOOK_ATTACK_RECOVERY -#undef ATTACK_INTERMISSION_TIME diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 50bb2e1a678..8be96944a5f 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -106,7 +106,6 @@ /mob/living/simple_animal/hostile/illusion/mirage, /mob/living/simple_animal/hostile/jungle, /mob/living/simple_animal/hostile/jungle/leaper, - /mob/living/simple_animal/hostile/jungle/mook, /mob/living/simple_animal/hostile/megafauna, /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner, /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner/doom, diff --git a/config/lavaruinblacklist.txt b/config/lavaruinblacklist.txt index 6f90b1f254e..e6279517dbf 100644 --- a/config/lavaruinblacklist.txt +++ b/config/lavaruinblacklist.txt @@ -23,20 +23,20 @@ #_maps/RandomRuins/LavaRuins/lavaland_surface_sloth.dmm ##MISC +#_maps/RandomRuins/AnywhereRuins/fountain_hall.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_automated_trade_outpost.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_elephant_graveyard.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_gaia.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_mookvillage.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_phonebooth.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm +#_maps/RandomRuins/LavaRuins/lavaland_surface_tomb.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_ww_vault.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_automated_trade_outpost.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm #_maps/RandomRuins/LavaRuins/lavaland_surface_wwiioutpost.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_tomb.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_gaia.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_elephant_graveyard.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm -#_maps/RandomRuins/AnywhereRuins/fountain_hall.dmm -#_maps/RandomRuins/LavaRuins/lavaland_surface_phonebooth.dmm \ No newline at end of file +#_maps/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm diff --git a/icons/mob/simple/jungle/mook.dmi b/icons/mob/simple/jungle/mook.dmi index c9265b22a0ad201b5cfc7f102ff610268157245b..fbc38d29d99deb3349f880a7908a5a38e1e9e0c5 100644 GIT binary patch literal 36875 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�seW3Fn|RluB2b-G9jYJzK}w#VS2PMy8v1k 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 literal 23590 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 ZRB$MC*ufz(2#m#G7mQ2|3-ujw{{fJ{#Cre$ diff --git a/tgstation.dme b/tgstation.dme index f98ec173ce3..5be8112bfd7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -804,7 +804,6 @@ #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\climb_tree.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_mineable_wall.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_parent.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targetting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\pick_up_item.dm" @@ -825,6 +824,7 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\find_parent.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.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\ranged_skirmish.dm" @@ -4434,6 +4434,10 @@ #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity.dm" #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity_ai.dm" #include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity_trophy.dm" +#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_village.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_ai.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_gaze.dm" @@ -4747,7 +4751,6 @@ #include "code\modules\mob\living\simple_animal\hostile\gorilla\visuals_icons.dm" #include "code\modules\mob\living\simple_animal\hostile\jungle\_jungle_mobs.dm" #include "code\modules\mob\living\simple_animal\hostile\jungle\leaper.dm" -#include "code\modules\mob\living\simple_animal\hostile\jungle\mook.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\_megafauna.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm" #include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm" diff --git a/tgui/packages/tgui/interfaces/MaterialStand.tsx b/tgui/packages/tgui/interfaces/MaterialStand.tsx new file mode 100644 index 00000000000..b6e87889cd2 --- /dev/null +++ b/tgui/packages/tgui/interfaces/MaterialStand.tsx @@ -0,0 +1,120 @@ +import { createSearch, toTitleCase } from 'common/string'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Input, Stack, Flex, Section } from '../components'; +import { Window } from '../layouts'; + +type Ores = { + id: string; + name: string; + amount: number; +}; + +type Ore_images = { + name: string; + icon: string; +}; + +type Data = { + ores: Ores[]; + ore_images: Ore_images[]; +}; + +export const MaterialStand = (props, context) => { + const { act, data } = useBackend(context); + const { ores = [] } = data; + const [searchItem, setSearchItem] = useLocalState(context, 'searchItem', ''); + const search = createSearch(searchItem, (ore: Ores) => ore.name); + const ores_filtered = + searchItem.length > 0 ? ores.filter((ore) => search(ore)) : ores; + return ( + + + + +
+ { + setSearchItem(value); + }} + fluid + /> +
+
+ +
+ + {ores_filtered.map((ore) => ( + + + + + + + + + Amount: {ore.amount} + +
+
+
+
+
+ ); +}; + +const RetrieveIcon = (props, context) => { + const { data } = useBackend(context); + const { ore_images = [] } = data; + const { ore } = props; + + let icon_display = ore_images.find((icon) => icon.name === ore.name); + + if (!icon_display) { + return null; + } + + return ( + + ); +}; + +const Orename = (props) => { + const { ore_name } = props; + const return_name = ore_name.split(' '); + if (return_name.length === 0) { + return null; + } + return return_name[0]; +};