From 9968f9472179b2fce434fdf29ebce50cca71e41e Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Sun, 11 May 2025 02:52:20 +0100 Subject: [PATCH] Regal Rat cleanup & minor changes (#91012) ## About The Pull Request Since #90505 added another entry to it the Regal Rat Riot ability, which turns maintenance creatures into versions loyal to the rat, has become sort of unmanageable (and to be honest it was a bit gross to start with). Instead of having a big if/else list (which was making the same range check multiple times...) that sets stats on a bunch of mobs, I delegated it to the mobs themselves and instead of changing some stats of the existing mobs we just turn them into a new mob which can be spawned or placed separately by mappers or admins if they want. Other stuff I changed: Riot (the ability which transforms mobs into minions) no longer spawns a mouse if it fails to find anything. Instead you have a chance to fish mice out of disposals bins while digging out trash and items. Domain is now a toggle which activates itself every 6 seconds rather than a button you manually click every 6 seconds. Riot makes a visual effect when used. Rare Pepe randomisation is done via a random spawner instead of the mob modifying a bunch of its own properties in Initialise. A bunch of mobs now automatically follow you after being tamed. I wrote this assuming I was going to add it to the rioted mobs but then didn't end up doing that because you might want them to immediately attack someone. My rule of thumb is that if I think you'd want the mob to attack someone the moment it is befriended I didn't add this and if you wouldn't I did. I changed some of the regal rat minion names, and some of them can now spawn from gold slime which couldn't before. ## Why It's Good For The Game This proc sucked and now it's nicer. As for the other changes; - A tamed mob immediately following you is nice feedback and saves you a click as it's likely to be your first action. Also removes some admin panel shitcode I added. - I changed Domain to a toggle because you generally want to use it on cooldown and someone suggested it on this PR and it sounded like a good idea. - I saw someone in Discord complaining that the previous flow of recruiting rats by hitting Riot with nothing around to summon one, waiting, hitting it again to convert one rat, and waiting again was tedious and annoying which I agree with. This method improves the quality of life by separating these two actions but _also_ as a side effect reduces a regal rat's ability to secretly stockpile 50 rats in a hidden maintenance room because most disposal bins are in slightly more visible areas, they'll actually need to go and make a mess somewhere someone can see them. ## Changelog :cl: balance: Regal Rats can now grab mice out of disposal bins, and no longer spawn them with the Riot ability. balance: The Riot ability no longer needs to be used once for each slightly different kind of mob in your radius. balance: The Regal Rat Domain ability is now toggled on and off. balance: Several kinds of mob will immediately start following you once tamed. balance: Rats, hostile frogs, and evil snails can be created via gold slime reaction. /:cl: --- .../signals/signals_mob/signals_mob_simple.dm | 3 + code/__DEFINES/mobs.dm | 8 + .../ai/basic_mobs/admin_ai_templates.dm | 7 +- .../pet_commands/pet_commands_basic.dm | 14 + code/datums/elements/regal_rat_minion.dm | 48 ++++ .../anomalies/anomalies_bioscrambler.dm | 31 --- .../objects/effects/spawners/random/trash.dm | 14 +- .../temporary_visuals/miscellaneous.dm | 31 +++ code/modules/cargo/packs/livestock.dm | 2 +- code/modules/events/wizard/object_rain.dm | 1 + code/modules/fishing/fish/types/freshwater.dm | 5 +- code/modules/fishing/sources/_fish_source.dm | 2 +- .../fishing/sources/subtypes/structures.dm | 4 +- .../modules/fishing/sources/subtypes/turfs.dm | 2 +- .../mob/living/basic/jungle/leaper/leaper.dm | 2 +- .../basic/jungle/leaper/leaper_abilities.dm | 2 +- .../living/basic/jungle/seedling/seedling.dm | 4 +- .../basic/lavaland/goldgrub/goldgrub.dm | 2 +- .../basic/lavaland/gutlunchers/gutlunchers.dm | 2 +- .../basic/lavaland/lobstrosity/lobstrosity.dm | 2 +- .../living/basic/lavaland/raptor/_raptor.dm | 2 +- .../mob/living/basic/minebots/minebot.dm | 2 +- code/modules/mob/living/basic/pets/cat/cat.dm | 2 +- .../modules/mob/living/basic/pets/dog/_dog.dm | 1 + code/modules/mob/living/basic/pets/fox.dm | 2 +- code/modules/mob/living/basic/snails/snail.dm | 29 ++- .../regal_rat/regal_rat_actions.dm | 242 +++++------------- .../space_fauna/regal_rat/regal_rat_ai.dm | 6 + .../living/basic/space_fauna/spider/spider.dm | 2 +- .../basic/vermin/{ => cockroach}/cockroach.dm | 206 ++++++++------- .../basic/vermin/cockroach/cockroach_ai.dm | 61 +++++ code/modules/mob/living/basic/vermin/frog.dm | 77 ++++-- .../basic/vermin/mothroach/mothroach.dm | 2 +- code/modules/mob/living/basic/vermin/mouse.dm | 6 +- code/modules/mob/mob_transformation_simple.dm | 2 +- code/modules/recycling/disposal/bin.dm | 17 +- .../vatgrowing/samples/cell_lines/common.dm | 2 +- icons/mob/actions/actions_animal.dmi | Bin 19573 -> 20357 bytes tgstation.dme | 4 +- 39 files changed, 493 insertions(+), 358 deletions(-) create mode 100644 code/datums/elements/regal_rat_minion.dm rename code/modules/mob/living/basic/vermin/{ => cockroach}/cockroach.dm (67%) create mode 100644 code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_simple.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_simple.dm index b648af8fa6c..cdbd0d3e432 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_simple.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_simple.dm @@ -17,3 +17,6 @@ ///Called when a /mob/living/simple_animal/hostile fines a new target: (atom/source, new_target) #define COMSIG_HOSTILE_FOUND_TARGET "comsig_hostile_found_target" + +///Called when a regal rat uses their riot ability on a mob +#define COMSIG_REGAL_RAT_RIOTED "comsig_regal_rat_rioted" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index dae67501b0b..681fb5c8581 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -1054,3 +1054,11 @@ GLOBAL_LIST_INIT(layers_to_offset, list( /// Distance which you can see someone's ID card /// Short enough that you can inspect over tables (bartender checking age) #define ID_EXAMINE_DISTANCE 3 + +GLOBAL_LIST_INIT(regal_rat_minion_commands, list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/protect_owner, + /datum/pet_command/follow, + /datum/pet_command/attack/mouse +)) diff --git a/code/datums/ai/basic_mobs/admin_ai_templates.dm b/code/datums/ai/basic_mobs/admin_ai_templates.dm index 050cc099947..5d63b078543 100644 --- a/code/datums/ai/basic_mobs/admin_ai_templates.dm +++ b/code/datums/ai/basic_mobs/admin_ai_templates.dm @@ -417,18 +417,15 @@ /datum/pet_command/idle, /datum/pet_command/move, /datum/pet_command/attack, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/protect_owner, ) - var/datum/component/obeys_commands/command_component = target.AddComponent(/datum/component/obeys_commands, pet_commands) + target.AddComponent(/datum/component/obeys_commands, pet_commands) if (isnull(da_boss)) return target.befriend(da_boss) - // Fuck it we're in admin territory we can do code crimes here - var/datum/pet_command/follow/follow_command = command_component.available_commands["Follow"] - follow_command?.set_command_active(target, da_boss) /// Whatever it was doing before we fucked with it (mostly, can't do this with total confidence) /datum/admin_ai_template/reset diff --git a/code/datums/components/pet_commands/pet_commands_basic.dm b/code/datums/components/pet_commands/pet_commands_basic.dm index cd535b33eca..bd544a47295 100644 --- a/code/datums/components/pet_commands/pet_commands_basic.dm +++ b/code/datums/components/pet_commands/pet_commands_basic.dm @@ -47,6 +47,8 @@ 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 /datum/pet_command/follow/set_command_active(mob/living/parent, mob/living/commander) . = ..() @@ -59,6 +61,18 @@ controller.queue_behavior(follow_behavior, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/follow/add_new_friend(mob/living/tamer) + . = ..() + var/mob/living/parent = weak_parent.resolve() + if (!parent) + return + if (activate_on_befriend && !parent.ai_controller.blackboard_key_exists(BB_ACTIVE_PET_COMMAND)) + try_activate_command(tamer) + +/// Like follow but start active +/datum/pet_command/follow/start_active + activate_on_befriend = TRUE + /** * # Pet Command: Play Dead * Pretend to be dead for a random period of time diff --git a/code/datums/elements/regal_rat_minion.dm b/code/datums/elements/regal_rat_minion.dm new file mode 100644 index 00000000000..d0b2198dbf9 --- /dev/null +++ b/code/datums/elements/regal_rat_minion.dm @@ -0,0 +1,48 @@ +/// Mob which can become evil when exposed to a certain ability +/datum/element/regal_rat_minion + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + /// Typepath of converted mob + var/converted_path + /// Balloon string to output on conversion + var/success_balloon + /// Commands to give this mob on conversion + var/list/pet_commands + +/datum/element/regal_rat_minion/Attach(datum/target, converted_path, success_balloon = "squeak", list/pet_commands) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + + src.converted_path = converted_path + src.success_balloon = success_balloon + src.pet_commands = pet_commands + + RegisterSignal(target, COMSIG_REGAL_RAT_RIOTED, PROC_REF(on_rioted)) + +/datum/element/regal_rat_minion/Detach(datum/source) + . = ..() + UnregisterSignal(source, COMSIG_REGAL_RAT_RIOTED) + +/// Makes a mob into a minion +/datum/element/regal_rat_minion/proc/on_rioted(mob/living/minion, mob/living/master) + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(do_conversion), minion, master) + +/// Actually turn the mob into a different mob +/datum/element/regal_rat_minion/proc/do_conversion(mob/living/minion, mob/living/master) + if (minion.stat == DEAD) + return + + var/mob/living/result = converted_path + var/new_name = minion.name == initial(minion.name) ? result::name : minion.name + var/mob/living/new_minion = minion.change_mob_type(converted_path, new_name = new_name, delete_old_mob = TRUE) + if (length(pet_commands)) + new_minion.AddComponent(/datum/component/obeys_commands, pet_commands) + + qdel(new_minion.GetComponent(/datum/component/tameable)) // Rats don't share + + new_minion.befriend(master) + new_minion.faction = master.faction.Copy() + new_minion.balloon_alert_to_viewers(success_balloon) diff --git a/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm b/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm index 4d3a9f37b2e..c9544008e82 100644 --- a/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm +++ b/code/game/objects/effects/anomalies/anomalies_bioscrambler.dm @@ -84,34 +84,3 @@ /obj/effect/anomaly/bioscrambler/detonate() COOLDOWN_RESET(src, pulse_cooldown) anomalyEffect() - -/// Visual effect spawned when the bioscrambler scrambles your bio -/obj/effect/temp_visual/circle_wave - icon = 'icons/effects/64x64.dmi' - icon_state = "circle_wave" - pixel_x = -16 - pixel_y = -16 - duration = 0.5 SECONDS - color = COLOR_LIME - var/max_alpha = 255 - ///How far the effect would scale in size - var/amount_to_scale = 2 - -/obj/effect/temp_visual/circle_wave/Initialize(mapload) - transform = matrix().Scale(0.1) - animate(src, transform = matrix().Scale(amount_to_scale), time = duration, flags = ANIMATION_PARALLEL) - animate(src, alpha = max_alpha, time = duration * 0.6, flags = ANIMATION_PARALLEL) - animate(alpha = 0, time = duration * 0.4) - apply_wibbly_filters(src) - return ..() - -/obj/effect/temp_visual/circle_wave/bioscrambler - color = COLOR_LIME - -/obj/effect/temp_visual/circle_wave/bioscrambler/light - max_alpha = 128 - -/obj/effect/temp_visual/circle_wave/void_conduit - color = COLOR_FULL_TONER_BLACK - duration = 12 SECONDS - amount_to_scale = 12 diff --git a/code/game/objects/effects/spawners/random/trash.dm b/code/game/objects/effects/spawners/random/trash.dm index a822bb3617d..e54f5549a63 100644 --- a/code/game/objects/effects/spawners/random/trash.dm +++ b/code/game/objects/effects/spawners/random/trash.dm @@ -182,7 +182,7 @@ loot = list( // This spawner will scatter water related items around a moist site. /obj/item/clothing/head/cone = 7, /obj/item/clothing/suit/caution = 3, - /mob/living/basic/frog = 2, + /obj/effect/spawner/random/frog = 2, /obj/item/reagent_containers/cup/rag = 2, /obj/item/reagent_containers/cup/bucket = 2, /obj/effect/decal/cleanable/blood/old = 2, @@ -194,9 +194,19 @@ if(mapload) var/turf/location = get_turf(loc) if(location.initial_gas_mix != OPENTURF_DEFAULT_ATMOS && location.initial_gas_mix != OPENTURF_DIRTY_ATMOS) - loot -= list(/mob/living/basic/frog, /mob/living/basic/axolotl) + loot -= list(/obj/effect/spawner/random/frog, /mob/living/basic/axolotl) return ..() +/obj/effect/spawner/random/frog + name = "random frog" + desc = "Spawns a frog, or sometimes a RARE frog." + icon = 'icons/mob/simple/animal.dmi' + icon_state = "frog" + loot = list( + /mob/living/basic/frog = 99, + /mob/living/basic/frog/rare = 1, + ) + /obj/effect/spawner/random/trash/graffiti name = "random graffiti spawner" icon_state = "rune" diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 421ac86a71f..9acef77728f 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -766,3 +766,34 @@ duration = 5 MINUTES pixel_x = -16 pixel_y = -8 //32 + +/// Visual effect spawned when the bioscrambler scrambles your bio +/obj/effect/temp_visual/circle_wave + icon = 'icons/effects/64x64.dmi' + icon_state = "circle_wave" + pixel_x = -16 + pixel_y = -16 + duration = 0.5 SECONDS + color = COLOR_LIME + var/max_alpha = 255 + ///How far the effect would scale in size + var/amount_to_scale = 2 + +/obj/effect/temp_visual/circle_wave/Initialize(mapload) + transform = matrix().Scale(0.1) + animate(src, transform = matrix().Scale(amount_to_scale), time = duration, flags = ANIMATION_PARALLEL) + animate(src, alpha = max_alpha, time = duration * 0.6, flags = ANIMATION_PARALLEL) + animate(alpha = 0, time = duration * 0.4) + apply_wibbly_filters(src) + return ..() + +/obj/effect/temp_visual/circle_wave/bioscrambler + color = COLOR_LIME + +/obj/effect/temp_visual/circle_wave/bioscrambler/light + max_alpha = 128 + +/obj/effect/temp_visual/circle_wave/void_conduit + color = COLOR_FULL_TONER_BLACK + duration = 12 SECONDS + amount_to_scale = 12 diff --git a/code/modules/cargo/packs/livestock.dm b/code/modules/cargo/packs/livestock.dm index f9fa142ceb0..9adbd31c84d 100644 --- a/code/modules/cargo/packs/livestock.dm +++ b/code/modules/cargo/packs/livestock.dm @@ -204,7 +204,7 @@ cost = CARGO_CRATE_VALUE * 2 contains = list( /mob/living/basic/axolotl, - /mob/living/basic/frog, + /obj/effect/spawner/random/frog, ) crate_name = "amphibian crate" diff --git a/code/modules/events/wizard/object_rain.dm b/code/modules/events/wizard/object_rain.dm index 04651992a5b..a98d1b540b9 100644 --- a/code/modules/events/wizard/object_rain.dm +++ b/code/modules/events/wizard/object_rain.dm @@ -96,6 +96,7 @@ /mob/living/basic/pet/dog/corgi/puppy = 5, /mob/living/basic/pet/cat/kitten = 5, /mob/living/basic/frog = 5, + /mob/living/basic/frog/rare = 1, /mob/living/basic/axolotl = 1, /mob/living/basic/pet/dog/corgi/puppy/void = 1, /mob/living/basic/pet/dog/corgi/puppy/slime = 1, diff --git a/code/modules/fishing/fish/types/freshwater.dm b/code/modules/fishing/fish/types/freshwater.dm index 6e0a5154a62..01c51b59b69 100644 --- a/code/modules/fishing/fish/types/freshwater.dm +++ b/code/modules/fishing/fish/types/freshwater.dm @@ -201,7 +201,8 @@ /obj/item/fish/tadpole/Initialize(mapload, apply_qualities = TRUE) . = ..() - AddComponent(/datum/component/fish_growth, /mob/living/basic/frog, rand(2 MINUTES, 3 MINUTES)) + var/output_path = prob(99) ? /mob/living/basic/frog : /mob/living/basic/frog/rare + AddComponent(/datum/component/fish_growth, output_path, rand(2 MINUTES, 3 MINUTES)) RegisterSignal(src, COMSIG_FISH_BEFORE_GROWING, PROC_REF(growth_checks)) RegisterSignal(src, COMSIG_FISH_FINISH_GROWING, PROC_REF(on_growth)) @@ -252,7 +253,7 @@ /obj/item/fish/tadpole/proc/gestation(mob/living/user) if(QDELETED(user) || QDELETED(src)) return - new /mob/living/basic/frog(user) + new /obj/effect/spawner/random/frog(user.drop_location()) user.gib() qdel(src) diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index 72f53599906..a170aa77497 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -14,7 +14,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) var/list/return_list = zebra_typecacheof(list( /datum/data/vending_product = FISH_ICON_COIN, /mob/living/basic/axolotl = FISH_ICON_CRITTER, - /mob/living/basic/frog = FISH_ICON_CRITTER, + /obj/effect/spawner/random/frog = FISH_ICON_CRITTER, /mob/living/basic/carp = FISH_ICON_DEF, /mob/living/basic/mining = FISH_ICON_HOSTILE, /mob/living/basic/skeleton = FISH_ICON_BONE, diff --git a/code/modules/fishing/sources/subtypes/structures.dm b/code/modules/fishing/sources/subtypes/structures.dm index 3d9f11e3c35..70afbc182ee 100644 --- a/code/modules/fishing/sources/subtypes/structures.dm +++ b/code/modules/fishing/sources/subtypes/structures.dm @@ -89,7 +89,7 @@ FISHING_RANDOM_SEED = 16, /obj/item/seeds/grass = 6, /obj/item/seeds/random = 1, - /mob/living/basic/frog = 1, + /obj/effect/spawner/random/frog = 1, /mob/living/basic/axolotl = 1, /mob/living/basic/turtle = 2, ) @@ -98,7 +98,7 @@ /obj/item/seeds/grass = 4, FISHING_RANDOM_SEED = 4, /obj/item/seeds/random = 1, - /mob/living/basic/frog = 1, + /obj/effect/spawner/random/frog = 1, /mob/living/basic/axolotl = 1, ) fishing_difficulty = FISHING_EASY_DIFFICULTY + 5 diff --git a/code/modules/fishing/sources/subtypes/turfs.dm b/code/modules/fishing/sources/subtypes/turfs.dm index 133312d5635..8468941681c 100644 --- a/code/modules/fishing/sources/subtypes/turfs.dm +++ b/code/modules/fishing/sources/subtypes/turfs.dm @@ -107,7 +107,7 @@ FISHING_DUD = 20, /obj/item/fish/bumpy = 10, /obj/item/fish/sacabambaspis = 10, - /mob/living/basic/frog = 2, + /obj/effect/spawner/random/frog = 2, /obj/item/fishing_rod/telescopic/master = 1, ) fish_counts = list( diff --git a/code/modules/mob/living/basic/jungle/leaper/leaper.dm b/code/modules/mob/living/basic/jungle/leaper/leaper.dm index 7e924bc0e20..f6276590fa4 100644 --- a/code/modules/mob/living/basic/jungle/leaper/leaper.dm +++ b/code/modules/mob/living/basic/jungle/leaper/leaper.dm @@ -42,7 +42,7 @@ /datum/pet_command/idle, /datum/pet_command/move, /datum/pet_command/free, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/untargeted_ability/blood_rain, /datum/pet_command/untargeted_ability/summon_toad, /datum/pet_command/attack, diff --git a/code/modules/mob/living/basic/jungle/leaper/leaper_abilities.dm b/code/modules/mob/living/basic/jungle/leaper/leaper_abilities.dm index 293881b6494..80e413fb1dd 100644 --- a/code/modules/mob/living/basic/jungle/leaper/leaper_abilities.dm +++ b/code/modules/mob/living/basic/jungle/leaper/leaper_abilities.dm @@ -239,7 +239,7 @@ overlay_icon_state = "bg_revenant_border" spell_requirements = NONE cooldown_time = 30 SECONDS - summon_type = list(/mob/living/basic/frog/frog_suicide) + summon_type = list(/mob/living/basic/frog/suicide) summon_radius = 2 summon_amount = 2 max_summons = 2 diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling.dm b/code/modules/mob/living/basic/jungle/seedling/seedling.dm index 9778aff8753..d6d189d2ebc 100644 --- a/code/modules/mob/living/basic/jungle/seedling/seedling.dm +++ b/code/modules/mob/living/basic/jungle/seedling/seedling.dm @@ -52,7 +52,7 @@ var/list/seedling_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, ) /mob/living/basic/seedling/Initialize(mapload) @@ -213,7 +213,7 @@ seedling_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/attack, /datum/pet_command/use_ability/solarbeam, /datum/pet_command/use_ability/rapidseeds, diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm index e8dcd3a0a32..f895e264db8 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm @@ -35,7 +35,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/grub_spit, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/fetch, ) diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm index 15c1d6f34c4..da1122b259b 100644 --- a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm @@ -122,7 +122,7 @@ /datum/pet_command/free, /datum/pet_command/attack, /datum/pet_command/breed/gutlunch, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/fetch, /datum/pet_command/mine_walls, ) diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index 11f7ff08363..0aac4e92e41 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -76,7 +76,7 @@ /datum/pet_command/move, /datum/pet_command/attack, charge_command, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/fish, ) AddComponent(/datum/component/happiness) diff --git a/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm b/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm index d442a1f5e71..2bbda673ed7 100644 --- a/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm +++ b/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm @@ -52,7 +52,7 @@ GLOBAL_LIST_EMPTY(raptor_population) /datum/pet_command/move, /datum/pet_command/free, /datum/pet_command/attack, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/fetch, ) ///things we inherited from our parent diff --git a/code/modules/mob/living/basic/minebots/minebot.dm b/code/modules/mob/living/basic/minebots/minebot.dm index bf906208f67..32316cafcfc 100644 --- a/code/modules/mob/living/basic/minebots/minebot.dm +++ b/code/modules/mob/living/basic/minebots/minebot.dm @@ -49,7 +49,7 @@ /datum/pet_command/minebot_ability/dump, /datum/pet_command/automate_mining, /datum/pet_command/free/minebot, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/attack/minebot, ) ///possible colors the bot can have diff --git a/code/modules/mob/living/basic/pets/cat/cat.dm b/code/modules/mob/living/basic/pets/cat/cat.dm index e3829b0b7af..d0deea930ab 100644 --- a/code/modules/mob/living/basic/pets/cat/cat.dm +++ b/code/modules/mob/living/basic/pets/cat/cat.dm @@ -49,7 +49,7 @@ var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/perform_trick_sequence, ) diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm index 247f4628090..24ae2e24f04 100644 --- a/code/modules/mob/living/basic/pets/dog/_dog.dm +++ b/code/modules/mob/living/basic/pets/dog/_dog.dm @@ -1,5 +1,6 @@ // Add 'walkies' as valid input /datum/pet_command/follow/dog + activate_on_befriend = TRUE speech_commands = list("heel", "follow", "walkies") // Add 'good dog' as valid input diff --git a/code/modules/mob/living/basic/pets/fox.dm b/code/modules/mob/living/basic/pets/fox.dm index a2e17211865..bcf93436d98 100644 --- a/code/modules/mob/living/basic/pets/fox.dm +++ b/code/modules/mob/living/basic/pets/fox.dm @@ -33,7 +33,7 @@ /datum/pet_command/idle, /datum/pet_command/move, /datum/pet_command/free, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/attack, /datum/pet_command/perform_trick_sequence, ) diff --git a/code/modules/mob/living/basic/snails/snail.dm b/code/modules/mob/living/basic/snails/snail.dm index c1e6ffc8e73..3e219548604 100644 --- a/code/modules/mob/living/basic/snails/snail.dm +++ b/code/modules/mob/living/basic/snails/snail.dm @@ -19,8 +19,11 @@ verb_exclaim = "gurgles loudly" verb_yell = "gurgles loudly" worn_slot_flags = ITEM_SLOT_HEAD + gold_core_spawnable = FRIENDLY_SPAWN faction = list(FACTION_NEUTRAL, FACTION_MAINT_CREATURES) ai_controller = /datum/ai_controller/basic_controller/snail + /// What do we turn into if effected by a regal rat? + var/minion_path = /mob/living/basic/snail/angry /mob/living/basic/snail/Initialize(mapload) . = ..() @@ -42,6 +45,9 @@ create_reagents(100, REAGENT_HOLDER_ALIVE) RegisterSignal(reagents, COMSIG_REAGENTS_HOLDER_UPDATED, PROC_REF(on_reagents_update)) + if (minion_path) + AddElement(/datum/element/regal_rat_minion, converted_path = minion_path, success_balloon = "gurgle", pet_commands = GLOB.regal_rat_minion_commands) + /mob/living/basic/snail/proc/on_entered(datum/source, obj/effect/decal/cleanable/food/salt/potential_salt) SIGNAL_HANDLER if(istype(potential_salt)) @@ -68,19 +74,36 @@ ) apply_damage(500) //ouch - /mob/living/basic/snail/mob_pickup(mob/living/user) var/obj/item/clothing/head/mob_holder/snail/holder = new(get_turf(src), src, held_state, head_icon, held_lh, held_rh, worn_slot_flags) var/display_message = "[user] [HAS_TRAIT(src, TRAIT_MOVE_FLOATING) ? "scoops up [src]" : "peels [src] off the ground"]!" user.visible_message(span_warning(display_message)) user.put_in_hands(holder) - /mob/living/basic/snail/update_icon_state() if(stat != DEAD) - icon_state = HAS_TRAIT(src, TRAIT_SHELL_RETREATED) ? "[base_icon_state]_shell" : "[base_icon_state][(faction.Find(FACTION_RAT)) ? "_maints" : ""]" + icon_state = HAS_TRAIT(src, TRAIT_SHELL_RETREATED) ? "snail_shell" : "[base_icon_state]" return ..() + +/// This snail is going to try and beat you up +/mob/living/basic/snail/angry + name = "sewer snail" + gold_core_spawnable = HOSTILE_SPAWN + icon_state = "snail_maints" + icon_living = "snail_maints" + icon_dead = "snail_maints_dead" + base_icon_state = "snail_maints" + health = 40 + maxHealth = 40 + melee_damage_lower = 5 + melee_damage_upper = 8 + obj_damage = 8 + can_be_held = FALSE + minion_path = null + ai_controller = /datum/ai_controller/basic_controller/snail/trash + + ///snail's custom holder object /obj/item/clothing/head/mob_holder/snail 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 8ed312ce7b3..e7bc44c4749 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 @@ -4,15 +4,21 @@ /datum/action/cooldown/mob_cooldown/domain name = "Rat King's Domain" - desc = "Corrupts this area to be more suitable for your rat army." + desc = "While enabled, continuously corrupt the surrounding area to be more suitable for your rat army." check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_INCAPACITATED click_to_activate = FALSE - cooldown_time = 6 SECONDS + cooldown_time = 1 SECONDS button_icon = 'icons/mob/actions/actions_animal.dmi' background_icon_state = "bg_clock" overlay_icon_state = "bg_clock_border" - button_icon_state = "coffer" + button_icon_state = "coffer_off" shared_cooldown = NONE + /// Are we currently ticking? + var/is_active = FALSE + /// How often do we make a mess? + var/mess_interval = 6 SECONDS + /// Don't do anything if we're on this cooldown + COOLDOWN_DECLARE(mess_cooldown) /datum/action/cooldown/mob_cooldown/domain/IsAvailable(feedback = FALSE) . = ..() @@ -23,25 +29,65 @@ owner.balloon_alert(owner, "can't use while ventcrawling!") return FALSE -/datum/action/cooldown/mob_cooldown/domain/proc/domain() - var/turf/location = get_turf(owner) - location.atmos_spawn_air("[GAS_MIASMA]=4;[TURF_TEMPERATURE(T20C)]") - switch (rand(1,10)) - if (8) - new /obj/effect/decal/cleanable/vomit(location) - if (9) - new /obj/effect/decal/cleanable/vomit/old(location) - if (10) - new /obj/effect/decal/cleanable/oil/slippery(location) - else - new /obj/effect/decal/cleanable/dirt(location) - StartCooldown() - /datum/action/cooldown/mob_cooldown/domain/Activate(atom/target) StartCooldown(10 SECONDS) - domain() + set_domain_active(!is_active) StartCooldown() +/datum/action/cooldown/mob_cooldown/domain/Remove(mob/removed_from) + set_domain_active(FALSE) + return ..() + +/datum/action/cooldown/mob_cooldown/domain/update_status_on_signal(datum/source, new_stat, old_stat) + . = ..() + if (!IsAvailable()) + set_domain_active(FALSE) + +/// Enable or disable the ability +/datum/action/cooldown/mob_cooldown/domain/proc/set_domain_active(should_active) + if (is_active == should_active || isnull(owner)) + return + is_active = should_active + + if (is_active) + RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_MOVE_VENTCRAWLING), PROC_REF(cancel_on_signal)) + button_icon_state = "coffer" + spread_domain() + else + UnregisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_MOVE_VENTCRAWLING)) + button_icon_state = "coffer_off" + + build_all_button_icons(update_flags = UPDATE_BUTTON_ICON) + +/// Stop spreading shit when one of these events happens +/datum/action/cooldown/mob_cooldown/domain/proc/cancel_on_signal() + SIGNAL_HANDLER + set_domain_active(FALSE) + +/// Create gas and spawn mess +/datum/action/cooldown/mob_cooldown/domain/proc/spread_domain() + if (!is_active || !COOLDOWN_FINISHED(src, mess_cooldown) || !owner) + return + + var/turf/our_location = get_turf(owner) + our_location.atmos_spawn_air("[GAS_MIASMA]=4;[TURF_TEMPERATURE(T20C)]") + + var/list/available_spots = list(our_location) + get_adjacent_open_turfs(owner) + var/turf/mess_location = pick(available_spots) + + switch (rand(1,10)) + if (8) + new /obj/effect/decal/cleanable/vomit(mess_location) + if (9) + new /obj/effect/decal/cleanable/vomit/old(mess_location) + if (10) + new /obj/effect/decal/cleanable/oil/slippery(mess_location) + else + new /obj/effect/decal/cleanable/dirt(mess_location) + + COOLDOWN_START(src, mess_cooldown, mess_interval) // We use a cooldown AND timer because of the toggle + addtimer(CALLBACK(src, PROC_REF(spread_domain)), mess_interval, TIMER_DELETE_ME) + /** * This action checks some nearby maintenance animals and makes them your minions. * If none are nearby, creates a new mouse. @@ -59,22 +105,6 @@ shared_cooldown = NONE /// How close does something need to be for us to recruit it? var/range = 5 - /// Commands you can give to your mouse army - var/static/list/mouse_commands = list( - /datum/pet_command/idle, - /datum/pet_command/free, - /datum/pet_command/protect_owner, - /datum/pet_command/follow, - /datum/pet_command/attack/mouse - ) - /// Commands you can give to glockroaches - var/static/list/glockroach_commands = list( - /datum/pet_command/idle, - /datum/pet_command/free, - /datum/pet_command/protect_owner/glockroach, - /datum/pet_command/follow, - /datum/pet_command/attack/glockroach - ) /datum/action/cooldown/mob_cooldown/riot/IsAvailable(feedback = FALSE) . = ..() @@ -98,145 +128,15 @@ * * Spawn a single mouse if below the mouse cap. */ /datum/action/cooldown/mob_cooldown/riot/proc/riot() - var/uplifted_mice = FALSE - for(var/mob/living/basic/mouse/nearby_mouse in oview(owner, range)) - uplifted_mice = convert_mouse(nearby_mouse) || uplifted_mice - if (uplifted_mice) - owner.visible_message(span_warning("[owner] commands their army to action, mutating them into rats!")) - return + playsound(owner, 'sound/mobs/non-humanoids/mouse/mousesqueek.ogg', vol = 150, frequency = 10000) - var/static/list/converted_check_list = list(FACTION_RAT) - var/uplifted_roach = FALSE - for (var/mob/living/basic/cockroach/nearby_roach in oview(owner, range)) - uplifted_roach = convert_roach(nearby_roach, converted_check_list) || uplifted_roach - if (uplifted_roach) - owner.visible_message(span_warning("[owner] commands their army to action, mutating them into sewer roaches!")) - return + new /obj/effect/temp_visual/circle_wave/brown(get_turf(owner)) + for (var/mob/living/possible_minion in oview(owner, range)) + SEND_SIGNAL(possible_minion, COMSIG_REGAL_RAT_RIOTED, owner) - var/uplifted_frog = FALSE - for (var/mob/living/basic/frog/nearby_frog in oview(owner, range)) - uplifted_frog = convert_frog(nearby_frog, converted_check_list) || uplifted_frog - if (uplifted_frog) - owner.visible_message(span_warning("[owner] commands their army to action, mutating them into trash frogs!")) - return - - var/uplifted_snail = FALSE - for(var/mob/living/basic/snail/nearby_snail in oview(owner, range)) - uplifted_snail = convert_snail(nearby_snail, converted_check_list) || uplifted_snail - if(uplifted_snail) - owner.visible_message(span_warning("[owner] commands their army to action, mutating them into death snails!")) - return - - var/rat_cap = CONFIG_GET(number/ratcap) - if (LAZYLEN(SSmobs.cheeserats) >= rat_cap) - to_chat(owner,span_warning("There's too many mice on this station to beckon a new one! Find them first!")) - return - new /mob/living/basic/mouse(owner.loc) - owner.visible_message(span_warning("[owner] commands a mouse to their side!")) - -/// Makes a passed mob into our minion -/datum/action/cooldown/mob_cooldown/riot/proc/make_minion(mob/living/new_minion, minion_desc, list/command_list = mouse_commands) - if (isbasicmob(new_minion)) - new_minion.AddComponent(/datum/component/obeys_commands, command_list) - qdel(new_minion.GetComponent(/datum/component/tameable)) // Rats don't share - new_minion.befriend(owner) - new_minion.faction = owner.faction.Copy() - // Give a hint in description too - new_minion.desc += minion_desc - new_minion.balloon_alert_to_viewers("squeak") - -/// Turns a mouse into an angry mouse -/datum/action/cooldown/mob_cooldown/riot/proc/convert_mouse(mob/living/basic/mouse/nearby_mouse) - // This mouse is already rat controlled, let's not bother with it. - if (istype(nearby_mouse.ai_controller, /datum/ai_controller/basic_controller/mouse/rat)) - return FALSE - - var/mob/living/basic/mouse/rat/rat_path = /mob/living/basic/mouse/rat - // Change name - if (nearby_mouse.name == "mouse") - nearby_mouse.name = initial(rat_path.name) - // Buffs our combat stats to that of a rat - nearby_mouse.melee_damage_lower = initial(rat_path.melee_damage_lower) - nearby_mouse.melee_damage_upper = initial(rat_path.melee_damage_upper) - nearby_mouse.obj_damage = initial(rat_path.obj_damage) - nearby_mouse.maxHealth = initial(rat_path.maxHealth) - nearby_mouse.health = initial(rat_path.health) - // Replace our AI with a rat one - nearby_mouse.ai_controller = new /datum/ai_controller/basic_controller/mouse/rat(nearby_mouse) - make_minion(nearby_mouse, " ...Except this one looks corrupted and aggressive.") - return TRUE - -/// Turns a roach into an angry roach -/datum/action/cooldown/mob_cooldown/riot/proc/convert_roach(mob/living/basic/cockroach/nearby_roach, list/converted_check_list) - // No need to convert when not on the same team. - if (faction_check(nearby_roach.faction, converted_check_list)) - return FALSE - - var/list/minion_commands = mouse_commands - if (!findtext(nearby_roach.name, "sewer")) - nearby_roach.name = "sewer [nearby_roach.name]" - - if (istype(nearby_roach, /mob/living/basic/cockroach/glockroach) || istype(nearby_roach, /mob/living/basic/cockroach/hauberoach)) - if (istype(nearby_roach, /mob/living/basic/cockroach/glockroach)) - minion_commands = glockroach_commands - nearby_roach.melee_damage_lower += 0.5 - nearby_roach.melee_damage_upper += 2 - else - nearby_roach.melee_damage_lower += 2 - nearby_roach.melee_damage_upper += 4 - nearby_roach.obj_damage += 5 - nearby_roach.ai_controller = new /datum/ai_controller/basic_controller/cockroach/sewer(nearby_roach) - nearby_roach.melee_attack_cooldown = 0.8 SECONDS - - nearby_roach.icon_state += "_sewer" - nearby_roach.maxHealth += 1 - nearby_roach.health += 1 - make_minion(nearby_roach, "
This one looks extra robust.", minion_commands) - return TRUE - -/// Turns a frog into a crazy frog. This doesn't do anything interesting and should when it becomes a basic mob. -/datum/action/cooldown/mob_cooldown/riot/proc/convert_frog(mob/living/basic/frog/nearby_frog, list/converted_check_list) - // No need to convert when not on the same team. - if(faction_check(nearby_frog.faction, converted_check_list) || nearby_frog.stat == DEAD) - return FALSE - - var/list/minion_commands = mouse_commands - if (!findtext(nearby_frog.name, "trash")) - nearby_frog.name = replacetext(nearby_frog.name, "frog", "trash frog") - - nearby_frog.icon_state += "_trash" - nearby_frog.icon_living += "_trash" - nearby_frog.icon_dead = nearby_frog.icon_state + "_dead" - nearby_frog.maxHealth += 10 - nearby_frog.health += 10 - nearby_frog.melee_damage_lower += 1 - nearby_frog.melee_damage_upper += 5 - nearby_frog.obj_damage += 10 - nearby_frog.ai_controller = new /datum/ai_controller/basic_controller/frog/trash(nearby_frog) - var/crazy_frog_desc = " ...[findtext(nearby_frog.name, "rare") ? "even though" : "perhaps because"] they live in a trash bag." - make_minion(nearby_frog, crazy_frog_desc, minion_commands) - return TRUE - -/datum/action/cooldown/mob_cooldown/riot/proc/convert_snail(mob/living/basic/snail/nearby_snail, list/converted_check_list) - // No need to convert when not on the same team. - if(faction_check(nearby_snail.faction, converted_check_list) || nearby_snail.stat == DEAD) - return FALSE - - - nearby_snail.icon_state = "[nearby_snail.base_icon_state]_maints" - nearby_snail.icon_living = "[nearby_snail.base_icon_state]_maints" - nearby_snail.icon_dead = "[nearby_snail.base_icon_state]_maints_dead" - nearby_snail.maxHealth += 10 - nearby_snail.health += 10 - nearby_snail.melee_damage_lower += 5 - nearby_snail.melee_damage_upper += 8 - nearby_snail.can_be_held = FALSE - nearby_snail.obj_damage += 8 - nearby_snail.fully_replace_character_name(nearby_snail.name, "trash [nearby_snail.name]") - nearby_snail.ai_controller = new /datum/ai_controller/basic_controller/snail/trash(nearby_snail) - make_minion(nearby_snail, " ...This one doesn't look as timid.", mouse_commands) - nearby_snail.update_appearance() - return TRUE +/obj/effect/temp_visual/circle_wave/brown + color = COLOR_BROWN + amount_to_scale = 4 // Command you can give to a mouse to make it kill someone /datum/pet_command/attack/mouse 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 073029bef3c..d763e63b703 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 @@ -25,3 +25,9 @@ /datum/ai_planning_subtree/use_mob_ability/domain ability_key = BB_DOMAIN_ABILITY + +/datum/ai_planning_subtree/use_mob_ability/domain/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/datum/action/cooldown/mob_cooldown/domain/domain = controller.blackboard[ability_key] + if (!istype(domain) || domain.is_active) + return + return ..() diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider.dm b/code/modules/mob/living/basic/space_fauna/spider/spider.dm index 6c8f72bc11a..ace8d97bbfb 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spider.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spider.dm @@ -193,7 +193,7 @@ var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/perform_trick_sequence, ) diff --git a/code/modules/mob/living/basic/vermin/cockroach.dm b/code/modules/mob/living/basic/vermin/cockroach/cockroach.dm similarity index 67% rename from code/modules/mob/living/basic/vermin/cockroach.dm rename to code/modules/mob/living/basic/vermin/cockroach/cockroach.dm index c6eead9a166..394abd5b87e 100644 --- a/code/modules/mob/living/basic/vermin/cockroach.dm +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach.dm @@ -34,6 +34,10 @@ ai_controller = /datum/ai_controller/basic_controller/cockroach var/cockroach_cell_line = CELL_LINE_TABLE_COCKROACH + /// What do we turn into if recruited by a regal rat? + var/minion_path = /mob/living/basic/cockroach/sewer + /// Command list given to minionised cockroaches + var/list/minion_commands /mob/living/basic/cockroach/Initialize(mapload) var/turf/our_turf = get_turf(src) @@ -57,6 +61,14 @@ ADD_TRAIT(src, TRAIT_NUKEIMMUNE, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_RADIMMUNE, INNATE_TRAIT) + if (minion_path) + set_pet_commands() + AddElement(/datum/element/regal_rat_minion, converted_path = minion_path, success_balloon = "hiss", pet_commands = minion_commands) + +/// Decide what this should be able to be told to do +/mob/living/basic/cockroach/proc/set_pet_commands() + minion_commands = GLOB.regal_rat_minion_commands + /mob/living/basic/cockroach/ex_act() //Explosions are a terrible way to handle a cockroach. return FALSE @@ -64,76 +76,22 @@ /mob/living/basic/cockroach/spawn_gibs() return -/datum/ai_controller/basic_controller/cockroach - 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." - ) - ) - - ai_traits = 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/random_speech/insect, - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) - -/obj/projectile/glockroachbullet - damage = 10 //same damage as a hivebot - damage_type = BRUTE - -/obj/item/ammo_casing/glockroach - name = "0.9mm bullet casing" - desc = "A... 0.9mm bullet casing? What?" - projectile_type = /obj/projectile/glockroachbullet - - -/mob/living/basic/cockroach/glockroach - name = "glockroach" - desc = "HOLY SHIT, THAT COCKROACH HAS A GUN!" - icon_state = "glockroach" - melee_damage_lower = 2.5 - melee_damage_upper = 10 - obj_damage = 10 +/// Roach which tries to ineffectually attack you +/mob/living/basic/cockroach/sewer + name = "sewer roach" + icon_state = "cockroach_sewer" + desc = "This bug has a really bad attitude." + health = 2 + maxHealth = 2 // Wow! + melee_damage_lower = 2 + melee_damage_upper = 4 + obj_damage = 5 + melee_attack_cooldown = 0.8 SECONDS gold_core_spawnable = HOSTILE_SPAWN - faction = list(FACTION_HOSTILE, FACTION_MAINT_CREATURES) - ai_controller = /datum/ai_controller/basic_controller/cockroach/glockroach - cockroach_cell_line = CELL_LINE_TABLE_GLOCKROACH - ///number of burst shots - var/burst_shots - ///cooldown between attacks - var/ranged_cooldown = 1 SECONDS - -/mob/living/basic/cockroach/glockroach/Initialize(mapload) - . = ..() - AddComponent(\ - /datum/component/ranged_attacks,\ - casing_type = /obj/item/ammo_casing/glockroach,\ - burst_shots = burst_shots,\ - cooldown_time = ranged_cooldown,\ - ) - if (ranged_cooldown <= 1 SECONDS) - AddComponent(/datum/component/ranged_mob_full_auto) - -/datum/ai_controller/basic_controller/cockroach/glockroach - planning_subtrees = list( - /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 + minion_path = null + ai_controller = /datum/ai_controller/basic_controller/cockroach/aggro +/// Roach with a spiky hat, like a caltrop /mob/living/basic/cockroach/hauberoach name = "hauberoach" desc = "Is that cockroach wearing a tiny yet immaculate replica 19th century Prussian spiked helmet? ...Is that a bad thing?" @@ -149,8 +107,9 @@ attack_vis_effect = ATTACK_EFFECT_SLASH faction = list(FACTION_HOSTILE, FACTION_MAINT_CREATURES) sharpness = SHARP_POINTY - ai_controller = /datum/ai_controller/basic_controller/cockroach/hauberoach + ai_controller = /datum/ai_controller/basic_controller/cockroach/aggro cockroach_cell_line = CELL_LINE_TABLE_HAUBEROACH + minion_path = /mob/living/basic/cockroach/hauberoach/imperial /mob/living/basic/cockroach/hauberoach/Initialize(mapload) . = ..() @@ -173,43 +132,92 @@ living_target.visible_message(span_notice("[living_target] squashes [cockroach], not even noticing its spike."), span_notice("You squashed [cockroach], not even noticing its spike.")) return FALSE -/datum/ai_controller/basic_controller/cockroach/hauberoach - planning_subtrees = list( - /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, //If we are attacking someone, this will prevent us from hunting - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) +/// Regal rat royal escort +/mob/living/basic/cockroach/hauberoach/imperial + name = "imperial hauberoach" + desc = "This cockroach seems to have found employment as a professional royal guard." + health = 2 + maxHealth = 2 + melee_damage_lower = 3 + melee_damage_upper = 12 + icon_state = "hauberoach_sewer" + minion_path = null + gold_core_spawnable = NO_SPAWN -/datum/ai_controller/basic_controller/cockroach/sewer - planning_subtrees = list( - /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, - ) +/// Roach with a gun +/mob/living/basic/cockroach/glockroach + name = "glockroach" + desc = "HOLY SHIT, THAT COCKROACH HAS A GUN!" + icon_state = "glockroach" + melee_damage_lower = 2.5 + melee_damage_upper = 10 + obj_damage = 10 + gold_core_spawnable = HOSTILE_SPAWN + faction = list(FACTION_HOSTILE, FACTION_MAINT_CREATURES) + ai_controller = /datum/ai_controller/basic_controller/cockroach/glockroach + cockroach_cell_line = CELL_LINE_TABLE_GLOCKROACH + minion_path = /mob/living/basic/cockroach/glockroach/gang + ///number of burst shots + var/burst_shots + ///cooldown between attacks + var/ranged_cooldown = 1 SECONDS +/mob/living/basic/cockroach/glockroach/Initialize(mapload) + . = ..() + AddComponent(\ + /datum/component/ranged_attacks,\ + casing_type = /obj/item/ammo_casing/glockroach,\ + burst_shots = burst_shots,\ + cooldown_time = ranged_cooldown,\ + ) + if (ranged_cooldown <= 1 SECONDS) + AddComponent(/datum/component/ranged_mob_full_auto) + +/mob/living/basic/cockroach/glockroach/set_pet_commands() + var/static/list/glockroach_commands = list( + /datum/pet_command/idle, + /datum/pet_command/free, + /datum/pet_command/protect_owner/glockroach, + /datum/pet_command/follow, + /datum/pet_command/attack/glockroach + ) + minion_commands = glockroach_commands + +/mob/living/basic/cockroach/glockroach/gang + name = "gangroach" + desc = "This roach grew up on the wrong side of the streets and has fallen in with the wrong crowd." + icon_state = "glockroach_sewer" + health = 2 + maxHealth = 2 + minion_path = null + gold_core_spawnable = NO_SPAWN + +/obj/projectile/glockroachbullet + damage = 10 //same damage as a hivebot + damage_type = BRUTE + +/obj/item/ammo_casing/glockroach + name = "0.9mm bullet casing" + desc = "A... 0.9mm bullet casing? What?" + projectile_type = /obj/projectile/glockroachbullet + +/// Roach with an SMG /mob/living/basic/cockroach/glockroach/mobroach name = "mobroach" - desc = "WE'RE FUCKED, THAT GLOCKROACH HAS A TOMMYGUN!" + desc = "WE'RE FUCKED, THAT COCKROACH HAS A TOMMYGUN!" icon_state = "mobroach" - ai_controller = /datum/ai_controller/basic_controller/cockroach/mobroach burst_shots = 4 ranged_cooldown = 2 SECONDS + minion_path = /mob/living/basic/cockroach/glockroach/mobroach/goon -/datum/ai_controller/basic_controller/cockroach/mobroach - planning_subtrees = list( - /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, //If we are attacking someone, this will prevent us from hunting - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) + ai_controller = /datum/ai_controller/basic_controller/cockroach/glockroach -/datum/ai_planning_subtree/basic_ranged_attack_subtree/mobroach - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/mobroach +/mob/living/basic/cockroach/glockroach/mobroach/goon + name = "goonroach" + desc = "You got it, boss." + icon_state = "mobroach_sewer" + health = 2 + maxHealth = 2 + minion_path = null + gold_core_spawnable = NO_SPAWN -/datum/ai_behavior/basic_ranged_attack/mobroach - action_cooldown = 2 SECONDS diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm b/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm new file mode 100644 index 00000000000..78f451a6852 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm @@ -0,0 +1,61 @@ + +/// AI controller for normal roach +/datum/ai_controller/basic_controller/cockroach + 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." + ) + ) + + ai_traits = 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/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/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, + ) + +/// AI controller for roach who can shoot at you +/datum/ai_controller/basic_controller/cockroach/glockroach + planning_subtrees = list( + /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 + +/// roach who shoots at you slightly slower +/datum/ai_controller/basic_controller/cockroach/mobroach + planning_subtrees = list( + /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, + ) + +/datum/ai_planning_subtree/basic_ranged_attack_subtree/mobroach + ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/mobroach + +/datum/ai_behavior/basic_ranged_attack/mobroach + action_cooldown = 2 SECONDS diff --git a/code/modules/mob/living/basic/vermin/frog.dm b/code/modules/mob/living/basic/vermin/frog.dm index 32efc7f5793..32604286c81 100644 --- a/code/modules/mob/living/basic/vermin/frog.dm +++ b/code/modules/mob/living/basic/vermin/frog.dm @@ -45,15 +45,14 @@ var/poison_per_bite = 3 ///What reagent the mob injects targets with var/poison_type = /datum/reagent/drug/space_drugs + ///What type do we become if influenced by a regal rat? + var/minion_type = /mob/living/basic/frog/crazy /mob/living/basic/frog/Initialize(mapload) . = ..() add_traits(list(TRAIT_NODROWN, TRAIT_SWIMMER, TRAIT_VENTCRAWLER_ALWAYS), INNATE_TRAIT) - if(prob(1)) - make_rare() - var/static/list/loc_connections = list( COMSIG_ATOM_ENTERED = PROC_REF(on_entered), ) @@ -61,23 +60,56 @@ AddElement(/datum/element/venomous, poison_type, poison_per_bite) AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/swabable, CELL_LINE_TABLE_FROG, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) + if (minion_type) + AddElement(/datum/element/regal_rat_minion, converted_path = minion_type, success_balloon = "ribbit", pet_commands = GLOB.regal_rat_minion_commands) -/mob/living/basic/frog/proc/make_rare() +/mob/living/basic/frog/proc/on_entered(datum/source, entered as mob|obj) + SIGNAL_HANDLER + if(stat || !isliving(entered)) + return + var/mob/living/entered_mob = entered + if(entered_mob.mob_size > MOB_SIZE_TINY) + playsound(src, stepped_sound, vol = 50, vary = TRUE) + +/mob/living/basic/frog/rare name = "rare frog" desc = "They seem a little smug." - icon_state = "rare_[icon_state]" - icon_living = "rare_[icon_living]" - icon_dead = "rare_[icon_dead]" + icon_state = "rare_frog" + icon_living = "rare_frog" + icon_dead = "rare_frog_dead" + gold_core_spawnable = NO_SPAWN + butcher_results = list(/obj/item/food/nugget = 5) + poison_type = /datum/reagent/drug/mushroomhallucinogen + minion_type = /mob/living/basic/frog/crazy/rare + +/// These frogs would REALLY rather like to get at your blood basically by any means possible +/mob/living/basic/frog/crazy + name = "trash frog" + desc = "They seem a little mad." + icon_state = "frog_trash" + icon_living = "frog_trash" + icon_dead = "frog_trash_dead" + health = 25 + maxHealth = 25 + melee_damage_lower = 6 + melee_damage_upper = 15 + obj_damage = 20 + minion_type = null + gold_core_spawnable = HOSTILE_SPAWN + ai_controller = /datum/ai_controller/basic_controller/frog/trash + +/mob/living/basic/frog/crazy/rare + name = "crazy frog" + desc = "They look hopping mad." + icon_state = "rare_frog_trash" + icon_living = "rare_frog_trash" + icon_dead = "rare_frog_trash_dead" + minion_type = null + gold_core_spawnable = NO_SPAWN butcher_results = list(/obj/item/food/nugget = 5) poison_type = /datum/reagent/drug/mushroomhallucinogen -/mob/living/basic/frog/proc/on_entered(datum/source, AM as mob|obj) - SIGNAL_HANDLER - if(!stat && isliving(AM)) - var/mob/living/L = AM - if(L.mob_size > MOB_SIZE_TINY) - playsound(src, stepped_sound, 50, TRUE) - +/// The cold doesn't bother him /mob/living/basic/frog/icemoon_facility name = "Peter Jr." desc = "They seem a little cold." @@ -86,11 +118,17 @@ habitable_atmos = null gold_core_spawnable = NO_SPAWN -/mob/living/basic/frog/icemoon_facility/make_rare() - . = ..() - name = "Peter Sr." //make him senior. +/mob/living/basic/frog/icemoon_facility/crazy + name = "Crazy Pete" + desc = "The cold is really getting to him." + icon_state = "frog_trash" + icon_living = "frog_trash" + icon_dead = "frog_trash_dead" + ai_controller = /datum/ai_controller/basic_controller/frog/trash -/mob/living/basic/frog/frog_suicide + +/// Frog spawned by leapers which explodes on attack +/mob/living/basic/frog/suicide name = "suicide frog" desc = "Driven by sheer will." icon_state = "frog_trash" @@ -99,10 +137,11 @@ maxHealth = 5 health = 5 ai_controller = /datum/ai_controller/basic_controller/frog/suicide_frog + minion_type = null ///how long do we exist for var/existence_period = 15 SECONDS -/mob/living/basic/frog/frog_suicide/Initialize(mapload) +/mob/living/basic/frog/suicide/Initialize(mapload) . = ..() 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) diff --git a/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm b/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm index 5f522385f87..7d3dd6906c2 100644 --- a/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm +++ b/code/modules/mob/living/basic/vermin/mothroach/mothroach.dm @@ -37,7 +37,7 @@ var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/perform_trick_sequence, ) diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm index e28382e580b..b0cffa70f21 100644 --- a/code/modules/mob/living/basic/vermin/mouse.dm +++ b/code/modules/mob/living/basic/vermin/mouse.dm @@ -40,7 +40,7 @@ var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/follow, + /datum/pet_command/follow/start_active, /datum/pet_command/perform_trick_sequence, ) @@ -86,6 +86,8 @@ var/static/list/food_types = list(/obj/item/food/cheese) AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 100) + AddElement(/datum/element/regal_rat_minion, converted_path = /mob/living/basic/mouse/rat, pet_commands = GLOB.regal_rat_minion_commands) + /mob/living/basic/mouse/Destroy() SSmobs.cheeserats -= src return ..() @@ -300,7 +302,7 @@ name = "rat" desc = "They're a nasty, ugly, evil, disease-ridden rodent with anger issues." - gold_core_spawnable = NO_SPAWN + gold_core_spawnable = HOSTILE_SPAWN melee_damage_lower = 3 melee_damage_upper = 5 obj_damage = 5 diff --git a/code/modules/mob/mob_transformation_simple.dm b/code/modules/mob/mob_transformation_simple.dm index fd2280dba72..b73df686d44 100644 --- a/code/modules/mob/mob_transformation_simple.dm +++ b/code/modules/mob/mob_transformation_simple.dm @@ -14,7 +14,7 @@ if(istext(new_type)) new_type = text2path(new_type) - if( !ispath(new_type) ) + if(!ispath(new_type) ) to_chat(usr, "Invalid type path (new_type = [new_type]) in change_mob_type(). Contact a coder.") return diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm index b662423b11f..0da491c8a97 100644 --- a/code/modules/recycling/disposal/bin.dm +++ b/code/modules/recycling/disposal/bin.dm @@ -143,21 +143,32 @@ king.visible_message(span_warning("[king] starts rummaging through [src]."),span_notice("You rummage through [src]...")) if (!do_after(king, 2 SECONDS, src, interaction_key = "regalrat")) return + + var/cheese = FALSE var/loot = rand(1,100) switch(loot) if(1 to 5) to_chat(king, span_notice("You find some leftover coins. More for the royal treasury!")) var/pickedcoin = pick(GLOB.ratking_coins) for(var/i = 1 to rand(1,3)) - new pickedcoin(get_turf(king)) + new pickedcoin(king.drop_location()) if(6 to 33) + cheese = TRUE king.say(pick("Treasure!","Our precious!","Cheese!"), ignore_spam = TRUE, forced = "regal rat rummaging") to_chat(king, span_notice("Score! You find some cheese!")) - new /obj/item/food/cheese/wedge(get_turf(king)) + new /obj/item/food/cheese/wedge(king.drop_location()) else var/pickedtrash = pick(GLOB.ratking_trash) to_chat(king, span_notice("You just find more garbage and dirt. Lovely, but beneath you now.")) - new pickedtrash(get_turf(king)) + new pickedtrash(king.drop_location()) + + if (cheese) + return // We don't want them to eat your reward + var/rat_cap = CONFIG_GET(number/ratcap) + if (LAZYLEN(SSmobs.cheeserats) < rat_cap && prob(33)) + var/mob/living/basic/mouse/new_subject = new(king.drop_location()) + playsound(new_subject, 'sound/mobs/non-humanoids/mouse/mousesqueek.ogg', 100) + visible_message(span_warning("[new_subject] climbs out of [src]!")) /// Moves an item into the diposal bin /obj/machinery/disposal/proc/place_item_in_disposal(obj/item/I, mob/user) 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 590cddf055d..65581357957 100644 --- a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm +++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm @@ -578,7 +578,7 @@ /datum/reagent/toxin = -1) virus_suspectibility = 0.5 - resulting_atom = /mob/living/basic/frog + resulting_atom = /obj/effect/spawner/random/frog /datum/micro_organism/cell_line/axolotl desc = "caudata amphibian cells" diff --git a/icons/mob/actions/actions_animal.dmi b/icons/mob/actions/actions_animal.dmi index c13290716b75c6238000082f51802f44e5ac9949..b0b58b463f545bced676fe73f1db88d82cc42d2b 100644 GIT binary patch literal 20357 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 JPDHLkV1hj$BlZ9Q diff --git a/tgstation.dme b/tgstation.dme index 9e1f225b294..f67f0ef8d68 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1734,6 +1734,7 @@ #include "code\datums\elements\radiation_protected_clothing.dm" #include "code\datums\elements\radioactive.dm" #include "code\datums\elements\ranged_armour.dm" +#include "code\datums\elements\regal_rat_minion.dm" #include "code\datums\elements\relay_attackers.dm" #include "code\datums\elements\ridable.dm" #include "code\datums\elements\rust.dm" @@ -5434,12 +5435,13 @@ #include "code\modules\mob\living\basic\turtle\turtle_ai.dm" #include "code\modules\mob\living\basic\vermin\axolotl.dm" #include "code\modules\mob\living\basic\vermin\butterfly.dm" -#include "code\modules\mob\living\basic\vermin\cockroach.dm" #include "code\modules\mob\living\basic\vermin\crab.dm" #include "code\modules\mob\living\basic\vermin\frog.dm" #include "code\modules\mob\living\basic\vermin\lizard.dm" #include "code\modules\mob\living\basic\vermin\mouse.dm" #include "code\modules\mob\living\basic\vermin\space_bat.dm" +#include "code\modules\mob\living\basic\vermin\cockroach\cockroach.dm" +#include "code\modules\mob\living\basic\vermin\cockroach\cockroach_ai.dm" #include "code\modules\mob\living\basic\vermin\mothroach\mothroach.dm" #include "code\modules\mob\living\basic\vermin\mothroach\mothroach_ai.dm" #include "code\modules\mob\living\brain\brain.dm"