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 c13290716b7..b0b58b463f5 100644 Binary files a/icons/mob/actions/actions_animal.dmi and b/icons/mob/actions/actions_animal.dmi differ 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"