diff --git a/_maps/RandomRuins/IceRuins/icemoon_surface_bughabitat.dmm b/_maps/RandomRuins/IceRuins/icemoon_surface_bughabitat.dmm index fb0f269ba29..eb846bfba7d 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_surface_bughabitat.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_surface_bughabitat.dmm @@ -334,7 +334,7 @@ dir = 8 }, /obj/item/plunger, -/mob/living/simple_animal/mouse, +/mob/living/basic/mouse, /obj/machinery/light/small/blacklight/directional/south, /turf/open/floor/plastic, /area/ruin/bughabitat) diff --git a/_maps/RandomRuins/SpaceRuins/hilbertresearchfacility.dmm b/_maps/RandomRuins/SpaceRuins/hilbertresearchfacility.dmm index 9c267ad9937..ffd83de0c7f 100644 --- a/_maps/RandomRuins/SpaceRuins/hilbertresearchfacility.dmm +++ b/_maps/RandomRuins/SpaceRuins/hilbertresearchfacility.dmm @@ -2222,7 +2222,7 @@ /area/ruin/space/has_grav/powered/hilbertresearchfacility) "WA" = ( /obj/item/storage/bag/trash/bluespace, -/mob/living/simple_animal/hostile/rat, +/mob/living/basic/mouse/rat, /obj/effect/decal/cleanable/blood/old, /turf/open/floor/plating, /area/ruin/space/has_grav/powered/hilbertresearchfacility) diff --git a/_maps/RandomZLevels/SnowCabin.dmm b/_maps/RandomZLevels/SnowCabin.dmm index 72d391c43d5..eb3c29fe592 100644 --- a/_maps/RandomZLevels/SnowCabin.dmm +++ b/_maps/RandomZLevels/SnowCabin.dmm @@ -1765,7 +1765,7 @@ /area/awaymission/cabin/caves/mountain) "fL" = ( /obj/effect/decal/cleanable/dirt, -/mob/living/simple_animal/mouse{ +/mob/living/basic/mouse{ faction = list("sewer") }, /turf/open/floor/carpet, diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 24249edd19a..4f080ece2b1 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -28528,7 +28528,7 @@ /area/station/maintenance/fore) "gQa" = ( /obj/effect/decal/cleanable/dirt, -/mob/living/simple_animal/mouse/brown/tom, +/mob/living/basic/mouse/brown/tom, /turf/open/floor/iron, /area/station/security/prison/garden) "gQd" = ( @@ -43522,7 +43522,7 @@ /area/station/security/execution/transfer) "kxm" = ( /obj/machinery/light/directional/east, -/mob/living/simple_animal/mouse/white, +/mob/living/basic/mouse/white, /turf/open/floor/iron/cafeteria, /area/station/security/prison/mess) "kxs" = ( @@ -57537,7 +57537,7 @@ /turf/open/floor/iron, /area/station/medical/virology) "nWw" = ( -/mob/living/simple_animal/mouse/gray, +/mob/living/basic/mouse/gray, /turf/open/floor/iron, /area/station/security/prison) "nWF" = ( diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index d148a692b76..3ad29e7ea4f 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -19958,7 +19958,7 @@ /area/station/security/detectives_office) "fYQ" = ( /obj/machinery/light/small/directional/west, -/mob/living/simple_animal/mouse/brown/tom, +/mob/living/basic/mouse/brown/tom, /turf/open/floor/plating, /area/station/security/prison/safe) "fYS" = ( @@ -20206,7 +20206,7 @@ /obj/item/food/canned/beans, /obj/item/food/canned/beans, /obj/effect/decal/cleanable/dirt/dust, -/mob/living/simple_animal/mouse/white, +/mob/living/basic/mouse/white, /turf/open/floor/iron, /area/station/maintenance/starboard/fore) "gbF" = ( @@ -75639,7 +75639,7 @@ /area/station/security/office) "xlH" = ( /obj/effect/mapping_helpers/broken_floor, -/mob/living/simple_animal/mouse/white{ +/mob/living/basic/mouse/white{ desc = "This mouse smells faintly of alcohol."; name = "Mik" }, diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm index 3769963bfef..b1bf739424c 100644 --- a/_maps/map_files/KiloStation/KiloStation.dmm +++ b/_maps/map_files/KiloStation/KiloStation.dmm @@ -79790,7 +79790,7 @@ c_tag = "Prison Maintenance"; network = list("ss13","prison") }, -/mob/living/simple_animal/mouse/brown/tom{ +/mob/living/basic/mouse/brown/tom{ name = "Jerm" }, /turf/open/floor/iron/grimy, diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 366b59cc0f7..b6fe7585cb9 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -3657,7 +3657,7 @@ "bmX" = ( /obj/machinery/light/small/directional/west, /obj/structure/sink/directional/south, -/mob/living/simple_animal/mouse/brown/tom, +/mob/living/basic/mouse/brown/tom, /turf/open/floor/plating, /area/station/security/prison/safe) "bno" = ( diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index 2562023f16a..950660675f9 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -14152,7 +14152,7 @@ default_raw_text = "ratsratsratsratsratsratsratsratsratsratsratsratsrats" }, /obj/effect/decal/cleanable/dirt, -/mob/living/simple_animal/mouse, +/mob/living/basic/mouse, /turf/open/floor/plating, /area/station/maintenance/tram/right) "eZs" = ( @@ -44862,7 +44862,7 @@ /turf/open/floor/plating, /area/station/hallway/primary/tram/left) "pZr" = ( -/mob/living/simple_animal/mouse/brown/tom, +/mob/living/basic/mouse/brown/tom, /turf/open/misc/asteroid, /area/station/security/prison/workout) "pZu" = ( @@ -60542,7 +60542,7 @@ /area/station/commons/toilet) "vBi" = ( /obj/effect/decal/cleanable/dirt, -/mob/living/simple_animal/mouse, +/mob/living/basic/mouse, /turf/open/floor/plating, /area/station/maintenance/tram/right) "vBC" = ( diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index f65b9c96ecf..d088294bfff 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -197,12 +197,9 @@ #define BB_BANE_BATMAN "BB_bane_batman" //yep thats it - -//Hunting defines -#define SUCCESFUL_HUNT_COOLDOWN 5 SECONDS - ///Hunting BB keys #define BB_CURRENT_HUNTING_TARGET "BB_current_hunting_target" +#define BB_LOW_PRIORITY_HUNTING_TARGET "BB_low_priority_hunting_target" #define BB_HUNTING_COOLDOWN "BB_HUNTING_COOLDOWN" ///Basic Mob Keys diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 5a312020538..3ac1ccf973c 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -136,7 +136,7 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list( #define isshade(A) (istype(A, /mob/living/simple_animal/shade)) -#define ismouse(A) (istype(A, /mob/living/simple_animal/mouse)) +#define ismouse(A) (istype(A, /mob/living/basic/mouse)) #define isslime(A) (istype(A, /mob/living/simple_animal/slime)) @@ -150,8 +150,6 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list( #define ishostile(A) (istype(A, /mob/living/simple_animal/hostile)) -#define israt(A) (istype(A, /mob/living/simple_animal/hostile/rat)) - #define isregalrat(A) (istype(A, /mob/living/simple_animal/hostile/regalrat)) #define isguardian(A) (istype(A, /mob/living/simple_animal/hostile/guardian)) diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 1241b5aca60..157d5f61d76 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -362,6 +362,8 @@ #define SHOCK_ILLUSION (1 << 2) ///The shock doesn't stun. #define SHOCK_NOSTUN (1 << 3) +/// No default message is sent from the shock +#define SHOCK_SUPPRESS_MESSAGE (1 << 4) #define INCORPOREAL_MOVE_BASIC 1 /// normal movement, see: [/mob/living/var/incorporeal_move] #define INCORPOREAL_MOVE_SHADOW 2 /// leaves a trail of shadows diff --git a/code/controllers/subsystem/minor_mapping.dm b/code/controllers/subsystem/minor_mapping.dm index 79132307ce4..20ce60f36e2 100644 --- a/code/controllers/subsystem/minor_mapping.dm +++ b/code/controllers/subsystem/minor_mapping.dm @@ -16,7 +16,7 @@ SUBSYSTEM_DEF(minor_mapping) /datum/controller/subsystem/minor_mapping/proc/trigger_migration(num_mice=10) var/list/exposed_wires = find_exposed_wires() - var/mob/living/simple_animal/mouse/mouse + var/mob/living/basic/mouse/mouse var/turf/open/proposed_turf diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm index 6420cc6fb0c..0ebb2d3a3ff 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm @@ -1,8 +1,9 @@ /datum/ai_behavior/find_potential_targets action_cooldown = 2 SECONDS + /// How far can we see stuff? var/vision_range = 9 - ///List of potentially dangerous objs - var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/vehicle/sealed/mecha)) + /// Static typecache list of potentially dangerous objs + var/static/list/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/vehicle/sealed/mecha)) /datum/ai_behavior/find_potential_targets/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) . = ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm index e02f8d3bc17..f6ad41d4e01 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm @@ -4,6 +4,6 @@ . = ..() var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] var/atom/target = weak_target?.resolve() - if(target && !QDELETED(target)) + if(!QDELETED(target)) return controller.queue_behavior(/datum/ai_behavior/find_potential_targets, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm index c85414fcd21..ba1be6b784a 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm @@ -36,12 +36,18 @@ /datum/ai_planning_subtree/random_speech/cockroach speech_chance = 5 - emote_hear = list("chitters") + emote_hear = list("chitters.") /datum/ai_planning_subtree/random_speech/mothroach speech_chance = 15 emote_hear = list("flutters.") +/datum/ai_planning_subtree/random_speech/mouse + speech_chance = 1 + speak = list("Squeak!", "SQUEAK!", "Squeak?") + emote_hear = list("squeaks.") + emote_see = list("runs in a circle.", "shakes.") + /datum/ai_planning_subtree/random_speech/sheep speech_chance = 5 speak = list("baaa","baaaAAAAAH!","baaah") diff --git a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm index e20ba88ee4a..5b1ffcd0e4e 100644 --- a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm +++ b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm @@ -13,6 +13,8 @@ return target_hiding_location /datum/targetting_datum/basic + /// When we do our basic faction check, do we look for exact faction matches? + var/check_factions_exactly = FALSE /datum/targetting_datum/basic/can_attack(mob/living/living_mob, atom/the_target) if(isturf(the_target) || !the_target) // bail out on invalids @@ -31,7 +33,7 @@ if(isliving(the_target)) //Targeting vs living mobs var/mob/living/L = the_target - var/faction_check = living_mob.faction_check_mob(L) + var/faction_check = living_mob.faction_check_mob(L, exact_match = check_factions_exactly) if(faction_check || L.stat) return FALSE return TRUE diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm index ed4208dfab6..f80b49833fb 100644 --- a/code/datums/ai/hunting_behavior/hunting_behaviors.dm +++ b/code/datums/ai/hunting_behavior/hunting_behaviors.dm @@ -1,73 +1,127 @@ +/** + * Tells the AI to find a certain target nearby to hunt. + * If a target has been found, we will start to move towards it, and eventually attack it. + */ /datum/ai_planning_subtree/find_and_hunt_target - var/list/hunt_targets = list(/obj/effect/decal/cleanable/ants) + /// What key in the blacbkboard do we store our hunting target? + /// If you want to have multiple hunting behaviors on a controller be sure that this is unique + var/target_key = BB_CURRENT_HUNTING_TARGET + /// What behavior to execute if we have no target + var/finding_behavior = /datum/ai_behavior/find_hunt_target + /// What behavior to execute if we do have a target + var/hunting_behavior = /datum/ai_behavior/hunt_target + /// What targets we're hunting for + var/list/hunt_targets + /// In what radius will we hunt + var/hunt_range = 2 + /// What are the chances we hunt something at any given moment + var/hunt_chance = 100 /datum/ai_planning_subtree/find_and_hunt_target/New() . = ..() hunt_targets = typecacheof(hunt_targets) /datum/ai_planning_subtree/find_and_hunt_target/SelectBehaviors(datum/ai_controller/controller, delta_time) - . = ..() + if(!DT_PROB(hunt_chance, delta_time)) + return if(controller.blackboard[BB_HUNTING_COOLDOWN] >= world.time) return - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(!target || QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_hunt_target, BB_CURRENT_HUNTING_TARGET, hunt_targets) + var/mob/living/living_pawn = controller.pawn + // We can't hunt if we're indisposed + if(HAS_TRAIT(controller.pawn, TRAIT_HANDS_BLOCKED) || living_pawn.stat != CONSCIOUS) + return + + // We're targeting something else for another reason + var/datum/weakref/target_weakref = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/atom/target = target_weakref?.resolve() + if(!QDELETED(target)) + return + + var/datum/weakref/hunting_weakref = controller.blackboard[target_key] + var/atom/hunted = hunting_weakref?.resolve() + // We're not hunting anything, look around for something + if(QDELETED(hunted)) + controller.queue_behavior(finding_behavior, target_key, hunt_targets, hunt_range) + + // We ARE hunting something, execute the hunt. + // Note that if our AI controller has multiple hunting subtrees set, + // we may accidentally be executing another tree's hunt - not ideal, + // try to set a unique target key if you have multiple else - controller.queue_behavior(/datum/ai_behavior/hunt_target, BB_CURRENT_HUNTING_TARGET, BB_HUNTING_COOLDOWN) + controller.queue_behavior(hunting_behavior, target_key, BB_HUNTING_COOLDOWN) return SUBTREE_RETURN_FINISH_PLANNING //If we're hunting we're too busy for anything else - -///Literaly for hunting specific mobs. +/// Finds a specific atom type to hunt. /datum/ai_behavior/find_hunt_target -/datum/ai_behavior/find_hunt_target/perform(delta_time, datum/ai_controller/controller, hunting_target_key, types_to_hunt) +/datum/ai_behavior/find_hunt_target/perform(delta_time, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) . = ..() var/mob/living/living_mob = controller.pawn - for(var/possible_dinner in typecache_filter_list(range(2, living_mob), types_to_hunt)) - if(isliving(possible_dinner)) - var/mob/living/living_target = possible_dinner - if(living_target.stat == DEAD) //bitch is dead - continue - if(can_see(living_mob, possible_dinner, 2)) - controller.blackboard[hunting_target_key] = possible_dinner - finish_action(controller, TRUE) - return + for(var/atom/possible_dinner as anything in typecache_filter_list(range(hunt_range, living_mob), types_to_hunt)) + if(!valid_dinner(living_mob, possible_dinner, hunt_range)) + continue + controller.blackboard[hunting_target_key] = WEAKREF(possible_dinner) + finish_action(controller, TRUE) + return finish_action(controller, FALSE) + +/datum/ai_behavior/find_hunt_target/proc/valid_dinner(mob/living/source, atom/dinner, radius) + if(isliving(dinner)) + var/mob/living/living_target = dinner + if(living_target.stat == DEAD) //bitch is dead + return FALSE + + return can_see(source, dinner, radius) + +/// Hunts down a specific atom type. /datum/ai_behavior/hunt_target behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + /// How long do we have to wait after a successful hunt? + var/hunt_cooldown = 5 SECONDS /datum/ai_behavior/hunt_target/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) . = ..() - controller.current_movement_target = controller.blackboard[hunting_target_key] + var/datum/weakref/hunting_weakref = controller.blackboard[hunting_target_key] + controller.current_movement_target = hunting_weakref?.resolve() /datum/ai_behavior/hunt_target/perform(delta_time, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) . = ..() + var/mob/living/hunter = controller.pawn + var/datum/weakref/hunting_weakref = controller.blackboard[hunting_target_key] + var/atom/hunted = hunting_weakref?.resolve() - if(!controller.blackboard[hunting_target_key]) //Target is gone for some reason. forget about this task! + if(QDELETED(hunted)) + //Target is gone for some reason. forget about this task! controller[hunting_target_key] = null finish_action(controller, FALSE, hunting_target_key) - return - - var/mob/living/hunter = controller.pawn - var/atom/hunted = controller.blackboard[hunting_target_key] + else + target_caught(hunter, hunted) + finish_action(controller, TRUE, hunting_target_key, hunting_cooldown_key) +/datum/ai_behavior/hunt_target/proc/target_caught(mob/living/hunter, atom/hunted) if(isliving(hunted)) // Are we hunting a living mob? var/mob/living/living_target = hunted hunter.manual_emote("chomps [living_target]!") living_target.death() + else if(IS_EDIBLE(hunted)) + hunted.attack_animal(hunter) + else // We're hunting an object, and should delete it instead of killing it. Mostly useful for decal bugs like ants or spider webs. hunter.manual_emote("chomps [hunted]!") qdel(hunted) - finish_action(controller, TRUE, hunting_target_key, hunting_cooldown_key) - return /datum/ai_behavior/hunt_target/finish_action(datum/ai_controller/controller, succeeded, hunting_target_key, hunting_cooldown_key) . = ..() if(succeeded) - controller.blackboard[hunting_cooldown_key] = world.time + SUCCESFUL_HUNT_COOLDOWN + controller.blackboard[hunting_cooldown_key] = world.time + hunt_cooldown else if(hunting_target_key) controller.blackboard[hunting_target_key] = null + +/datum/ai_behavior/hunt_target/unarmed_attack_target + +/datum/ai_behavior/hunt_target/unarmed_attack_target/target_caught(mob/living/hunter, obj/structure/cable/hunted) + hunter.UnarmedAttack(hunted, TRUE) diff --git a/code/datums/ai/hunting_behavior/hunting_cockroach.dm b/code/datums/ai/hunting_behavior/hunting_cockroach.dm new file mode 100644 index 00000000000..a786342bfa0 --- /dev/null +++ b/code/datums/ai/hunting_behavior/hunting_cockroach.dm @@ -0,0 +1,2 @@ +/datum/ai_planning_subtree/find_and_hunt_target/roach + hunt_targets = list(/obj/effect/decal/cleanable/ants) diff --git a/code/datums/ai/hunting_behavior/hunting_mouse.dm b/code/datums/ai/hunting_behavior/hunting_mouse.dm new file mode 100644 index 00000000000..d4160f826dd --- /dev/null +++ b/code/datums/ai/hunting_behavior/hunting_mouse.dm @@ -0,0 +1,32 @@ +// Mouse subtree to hunt down delicious cheese. +/datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/mouse + hunt_targets = list(/obj/item/food/cheese) + hunt_range = 1 + +// Mouse subtree to hunt down ... delicious cabling? +/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables + target_key = BB_LOW_PRIORITY_HUNTING_TARGET + hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/mouse + finding_behavior = /datum/ai_behavior/find_hunt_target/mouse_cable + hunt_targets = list(/obj/structure/cable) + hunt_range = 0 // Only look below us + hunt_chance = 5 + +// When looking for a cable, we can only bite things we can reach. +/datum/ai_behavior/find_hunt_target/mouse_cable + +/datum/ai_behavior/find_hunt_target/mouse_cable/valid_dinner(mob/living/source, obj/structure/cable/dinner, radius) + . = ..() + if(!.) + return + + var/turf/open/floor/below_the_cable = get_turf(dinner) + if(!istype(below_the_cable)) + return FALSE + + return below_the_cable.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE + +// Our hunts have a decent cooldown. +/datum/ai_behavior/hunt_target/unarmed_attack_target/mouse + hunt_cooldown = 20 SECONDS diff --git a/code/datums/ai/movement/_ai_movement.dm b/code/datums/ai/movement/_ai_movement.dm index fa83d64e798..ef43406d55d 100644 --- a/code/datums/ai/movement/_ai_movement.dm +++ b/code/datums/ai/movement/_ai_movement.dm @@ -15,7 +15,9 @@ /datum/ai_movement/proc/stop_moving_towards(datum/ai_controller/controller) controller.pathing_attempts = 0 moving_controllers -= controller - SSmove_manager.stop_looping(controller.pawn, SSai_movement) + // We got deleted as we finished an action + if(!QDELETED(controller.pawn)) + SSmove_manager.stop_looping(controller.pawn, SSai_movement) /datum/ai_movement/proc/increment_pathing_failures(datum/ai_controller/controller) controller.pathing_attempts++ diff --git a/code/datums/elements/animal_variety.dm b/code/datums/elements/animal_variety.dm index 2ed9cc1b344..4874f74334e 100644 --- a/code/datums/elements/animal_variety.dm +++ b/code/datums/elements/animal_variety.dm @@ -1,40 +1,50 @@ /** - * # animal variety element! + * # Animal variety element! * - * bespoke element that picks an suffix to append onto the icon state from a list given on creation, among some pixel shifting stuff - * basically you will see VARIETY in a batch of simplemobs. + * Element that picks an suffix to append onto the icon state from a list given on creation, + * among some pixel shifting stuff. Basically you will see VARIETY in a batch of animals. */ /datum/element/animal_variety - element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH - id_arg_index = 2 - - /// prefix for the icon state, this will always stay the same - var/icon_prefix - /// chosen sprite suffix, in element args it picks from a list on what this will be - var/chosen_sprite_suffix - /// boolean on whether the element should also mess with the pixel_x and pixel_y - var/modify_pixels /datum/element/animal_variety/Attach(datum/target, icon_prefix, chosen_sprite_suffix, modify_pixels = FALSE) . = ..() - if(!isanimal(target)) - return ELEMENT_INCOMPATIBLE - var/mob/living/simple_animal/animal = target - src.icon_prefix = icon_prefix - src.chosen_sprite_suffix = chosen_sprite_suffix - src.modify_pixels = modify_pixels - animal.icon_state = "[icon_prefix]_[chosen_sprite_suffix]" - animal.icon_living = "[icon_prefix]_[chosen_sprite_suffix]" - animal.icon_dead = "[icon_prefix]_[chosen_sprite_suffix]_dead" - if(modify_pixels) - animal.pixel_x = animal.base_pixel_x + rand(-6, 6) - animal.pixel_y = animal.base_pixel_y + rand(0, 10) + if(isanimal(target)) + var/mob/living/simple_animal/animal = target + animal.icon_state = "[icon_prefix]_[chosen_sprite_suffix]" + animal.icon_living = "[icon_prefix]_[chosen_sprite_suffix]" + animal.icon_dead = "[icon_prefix]_[chosen_sprite_suffix]_dead" + if(modify_pixels) + animal.pixel_x = animal.base_pixel_x + rand(-6, 6) + animal.pixel_y = animal.base_pixel_y + rand(0, 10) + return + + if(isbasicmob(target)) + var/mob/living/basic/animal = target + animal.icon_state = "[icon_prefix]_[chosen_sprite_suffix]" + animal.icon_living = "[icon_prefix]_[chosen_sprite_suffix]" + animal.icon_dead = "[icon_prefix]_[chosen_sprite_suffix]_dead" + if(modify_pixels) // Yeah I know I didn't actually need to copy and paste this part but basicmob's entire existence is copypaste + animal.pixel_x = animal.base_pixel_x + rand(-6, 6) + animal.pixel_y = animal.base_pixel_y + rand(0, 10) + return + + return ELEMENT_INCOMPATIBLE /datum/element/animal_variety/Detach(datum/target) - var/mob/living/simple_animal/animal = target - animal.icon_state = initial(animal.icon_state) - animal.icon_living = initial(animal.icon_living) - animal.icon_dead = initial(animal.icon_living) - animal.pixel_x = animal.base_pixel_x - animal.pixel_y = animal.base_pixel_y - . = ..() + if(isanimal(target)) + var/mob/living/simple_animal/animal = target + animal.icon_state = initial(animal.icon_state) + animal.icon_living = initial(animal.icon_living) + animal.icon_dead = initial(animal.icon_living) + animal.pixel_x = animal.base_pixel_x + animal.pixel_y = animal.base_pixel_y + + if(isbasicmob(target)) + var/mob/living/basic/animal = target + animal.icon_state = initial(animal.icon_state) + animal.icon_living = initial(animal.icon_living) + animal.icon_dead = initial(animal.icon_living) + animal.pixel_x = animal.base_pixel_x + animal.pixel_y = animal.base_pixel_y + + return ..() diff --git a/code/datums/memory/memory.dm b/code/datums/memory/memory.dm index 11a5b3b5433..300e6fca33b 100644 --- a/code/datums/memory/memory.dm +++ b/code/datums/memory/memory.dm @@ -70,7 +70,7 @@ /mob/living/simple_animal/crab, /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/pet/cat, - /mob/living/simple_animal/mouse, + /mob/living/basic/mouse, /mob/living/simple_animal/chicken, /mob/living/basic/cow, /mob/living/simple_animal/hostile/lizard, @@ -205,5 +205,3 @@ var/detail = extra_info[key] line = replacetext(line, "%[key]", "[detail]") name = line - - diff --git a/code/datums/storage/subtypes/pockets.dm b/code/datums/storage/subtypes/pockets.dm index f8a594230b2..6713e84c667 100644 --- a/code/datums/storage/subtypes/pockets.dm +++ b/code/datums/storage/subtypes/pockets.dm @@ -61,7 +61,7 @@ . = ..() if(ispickedupmob(to_insert)) var/obj/item/clothing/head/mob_holder/mausholder = to_insert - if(locate(/mob/living/simple_animal/mouse) in mausholder.contents) + if(locate(/mob/living/basic/mouse) in mausholder.contents) return return FALSE diff --git a/code/game/objects/structures/toiletbong.dm b/code/game/objects/structures/toiletbong.dm index 0c21f500cf4..a29d683b50d 100644 --- a/code/game/objects/structures/toiletbong.dm +++ b/code/game/objects/structures/toiletbong.dm @@ -55,7 +55,7 @@ user.visible_message(span_danger("[user] spits out a mouse.")) user.adjust_disgust(50) user.vomit(10) - var/mob/living/spawned_mob = new /mob/living/simple_animal/mouse(get_turf(user)) + var/mob/living/spawned_mob = new /mob/living/basic/mouse(get_turf(user)) spawned_mob.faction |= "[REF(user)]" if(prob(50)) for(var/j in 1 to rand(1, 3)) diff --git a/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm b/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm index 7f720cf888f..f723b8e788c 100644 --- a/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm +++ b/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm @@ -11,7 +11,7 @@ spell_requirements = NONE possible_shapes = list( - /mob/living/simple_animal/mouse, + /mob/living/basic/mouse, /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/hostile/carp, /mob/living/simple_animal/bot/secbot, diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm index 90098f8dbb9..085fb8f28bf 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -136,16 +136,17 @@ if(affecting.receive_damage(1, 0)) victim.update_damage_overlays() else if(ismouse(target)) - var/mob/living/simple_animal/mouse/M = target + var/mob/living/basic/mouse/splatted = target visible_message(span_boldannounce("SPLAT!")) - M.splat() - else if(israt(target)) - var/mob/living/simple_animal/hostile/rat/ratt = target - visible_message(span_boldannounce("Clink!")) - ratt.apply_damage(5) //Not lethal, but just enought to make a mark. - ratt.Stun(1 SECONDS) + if(splatted.health <= 5) + splatted.splat() + else + splatted.adjust_health(5) + splatted.Stun(1 SECONDS) + else if(isregalrat(target)) visible_message(span_boldannounce("Skreeeee!")) //He's simply too large to be affected by a tiny mouse trap. + playsound(src, 'sound/effects/snap.ogg', 50, TRUE) pulse(FALSE) @@ -199,7 +200,7 @@ INVOKE_ASYNC(src, .proc/triggered, H) H.visible_message(span_warning("[H] accidentally steps on [src]."), \ span_warning("You accidentally step on [src]")) - else if(ismouse(MM) || israt(MM) || isregalrat(MM)) + else if(ismouse(MM) || isregalrat(MM)) INVOKE_ASYNC(src, .proc/triggered, MM) else if(AM.density) // For mousetrap grenades, set off by anything heavy INVOKE_ASYNC(src, .proc/triggered, AM) diff --git a/code/modules/events/ghost_role/sentience.dm b/code/modules/events/ghost_role/sentience.dm index 3888c4cf631..4c4ea1ff22d 100644 --- a/code/modules/events/ghost_role/sentience.dm +++ b/code/modules/events/ghost_role/sentience.dm @@ -3,7 +3,7 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( /mob/living/simple_animal/parrot, /mob/living/simple_animal/hostile/lizard, /mob/living/simple_animal/sloth, - /mob/living/simple_animal/mouse/brown/tom, + /mob/living/basic/mouse/brown/tom, /mob/living/simple_animal/hostile/retaliate/goat, /mob/living/simple_animal/chicken, /mob/living/basic/cow, diff --git a/code/modules/events/scrubber_clog.dm b/code/modules/events/scrubber_clog.dm index ba5d079f9a7..cb1b62b53bc 100644 --- a/code/modules/events/scrubber_clog.dm +++ b/code/modules/events/scrubber_clog.dm @@ -64,7 +64,7 @@ /datum/round_event/scrubber_clog/proc/get_mob() var/static/list/mob_list = list( - /mob/living/simple_animal/mouse, + /mob/living/basic/mouse, /mob/living/basic/cockroach, /mob/living/simple_animal/butterfly, ) @@ -145,7 +145,7 @@ /datum/round_event/scrubber_clog/major/get_mob() var/static/list/mob_list = list( - /mob/living/simple_animal/hostile/rat, + /mob/living/basic/mouse/rat, /mob/living/simple_animal/hostile/bee, /mob/living/simple_animal/hostile/giant_spider, ) diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index 553eb06acd8..2ab2660a8e3 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -32,7 +32,7 @@ description = "A scientist needs vermin to test on, use the cytology equipment to grow some of these simple critters!" total_requirement = 3 max_requirement_per_type = 2 - possible_types = list(/mob/living/basic/cockroach, /mob/living/simple_animal/mouse) + possible_types = list(/mob/living/basic/cockroach, /mob/living/basic/mouse) /datum/experiment/scanning/random/cytology/medium name = "Advanced Cytology Scanning Experiment" diff --git a/code/modules/mob/living/basic/vermin/cockroach.dm b/code/modules/mob/living/basic/vermin/cockroach.dm index 420b5f3ba55..25467e0825c 100644 --- a/code/modules/mob/living/basic/vermin/cockroach.dm +++ b/code/modules/mob/living/basic/vermin/cockroach.dm @@ -57,8 +57,8 @@ idle_behavior = /datum/idle_behavior/idle_random_walk planning_subtrees = list( /datum/ai_planning_subtree/random_speech/cockroach, - /datum/ai_planning_subtree/find_and_hunt_target -) + /datum/ai_planning_subtree/find_and_hunt_target/roach, + ) /obj/projectile/glockroachbullet damage = 10 //same damage as a hivebot @@ -91,7 +91,7 @@ /datum/ai_planning_subtree/random_speech/cockroach, /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 + /datum/ai_planning_subtree/find_and_hunt_target/roach, ) /datum/ai_planning_subtree/basic_ranged_attack_subtree/glockroach @@ -136,7 +136,7 @@ /datum/ai_planning_subtree/random_speech/cockroach, /datum/ai_planning_subtree/simple_find_target, /datum/ai_planning_subtree/basic_melee_attack_subtree/hauberoach, //If we are attacking someone, this will prevent us from hunting - /datum/ai_planning_subtree/find_and_hunt_target + /datum/ai_planning_subtree/find_and_hunt_target/roach, ) /datum/ai_planning_subtree/basic_melee_attack_subtree/hauberoach diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm new file mode 100644 index 00000000000..8409c20b6cb --- /dev/null +++ b/code/modules/mob/living/basic/vermin/mouse.dm @@ -0,0 +1,371 @@ +/mob/living/basic/mouse + name = "mouse" + desc = "This cute little guy just loves the taste of uninsulated electrical cables. Isn't he adorable?" + icon_state = "mouse_gray" + icon_living = "mouse_gray" + icon_dead = "mouse_gray_dead" + held_state = "mouse_gray" + + maxHealth = 5 + health = 5 + see_in_dark = 6 + density = FALSE + pass_flags = PASSTABLE|PASSGRILLE|PASSMOB + mob_size = MOB_SIZE_TINY + can_be_held = TRUE + held_w_class = WEIGHT_CLASS_TINY + mob_biotypes = MOB_ORGANIC|MOB_BEAST + gold_core_spawnable = FRIENDLY_SPAWN + faction = list(FACTION_RAT) + butcher_results = list(/obj/item/food/meat/slab/mouse = 1) + + speak_emote = list("squeaks") + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + response_harm_continuous = "splats" + response_harm_simple = "splat" + + ai_controller = /datum/ai_controller/basic_controller/mouse + + /// What color our mouse is. Brown, gray and white - leave blank for random. + var/body_color + /// Does this mouse contribute to the ratcap? + var/contributes_to_ratcap = TRUE + /// Probability that, if we successfully bite a shocked cable, that we will die to it. + var/cable_zap_prob = 85 + +/mob/living/basic/mouse/Initialize(mapload) + . = ..() + if(contributes_to_ratcap) + SSmobs.cheeserats |= src + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + + if(isnull(body_color)) + body_color = pick("brown", "gray", "white") + held_state = "mouse_[body_color]" // not handled by variety element + AddElement(/datum/element/animal_variety, "mouse", body_color, FALSE) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOUSE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 10) + AddComponent(/datum/component/squeak, list('sound/effects/mousesqueek.ogg' = 1), 100, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) //as quiet as a mouse or whatever + + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = .proc/on_entered, + ) + AddElement(/datum/element/connect_loc, loc_connections) + +/mob/living/basic/mouse/Destroy() + SSmobs.cheeserats -= src + return ..() + +/mob/living/basic/mouse/examine(mob/user) + . = ..() + + var/sameside = user.faction_check_mob(src, exact_match = TRUE) + if(isregalrat(user)) + if(sameside) + . += span_notice("This rat serves under you.") + else + . += span_warning("This peasant serves a different king! Strike [p_them()] down!") + + else if(user != src && ismouse(user)) + if(sameside) + . += span_notice("You both serve the same king.") + else + . += span_warning("This fool serves a different king!") + +/// Kills the rat and changes its icon state to be splatted (bloody). +/mob/living/basic/mouse/proc/splat() + icon_dead = "mouse_[body_color]_splat" + adjust_health(-maxHealth) + +// On revival, re-add the mouse to the ratcap, or block it if we're at it +/mob/living/basic/mouse/revive(full_heal = FALSE, admin_revive = FALSE) + if(!contributes_to_ratcap) + return ..() + + var/cap = CONFIG_GET(number/ratcap) + if(!admin_revive && !ckey && length(SSmobs.cheeserats) >= cap) + visible_message(span_warning("[src] twitches, but does not continue moving \ + due to the overwhelming rodent population on the station!")) + return + + . = ..() + if(stat != DEAD) + SSmobs.cheeserats |= src + +// On death, remove the mouse from the ratcap, and turn it into an item if applicable +/mob/living/basic/mouse/death(gibbed) + SSmobs.cheeserats -= src + // Rats with a mind will not turn into a lizard snack on death + if(mind) + return ..() + + // Call parent with gibbed = TRUE, becuase we're getting rid of the body + . = ..(TRUE) + // Now if we were't ACTUALLY gibbed, spawn the dead mouse + if(!gibbed) + var/obj/item/food/deadmouse/mouse = new(loc) + mouse.name = name + mouse.icon_state = icon_dead + if(HAS_TRAIT(src, TRAIT_BEING_SHOCKED)) + mouse.desc = "They're toast." + mouse.add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY) + qdel(src) + +/mob/living/basic/mouse/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + . = ..() + if(!.) + return + + if(!proximity_flag) + return + + if(istype(attack_target, /obj/item/food/cheese)) + try_consume_cheese(attack_target) + return TRUE + + if(istype(attack_target, /obj/structure/cable)) + try_bite_cable(attack_target) + return TRUE + +/// Signal proc for [COMSIG_ATOM_ENTERED]. Sends a lil' squeak to chat when someone walks over us. +/mob/living/basic/mouse/proc/on_entered(datum/source, atom/movable/entered) + SIGNAL_HANDLER + + if(ishuman(entered) && stat == CONSCIOUS) + to_chat(entered, span_notice("[icon2html(src, entered)] Squeak!")) + +/// Attempts to consume a piece of cheese, causing a few effects. +/mob/living/basic/mouse/proc/try_consume_cheese(obj/item/food/cheese/cheese) + // Royal cheese will evolve us into a regal rat + if(istype(cheese, /obj/item/food/cheese/royal)) + visible_message( + span_warning("[src] devours [cheese]! They morph into something... greater!"), + span_notice("You devour [cheese], and start morphing into something... greater!"), + ) + evolve_into_regal_rat() + qdel(cheese) + return + + var/cap = CONFIG_GET(number/ratcap) + // Normal cheese will either heal us + if(prob(90) || health < maxHealth) + visible_message( + span_notice("[src] nibbles [cheese]."), + span_notice("You nibble [cheese][health < maxHealth ? ", restoring your health" : ""].") + ) + adjust_health(-maxHealth) + + // Or, if we're at full health, there's a 10% chance that normal cheese will spawn a new mouse + // ...if the rat cap allows us, that is + else if(length(SSmobs.cheeserats) >= cap) + visible_message( + span_warning("[src] carefully eats [cheese], hiding it from the [cap] mice on the station!"), + span_notice("You carefully nibble [cheese], hiding it from the [cap] other mice on board the station.") + ) + else + visible_message( + span_notice("[src] nibbles through [cheese], attracting another mouse!"), + span_notice("You nibble through [cheese], attracting another mouse!") + ) + create_a_new_rat() + + qdel(cheese) + +/// Evolves this rat into a regal rat +/mob/living/basic/mouse/proc/evolve_into_regal_rat() + var/mob/living/simple_animal/hostile/regalrat/controlled/regalrat = new(loc) + mind?.transfer_to(regalrat) + INVOKE_ASYNC(regalrat, /atom/movable/proc/say, "RISE, MY SUBJECTS! SCREEEEEEE!") + qdel(src) + +/// Creates a new mouse based on this mouse's subtype. +/mob/living/basic/mouse/proc/create_a_new_rat() + new /mob/living/basic/mouse(loc) + +/// Biting into a cable will cause a mouse to get shocked and die if applicable. Or do nothing if they're lucky. +/mob/living/basic/mouse/proc/try_bite_cable(obj/structure/cable/cable) + if(cable.avail() && !HAS_TRAIT(src, TRAIT_SHOCKIMMUNE) && prob(cable_zap_prob)) + visible_message( + span_warning("[src] chews through \the [cable]. It's toast!"), + span_userdanger("As you bite deeply into [cable], you suddenly realize this may have been a bad idea."), + span_hear("You hear electricity crack."), + ) + // Finely toasted + ADD_TRAIT(src, TRAIT_BEING_SHOCKED, TRAIT_GENERIC) + // Unfortunately we can't check the return value of electrocute_act before displaying a message, + // as it's possible the damage from electrocution results in our hunter being deleted. + // But what are the odds of the shock failing? Hahaha... + electrocute_act(maxHealth * 2, cable, flags = SHOCK_SUPPRESS_MESSAGE) + + else + visible_message( + span_warning("[src] chews through \the [cable]."), + span_notice("You chew through \the [cable]."), + ) + + playsound(cable, 'sound/effects/sparks2.ogg', 100, TRUE) + cable.deconstruct() + +/mob/living/basic/mouse/white + body_color = "white" + icon_state = "mouse_white" + held_state = "mouse_white" + +/mob/living/basic/mouse/gray + body_color = "gray" + icon_state = "mouse_gray" + +/mob/living/basic/mouse/brown + body_color = "brown" + icon_state = "mouse_brown" + held_state = "mouse_brown" + +//TOM IS ALIVE! SQUEEEEEEEE~K :) +/mob/living/basic/mouse/brown/tom + name = "Tom" + desc = "Jerry the cat is not amused." + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + response_harm_continuous = "splats" + response_harm_simple = "splat" + gold_core_spawnable = NO_SPAWN + +/mob/living/basic/mouse/brown/tom/Initialize(mapload) + . = ..() + // Tom fears no cable. + ADD_TRAIT(src, TRAIT_SHOCKIMMUNE, INNATE_TRAIT) + AddElement(/datum/element/pet_bonus, "squeaks happily!") + +/mob/living/basic/mouse/brown/tom/create_a_new_rat() + new /mob/living/basic/mouse/brown(loc) // dominant gene + +/mob/living/basic/mouse/rat + name = "rat" + desc = "They're a nasty, ugly, evil, disease-ridden rodent with anger issues." + + melee_damage_lower = 3 + melee_damage_upper = 5 + obj_damage = 5 + maxHealth = 15 + health = 15 + + ai_controller = /datum/ai_controller/basic_controller/mouse/rat + +/mob/living/basic/mouse/rat/create_a_new_rat() + new /mob/living/basic/mouse/rat(loc) + +/// Mice turn into food when they die +/obj/item/food/deadmouse + name = "dead mouse" + desc = "They look like somebody dropped the bass on it. A lizard's favorite meal." + icon = 'icons/mob/simple/animal.dmi' + icon_state = "mouse_gray_dead" + bite_consumption = 3 + eatverbs = list("devour") + food_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 2) + foodtypes = GORE | MEAT | RAW + grind_results = list(/datum/reagent/blood = 20, /datum/reagent/liquidgibs = 5) + decomp_req_handle = TRUE + ant_attracting = FALSE + decomp_type = /obj/item/food/deadmouse/moldy + +/obj/item/food/deadmouse/Initialize(mapload) + . = ..() + AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOUSE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 10) + +/obj/item/food/deadmouse/examine(mob/user) + . = ..() + if (reagents?.has_reagent(/datum/reagent/yuck) || reagents?.has_reagent(/datum/reagent/fuel)) + . += span_warning("[p_theyre(TRUE)] dripping with fuel and smells terrible.") + +/obj/item/food/deadmouse/attackby(obj/item/attacking_item, mob/user, params) + var/mob/living/living_user = user + if(istype(living_user) && attacking_item.get_sharpness() && living_user.combat_mode) + if(!isturf(loc)) + balloon_alert(user, "can't butcher here!") + return + + balloon_alert(user, "butchering...") + if(!do_after(user, 0.75 SECONDS, src)) + balloon_alert(user, "interrupted!") + return + + loc.balloon_alert(user, "butchered") + new /obj/item/food/meat/slab/mouse(loc) + qdel(src) + return + + return ..() + +/obj/item/food/deadmouse/afterattack(obj/target, mob/living/user, proximity_flag) + . = ..() + if(proximity_flag && reagents && target.is_open_container()) + // is_open_container will not return truthy if target.reagents doesn't exist + var/datum/reagents/target_reagents = target.reagents + var/trans_amount = reagents.maximum_volume - reagents.total_volume * (4 / 3) + if(target_reagents.has_reagent(/datum/reagent/fuel) && target_reagents.trans_to(src, trans_amount)) + to_chat(user, span_notice("You dip [src] into [target].")) + else + to_chat(user, span_warning("That's a terrible idea.")) + return + +/obj/item/food/deadmouse/moldy + name = "moldy dead mouse" + desc = "A dead rodent, consumed by mold and rot. There is a slim chance that a lizard might still eat it." + icon_state = "mouse_gray_dead" + food_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 2, /datum/reagent/consumable/mold = 10) + foodtypes = GORE | MEAT | RAW | GROSS + grind_results = list(/datum/reagent/blood = 20, /datum/reagent/liquidgibs = 5, /datum/reagent/consumable/mold = 10) + preserved_food = TRUE + +/// The mouse AI controller +/datum/ai_controller/basic_controller/mouse + blackboard = list( + BB_CURRENT_HUNTING_TARGET = null, // cheese + BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable + ) + + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + // Top priority is to look for and execute hunts for cheese + /datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese, + // Try to speak after a cheese hunt, because it's cute + /datum/ai_planning_subtree/random_speech/mouse, + // Otherwise, look for and execute hunts for cabling + /datum/ai_planning_subtree/find_and_hunt_target/look_for_cables, + ) + +/// AI controller for rats, slightly more complex than mice becuase they attack people +/datum/ai_controller/basic_controller/mouse/rat + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/rat(), + BB_BASIC_MOB_CURRENT_TARGET = null, // heathen + BB_CURRENT_HUNTING_TARGET = null, // cheese + BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable + ) + + 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/simple_find_target, + /datum/ai_planning_subtree/basic_melee_attack_subtree/rat, + /datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese, + /datum/ai_planning_subtree/random_speech/mouse, + /datum/ai_planning_subtree/find_and_hunt_target/look_for_cables, + ) + +/datum/ai_planning_subtree/basic_melee_attack_subtree/rat + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/rat + +/datum/ai_behavior/basic_melee_attack/rat + action_cooldown = 2 SECONDS + +/datum/targetting_datum/basic/rat + check_factions_exactly = TRUE diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 1b9e57f6b99..cfb5c183cb4 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1319,7 +1319,7 @@ /mob/living/simple_animal/crab, /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/pet/cat, - /mob/living/simple_animal/mouse, + /mob/living/basic/mouse, /mob/living/simple_animal/chicken, /mob/living/basic/cow, /mob/living/simple_animal/hostile/lizard, diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index a3fb077fdf7..aea80f014af 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -387,11 +387,12 @@ adjustFireLoss(shock_damage) else adjustStaminaLoss(shock_damage) - visible_message( - span_danger("[src] was shocked by \the [source]!"), \ - span_userdanger("You feel a powerful shock coursing through your body!"), \ - span_hear("You hear a heavy electrical crack.") \ - ) + if(!(flags & SHOCK_SUPPRESS_MESSAGE)) + visible_message( + span_danger("[src] was shocked by \the [source]!"), \ + span_userdanger("You feel a powerful shock coursing through your body!"), \ + span_hear("You hear a heavy electrical crack.") \ + ) return shock_damage /mob/living/emp_act(severity) diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm index 1fe06691039..c32189eb817 100644 --- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm +++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm @@ -339,7 +339,7 @@ if(janitor_mode_flags & CLEANBOT_CLEAN_PESTS) target_types += list( /mob/living/basic/cockroach, - /mob/living/simple_animal/mouse, + /mob/living/basic/mouse, ) if(janitor_mode_flags & CLEANBOT_CLEAN_DRAWINGS) diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm index 6f156877e2d..7f3545f9ae7 100644 --- a/code/modules/mob/living/simple_animal/friendly/cat.dm +++ b/code/modules/mob/living/simple_animal/friendly/cat.dm @@ -29,7 +29,7 @@ response_harm_continuous = "kicks" response_harm_simple = "kick" mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT - var/mob/living/simple_animal/mouse/movement_target + var/mob/living/basic/mouse/movement_target gold_core_spawnable = FRIENDLY_SPAWN collar_type = "cat" can_be_held = TRUE @@ -231,16 +231,16 @@ if(!M.stat && Adjacent(M)) if (FACTION_RAT in M.faction) //Jerry can never catch Tom snowflaking - if(istype(M, /mob/living/simple_animal/mouse/brown/tom) && inept_hunter) + if(istype(M, /mob/living/basic/mouse/brown/tom) && inept_hunter) if(COOLDOWN_FINISHED(src, emote_cooldown)) visible_message(span_warning("[src] chases [M] around, to no avail!")) step(M, pick(GLOB.cardinals)) COOLDOWN_START(src, emote_cooldown, 1 MINUTES) break //Mouse splatting - if(istype(M, /mob/living/simple_animal/mouse)) + if(ismouse(M)) manual_emote("splats \the [M]!") - var/mob/living/simple_animal/mouse/snack = M + var/mob/living/basic/mouse/snack = M snack.splat() movement_target = null stop_automated_movement = 0 diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm deleted file mode 100644 index d154e7746a2..00000000000 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ /dev/null @@ -1,259 +0,0 @@ -/mob/living/simple_animal/mouse - name = "mouse" - desc = "This cute little guy just loves the taste of uninsulated electrical cables. Isn't he adorable?" - icon_state = "mouse_gray" - icon_living = "mouse_gray" - icon_dead = "mouse_gray_dead" - speak = list("Squeak!","SQUEAK!","Squeak?") - speak_emote = list("squeaks") - emote_hear = list("squeaks.") - emote_see = list("runs in a circle.", "shakes.") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - maxHealth = 5 - health = 5 - butcher_results = list(/obj/item/food/meat/slab/mouse = 1) - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently pushes aside" - response_disarm_simple = "gently push aside" - response_harm_continuous = "splats" - response_harm_simple = "splat" - density = FALSE - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - mob_size = MOB_SIZE_TINY - mob_biotypes = MOB_ORGANIC|MOB_BEAST - var/body_color //brown, gray and white, leave blank for random - gold_core_spawnable = FRIENDLY_SPAWN - var/chew_probability = 1 - can_be_held = TRUE - held_w_class = WEIGHT_CLASS_TINY - held_state = "mouse_gray" - faction = list(FACTION_RAT) - -/mob/living/simple_animal/mouse/Initialize(mapload) - . = ..() - if(body_color == null) - body_color = pick("brown","gray","white") - AddElement(/datum/element/animal_variety, "mouse", body_color, FALSE) - AddComponent(/datum/component/squeak, list('sound/effects/mousesqueek.ogg' = 1), 100, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) //as quiet as a mouse or whatever - add_cell_sample() - - ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = .proc/on_entered, - ) - AddElement(/datum/element/connect_loc, loc_connections) - -/mob/living/simple_animal/mouse/add_cell_sample() - AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOUSE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 10) - -/mob/living/simple_animal/mouse/proc/splat() - src.health = 0 - src.icon_dead = "mouse_[body_color]_splat" - death() - -/mob/living/simple_animal/mouse/death(gibbed, toast) - if(!ckey) - ..(1) - if(!gibbed) - var/obj/item/food/deadmouse/M = new(loc) - M.icon_state = icon_dead - M.name = name - if(toast) - M.add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY) - M.desc = "They're toast." - qdel(src) - else - SSmobs.cheeserats -= src // remove play controlled mouse also - ..(gibbed) - -/mob/living/simple_animal/mouse/revive(full_heal = FALSE, admin_revive = FALSE) - var/cap = CONFIG_GET(number/ratcap) - if(!admin_revive && !ckey && LAZYLEN(SSmobs.cheeserats) >= cap) - visible_message(span_warning("[src] twitched but does not continue moving due to the overwhelming rodent population on the station!")) - return FALSE - . = ..() - if(.) - SSmobs.cheeserats += src - -/mob/living/simple_animal/mouse/proc/on_entered(datum/source, AM as mob|obj) - SIGNAL_HANDLER - if(ishuman(AM)) - if(!stat) - var/mob/M = AM - to_chat(M, span_notice("[icon2html(src, M)] Squeak!")) - if(istype(AM, /obj/item/food/cheese/royal)) - evolve() - qdel(AM) - -/// How miceys go nom nom nom on things. force_chew is used to bypass the probability check (for unit tests). -/mob/living/simple_animal/mouse/handle_automated_action() - if(prob(chew_probability)) - var/turf/open/floor/position = get_turf(src) - if(istype(position) && position.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE) - var/obj/structure/cable/wire = locate() in position - if(wire && should_chew_cable()) - var/powered = wire.avail() - if(powered && !HAS_TRAIT(src, TRAIT_SHOCKIMMUNE)) - visible_message(span_warning("[src] chews through the [wire]. It's toast!")) - death(toast = TRUE) - wire.deconstruct() - playsound(src, 'sound/effects/sparks2.ogg', 100, TRUE) - return - else - visible_message(span_warning("[src] chews through the [wire].")) - wire.deconstruct() - - for(var/obj/item/food/cheese/cheese in range(1, src)) - if(prob(10)) - be_fruitful() - qdel(cheese) - return - for(var/obj/item/food/cheese/royal/bigcheese in range(1, src)) - qdel(bigcheese) - evolve() - return - -/// Proc to pass the probability of a mouse chewing on a wire to handle_automated_action. Overriden on applicable subtypes. -/mob/living/simple_animal/mouse/proc/should_chew_cable() - return prob(15) - -/mob/living/simple_animal/mouse/UnarmedAttack(atom/A, proximity_flag, list/modifiers) - if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) - return - . = ..() - if(istype(A, /obj/item/food/cheese) && canUseTopic(A, be_close = TRUE, no_dexterity = TRUE)) - if(health == maxHealth) - to_chat(src,span_warning("You don't need to eat or heal.")) - return - to_chat(src,span_green("You nibble some cheese, restoring your health.")) - adjustHealth(-(maxHealth-health)) - qdel(A) - return - return ..() - -/** - *Checks the mouse cap, if it's above the cap, doesn't spawn a mouse. If below, spawns a mouse and adds it to cheeserats. - */ -/mob/living/simple_animal/mouse/proc/be_fruitful() - var/cap = CONFIG_GET(number/ratcap) - if(LAZYLEN(SSmobs.cheeserats) >= cap) - visible_message(span_warning("[src] carefully eats the cheese, hiding it from the [cap] mice on the station!")) - return - var/mob/living/newmouse = new /mob/living/simple_animal/mouse(loc) - SSmobs.cheeserats += newmouse - visible_message(span_notice("[src] nibbles through the cheese, attracting another mouse!")) - -/** - *Spawns a new regal rat, says some good jazz, and if sentient, transfers the relivant mind. - */ -/mob/living/simple_animal/mouse/proc/evolve() - var/mob/living/simple_animal/hostile/regalrat/regalrat = new /mob/living/simple_animal/hostile/regalrat/controlled(loc) - visible_message(span_warning("[src] devours the cheese! They morph into something... greater!")) - INVOKE_ASYNC(regalrat, /atom/movable/proc/say, "RISE, MY SUBJECTS! SCREEEEEEE!") - if(mind) - mind.transfer_to(regalrat) - qdel(src) - -/* - * Mouse types - */ - -/mob/living/simple_animal/mouse/white - body_color = "white" - icon_state = "mouse_white" - held_state = "mouse_white" - -/mob/living/simple_animal/mouse/gray - body_color = "gray" - icon_state = "mouse_gray" - -/mob/living/simple_animal/mouse/brown - body_color = "brown" - icon_state = "mouse_brown" - held_state = "mouse_brown" - -/mob/living/simple_animal/mouse/Destroy() - SSmobs.cheeserats -= src - return ..() - -//TOM IS ALIVE! SQUEEEEEEEE~K :) -/mob/living/simple_animal/mouse/brown/tom - name = "Tom" - desc = "Jerry the cat is not amused." - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently pushes aside" - response_disarm_simple = "gently push aside" - response_harm_continuous = "splats" - response_harm_simple = "splat" - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/mouse/brown/tom/Initialize(mapload) - . = ..() - AddElement(/datum/element/pet_bonus, "squeaks happily!") - // Tom fears no cable. - ADD_TRAIT(src, TRAIT_SHOCKIMMUNE, INNATE_TRAIT) - -/// Subtype that only exists in tests, it fucking loves eating cables. -/mob/living/simple_animal/mouse/cable_lover - -/mob/living/simple_animal/mouse/cable_lover/should_chew_cable() - return TRUE // Always chew. - -/obj/item/food/deadmouse - name = "dead mouse" - desc = "They look like somebody dropped the bass on it. A lizard's favorite meal." - icon = 'icons/mob/simple/animal.dmi' - icon_state = "mouse_gray_dead" - bite_consumption = 3 - eatverbs = list("devour") - food_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 2) - foodtypes = GORE | MEAT | RAW - grind_results = list(/datum/reagent/blood = 20, /datum/reagent/liquidgibs = 5) - decomp_req_handle = TRUE - ant_attracting = FALSE - decomp_type = /obj/item/food/deadmouse/moldy - -/obj/item/food/deadmouse/Initialize(mapload) - . = ..() - AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOUSE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 10) - -/obj/item/food/deadmouse/examine(mob/user) - . = ..() - if (reagents?.has_reagent(/datum/reagent/yuck) || reagents?.has_reagent(/datum/reagent/fuel)) - . += span_warning("They're dripping with fuel and smells terrible.") - -/obj/item/food/deadmouse/attackby(obj/item/I, mob/living/user, params) - if(I.get_sharpness() && user.combat_mode) - if(isturf(loc)) - new /obj/item/food/meat/slab/mouse(loc) - to_chat(user, span_notice("You butcher [src].")) - qdel(src) - else - to_chat(user, span_warning("You need to put [src] on a surface to butcher them!")) - else - return ..() - -/obj/item/food/deadmouse/afterattack(obj/target, mob/living/user, proximity_flag) - if(proximity_flag && reagents && target.is_open_container()) - // is_open_container will not return truthy if target.reagents doesn't exist - var/datum/reagents/target_reagents = target.reagents - var/trans_amount = reagents.maximum_volume - reagents.total_volume * (4 / 3) - if(target_reagents.has_reagent(/datum/reagent/fuel) && target_reagents.trans_to(src, trans_amount)) - to_chat(user, span_notice("You dip [src] into [target].")) - else - to_chat(user, span_warning("That's a terrible idea.")) - else - return ..() - -/obj/item/food/deadmouse/moldy - name = "moldy dead mouse" - desc = "A dead rodent, consumed by mold and rot. There is a slim chance that a lizard might still eat it." - icon_state = "mouse_gray_dead" - food_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 2, /datum/reagent/consumable/mold = 10) - foodtypes = GORE | MEAT | RAW | GROSS - grind_results = list(/datum/reagent/blood = 20, /datum/reagent/liquidgibs = 5, /datum/reagent/consumable/mold = 10) - preserved_food = TRUE diff --git a/code/modules/mob/living/simple_animal/hostile/regalrat.dm b/code/modules/mob/living/simple_animal/hostile/regalrat.dm index 5c868a5f0ac..29ad1caf50d 100644 --- a/code/modules/mob/living/simple_animal/hostile/regalrat.dm +++ b/code/modules/mob/living/simple_animal/hostile/regalrat.dm @@ -94,27 +94,22 @@ return ..() /mob/living/simple_animal/hostile/regalrat/CanAttack(atom/the_target) - if(istype(the_target,/mob/living/simple_animal)) - var/mob/living/A = the_target - if(isregalrat(the_target) && A.stat == CONSCIOUS) - return TRUE - if(israt(the_target) && A.stat == CONSCIOUS) - var/mob/living/simple_animal/hostile/rat/R = the_target - if(R.faction_check_mob(src, TRUE)) - return FALSE - else - return TRUE - return ..() + if(isliving(the_target)) + var/mob/living/living_target = the_target + return !living_target.faction_check_mob(src, exact_match = TRUE) + + return ..() /mob/living/simple_animal/hostile/regalrat/examine(mob/user) . = ..() - if(istype(user,/mob/living/simple_animal/hostile/rat)) - var/mob/living/simple_animal/hostile/rat/ratself = user - if(ratself.faction_check_mob(src, TRUE)) - . += span_notice("This is your king. Long live their majesty!") + + if(ismouse(user)) + if(user.faction_check_mob(src, TRUE)) + . += span_notice("This is your king. Long live [p_their()] majesty!") else - . += span_warning("This is a false king! Strike them down!") - else if(user != src && istype(user,/mob/living/simple_animal/hostile/regalrat)) + . += span_warning("This is a false king! Strike [p_them()] down!") + + else if(user != src && isregalrat(user)) . += span_warning("Who is this foolish false king? This will not stand!") /mob/living/simple_animal/hostile/regalrat/handle_environment(datum/gas_mixture/environment) @@ -126,6 +121,7 @@ heal_bodypart_damage(1) #define REGALRAT_INTERACTION "regalrat" + /mob/living/simple_animal/hostile/regalrat/AttackingTarget() if (DOING_INTERACTION(src, REGALRAT_INTERACTION)) return @@ -149,7 +145,9 @@ if (DOING_INTERACTION(src, REGALRAT_INTERACTION)) // check again in case we started interacting return return ..() + #undef REGALRAT_INTERACTION + /** * Conditionally "eat" cheese object and heal, if injured. * @@ -238,9 +236,9 @@ StartCooldown() /** - *This action checks all nearby mice, and converts them into hostile rats. If no mice are nearby, creates a new one. + * This action checks all nearby mice, and converts them into hostile rats. + * If no mice are nearby, creates a new one. */ - /datum/action/cooldown/riot name = "Raise Army" desc = "Raise an army out of the hordes of mice and pests crawling around the maintenance shafts." @@ -251,148 +249,40 @@ cooldown_time = 8 SECONDS melee_cooldown_time = 0 SECONDS -/datum/action/cooldown/riot/proc/riot() - var/cap = CONFIG_GET(number/ratcap) - var/something_from_nothing = FALSE - for(var/mob/living/simple_animal/mouse/M in oview(owner, 5)) - var/mob/living/simple_animal/hostile/rat/new_rat = new(get_turf(M)) - something_from_nothing = TRUE - if(M.mind && M.stat == CONSCIOUS) - M.mind.transfer_to(new_rat) - if(istype(owner,/mob/living/simple_animal/hostile/regalrat)) - var/mob/living/simple_animal/hostile/regalrat/giantrat = owner - new_rat.faction = giantrat.faction - qdel(M) - if(!something_from_nothing) - if(LAZYLEN(SSmobs.cheeserats) >= 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/simple_animal/mouse(owner.loc) - owner.visible_message(span_warning("[owner] commands a mouse to their side!")) - else - owner.visible_message(span_warning("[owner] commands their army to action, mutating them into rats!")) - StartCooldown() - /datum/action/cooldown/riot/Activate(atom/target) StartCooldown(10 SECONDS) riot() StartCooldown() -/mob/living/simple_animal/hostile/rat - name = "rat" - desc = "They're a nasty, ugly, evil, disease-ridden rodent with anger issues." - icon_state = "mouse_gray" - icon_living = "mouse_gray" - icon_dead = "mouse_gray_dead" - speak = list("Skree!","SKREEE!","Squeak?") - speak_emote = list("squeaks") - emote_hear = list("Hisses.") - emote_see = list("runs in a circle.", "stands on their hind legs.") - melee_damage_lower = 3 - melee_damage_upper = 5 - obj_damage = 5 - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - maxHealth = 15 - health = 15 - butcher_results = list(/obj/item/food/meat/slab/mouse = 1) - density = FALSE - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - mob_size = MOB_SIZE_TINY - mob_biotypes = MOB_ORGANIC|MOB_BEAST - faction = list(FACTION_RAT) - -/mob/living/simple_animal/hostile/rat/Initialize(mapload) - . = ..() - SSmobs.cheeserats += src - - ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) - -/mob/living/simple_animal/hostile/rat/Destroy() - SSmobs.cheeserats -= src - return ..() - -/mob/living/simple_animal/hostile/rat/death(gibbed) - if(!ckey) - ..(TRUE) - if(!gibbed) - var/obj/item/food/deadmouse/mouse = new(loc) - mouse.icon_state = icon_dead - mouse.name = name - qdel(src) - else - SSmobs.cheeserats -= src // remove rats on death - return ..() - -/mob/living/simple_animal/hostile/rat/revive(full_heal = FALSE, admin_revive = FALSE) +/datum/action/cooldown/riot/proc/riot() var/cap = CONFIG_GET(number/ratcap) - if(!admin_revive && !ckey && LAZYLEN(SSmobs.cheeserats) >= cap) - visible_message(span_warning("[src] twitched but does not continue moving due to the overwhelming rodent population on the station!")) - return FALSE - . = ..() - if(.) - SSmobs.cheeserats += src + var/uplifted_mice = FALSE + for(var/mob/living/basic/mouse/nearby_mouse in oview(owner, 5)) + var/mob/living/basic/mouse/rat/rat_path = /mob/living/basic/mouse/rat -/mob/living/simple_animal/hostile/rat/examine(mob/user) - . = ..() - if(istype(user,/mob/living/simple_animal/hostile/rat)) - var/mob/living/simple_animal/hostile/rat/ratself = user - if(ratself.faction_check_mob(src, TRUE)) - . += span_notice("You both serve the same king.") - else - . += span_warning("This fool serves a different king!") - else if(istype(user,/mob/living/simple_animal/hostile/regalrat)) - var/mob/living/simple_animal/hostile/regalrat/ratking = user - if(ratking.faction_check_mob(src, TRUE)) - . += span_notice("This rat serves under you.") - else - . += span_warning("This peasant serves a different king! Strike them down!") + // 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) + // Give a hint in description too + nearby_mouse.desc += " ...Except this one looks corrupted and aggressive." + // Now we share factions! + nearby_mouse.faction = owner.faction.Copy() + uplifted_mice = TRUE -/mob/living/simple_animal/hostile/rat/CanAttack(atom/the_target) - if(istype(the_target,/mob/living/simple_animal)) - var/mob/living/A = the_target - if(isregalrat(the_target) && A.stat == CONSCIOUS) - var/mob/living/simple_animal/hostile/regalrat/ratking = the_target - if(ratking.faction_check_mob(src, TRUE)) - return FALSE - else - return TRUE - if(israt(the_target) && A.stat == CONSCIOUS) - var/mob/living/simple_animal/hostile/rat/R = the_target - if(R.faction_check_mob(src, TRUE)) - return FALSE - else - return TRUE - return ..() + if(uplifted_mice) + owner.visible_message(span_warning("[owner] commands their army to action, mutating them into rats!")) -/mob/living/simple_animal/hostile/rat/handle_automated_action() - . = ..() - if(prob(40)) - var/turf/open/floor/F = get_turf(src) - if(istype(F) && F.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE) - var/obj/structure/cable/C = locate() in F - if(C && prob(15)) - if(C.avail()) - visible_message(span_warning("[src] chews through the [C]. It's toast!")) - playsound(src, 'sound/effects/sparks2.ogg', 100, TRUE) - C.deconstruct() - death() - else if(C?.avail()) - visible_message(span_warning("[src] chews through the [C]. It looks unharmed!")) - playsound(src, 'sound/effects/sparks2.ogg', 100, TRUE) - C.deconstruct() + else if(LAZYLEN(SSmobs.cheeserats) < cap) + new /mob/living/basic/mouse(owner.loc, owner.faction) + owner.visible_message(span_warning("[owner] commands a rat to their side!")) -/mob/living/simple_animal/hostile/rat/AttackingTarget() - . = ..() - if(istype(target, /obj/item/food/cheese)) - if (health >= maxHealth) - to_chat(src, span_warning("You feel fine, no need to eat anything!")) - return - to_chat(src, span_green("You eat \the [src], restoring some health.")) - heal_bodypart_damage(maxHealth) - qdel(target) + else + to_chat(owner,span_warning("There's too many mice on this station to beckon a new one! Find them first!")) + StartCooldown() /** *Spittle; harmless reagent that is added by rat king, and makes you disgusted. diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 7db19962196..bf7ac2996e2 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -380,7 +380,7 @@ return TRUE if(ispath(MP, /mob/living/simple_animal/hostile/killertomato)) return TRUE - if(ispath(MP, /mob/living/simple_animal/mouse)) + if(ispath(MP, /mob/living/basic/mouse)) return TRUE if(ispath(MP, /mob/living/simple_animal/hostile/bear)) return TRUE diff --git a/code/modules/mob_spawn/ghost_roles/unused_roles.dm b/code/modules/mob_spawn/ghost_roles/unused_roles.dm index cfc49a57207..cc7543f5989 100644 --- a/code/modules/mob_spawn/ghost_roles/unused_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/unused_roles.dm @@ -204,7 +204,7 @@ /obj/effect/mob_spawn/ghost_role/mouse name = "sleeper" - mob_type = /mob/living/simple_animal/mouse + mob_type = /mob/living/basic/mouse prompt_name = "a mouse" you_are_text = "You're a mouse!" flavour_text = "Uh... yep! Squeak squeak, motherfucker." diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index 59ae92c63e5..f598e079584 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -621,7 +621,20 @@ visible_message(message) to_chat(user, message) - var/list/valid_animals = list(/mob/living/simple_animal/parrot/natural, /mob/living/simple_animal/butterfly, /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/crab, /mob/living/simple_animal/pet/fox, /mob/living/simple_animal/hostile/lizard, /mob/living/simple_animal/mouse, /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/bee, /mob/living/simple_animal/hostile/carp) + var/static/list/valid_animals = list( + /mob/living/simple_animal/parrot/natural, + /mob/living/simple_animal/butterfly, + /mob/living/simple_animal/pet/cat, + /mob/living/simple_animal/pet/dog/corgi, + /mob/living/simple_animal/crab, + /mob/living/simple_animal/pet/fox, + /mob/living/simple_animal/hostile/lizard, + /mob/living/basic/mouse, + /mob/living/simple_animal/pet/dog/pug, + /mob/living/simple_animal/hostile/bear, + /mob/living/simple_animal/hostile/bee, + /mob/living/simple_animal/hostile/carp, + ) for(var/counter in 1 to rand(1, 25)) var/mobType = pick(valid_animals) new mobType(get_turf(src)) 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 637c5d033f0..402c0a5ade4 100644 --- a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm +++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm @@ -26,7 +26,7 @@ virus_suspectibility = 2 growth_rate = VAT_GROWTH_RATE - resulting_atoms = list(/mob/living/simple_animal/mouse = 2) + resulting_atoms = list(/mob/living/basic/mouse = 2) /datum/micro_organism/cell_line/chicken //basic cell line designed as a good source of protein and eggyolk. desc = "Galliform skin cells." diff --git a/code/modules/spells/spell_types/shapeshift/shapechange.dm b/code/modules/spells/spell_types/shapeshift/shapechange.dm index de1b2cd8a8a..2b52826b367 100644 --- a/code/modules/spells/spell_types/shapeshift/shapechange.dm +++ b/code/modules/spells/spell_types/shapeshift/shapechange.dm @@ -11,7 +11,7 @@ spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC possible_shapes = list( - /mob/living/simple_animal/mouse, + /mob/living/basic/mouse, /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/hostile/carp/ranged/chaos, /mob/living/simple_animal/bot/secbot/ed209, diff --git a/code/modules/unit_tests/mouse_bite_cable.dm b/code/modules/unit_tests/mouse_bite_cable.dm index b6bc3df9304..e5b8cb1d2b7 100644 --- a/code/modules/unit_tests/mouse_bite_cable.dm +++ b/code/modules/unit_tests/mouse_bite_cable.dm @@ -2,19 +2,53 @@ /datum/unit_test/mouse_bite_cable /datum/unit_test/mouse_bite_cable/Run() - var/mob/living/simple_animal/mouse/biter = allocate(/mob/living/simple_animal/mouse/cable_lover) // use special subtype that will bypass the probability check to bite on a cable + // use dummy subtype that will bypass the probability check to bite on a cable + var/mob/living/basic/mouse/biter = allocate(/mob/living/basic/mouse/cable_lover) var/obj/structure/cable/wire = allocate(/obj/structure/cable) - biter.chew_probability = 100 // Make sure the mouse bites the cable! - wire.powernet = new /datum/powernet() // Make sure the cable has a powernet. - wire.powernet.avail = 100000 // Make sure the powernet has a good amount of power! handle_automated_action checks to see if there is ANY power in the powernet and passes fine, but better safe than sorry in this instance. + // Make sure the cable has a powernet. + wire.powernet = new() + // Make sure the powernet has a good amount of power! + // mice bites check if there is ANY power in the powernet and passes fine, but better safe than sorry + wire.powernet.avail = 100000 var/turf/open/floor/stage = get_turf(wire) - stage.underfloor_accessibility = UNDERFLOOR_INTERACTABLE // the unit tests room has normal flooring so let's just make it be interactable for the sake of this test - + // the unit tests room has normal flooring so let's just make it be interactable for the sake of this test + stage.underfloor_accessibility = UNDERFLOOR_INTERACTABLE + // relocate the rat biter.forceMove(stage) - biter.handle_automated_action() // it's not so automated since we're forcing it and doing everything we can to ensure that mice fucking bites that wire but potato potato - TEST_ASSERT(QDELETED(biter), "Mouse did not die after biting a powered cable.") // we qdel the mouse mob on death - TEST_ASSERT(QDELETED(wire), "Cable was not deleted after being bitten by a mouse.") + // Ai controlling processes expect a delta_time, supply a real-fake dt + var/fake_dt = SSai_controllers.wait * 0.1 + // Select behavior - this will queue finding the cable + biter.ai_controller.SelectBehaviors(fake_dt) + // Process behavior - this will execute the "locate the cable" behavior + biter.ai_controller.process(fake_dt) + // Check that the cable was found + TEST_ASSERT(biter.ai_controller.blackboard[BB_LOW_PRIORITY_HUNTING_TARGET] == WEAKREF(wire), "Mouse, after executing find, did not set the cable as a target.") + // Select behavior - this will queue hunting + biter.ai_controller.SelectBehaviors(fake_dt) + // Process behavior - this will execute the hunt for the cable and cause a bite (as we're in the min range) + biter.ai_controller.process(fake_dt) + // Check that the cable was removed, as it was hunted correctly + TEST_ASSERT_NULL(biter.ai_controller.blackboard[BB_LOW_PRIORITY_HUNTING_TARGET], "Mouse, after executing hunt, did not clear their target blackboard.") - stage.underfloor_accessibility = initial(stage.underfloor_accessibility) // reset the floor to its original state, to be nice to other tests in case that matters + // Now check that the bite went through - remember we qdel mice on death + TEST_ASSERT(QDELETED(biter), "Mouse, did not die after biting a powered cable.") + TEST_ASSERT(QDELETED(wire), "Cable, was not deleted after being bitten by a mouse.") + + // reset the floor to its original state, to be nice to other tests in case that matters + stage.underfloor_accessibility = initial(stage.underfloor_accessibility) + + +/// Dummy mouse that is guaranteed to die when biting shocked cables. +/mob/living/basic/mouse/cable_lover + cable_zap_prob = 100 + ai_controller = /datum/ai_controller/basic_controller/mouse/guaranteed_to_bite + +/// Dummy mouse's ai controller that is guaranteed to find and bite a cable beneath it +/datum/ai_controller/basic_controller/mouse/guaranteed_to_bite + planning_subtrees = list(/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables/guaranteed) + +/// Cable hunting subtree that's guarantee to hunt its target. +/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables/guaranteed + hunt_chance = 100 diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 069ab441073..d06ba6bf89d 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -267,7 +267,6 @@ /mob/living/simple_animal/hostile/pirate/melee/space, /mob/living/simple_animal/hostile/pirate/ranged, /mob/living/simple_animal/hostile/pirate/ranged/space, - /mob/living/simple_animal/hostile/rat, /mob/living/simple_animal/hostile/regalrat, /mob/living/simple_animal/hostile/regalrat/controlled, /mob/living/simple_animal/hostile/retaliate, @@ -344,12 +343,6 @@ /mob/living/simple_animal/hostile/viscerator, /mob/living/simple_animal/hostile/wizard, /mob/living/simple_animal/hostile/zombie, - /mob/living/simple_animal/mouse, - /mob/living/simple_animal/mouse/brown, - /mob/living/simple_animal/mouse/brown/tom, - /mob/living/simple_animal/mouse/cable_lover, - /mob/living/simple_animal/mouse/gray, - /mob/living/simple_animal/mouse/white, /mob/living/simple_animal/parrot, /mob/living/simple_animal/parrot/natural, /mob/living/simple_animal/parrot/poly, diff --git a/tgstation.dme b/tgstation.dme index 432998ae4aa..855fcc9cdad 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -715,6 +715,8 @@ #include "code\datums\ai\hauntium\haunted_controller.dm" #include "code\datums\ai\hauntium\hauntium_subtrees.dm" #include "code\datums\ai\hunting_behavior\hunting_behaviors.dm" +#include "code\datums\ai\hunting_behavior\hunting_cockroach.dm" +#include "code\datums\ai\hunting_behavior\hunting_mouse.dm" #include "code\datums\ai\idle_behaviors\_idle_behavior.dm" #include "code\datums\ai\idle_behaviors\idle_dog.dm" #include "code\datums\ai\idle_behaviors\idle_haunted.dm" @@ -3529,6 +3531,7 @@ #include "code\modules\mob\living\basic\ruin_defender\stickman.dm" #include "code\modules\mob\living\basic\vermin\cockroach.dm" #include "code\modules\mob\living\basic\vermin\mothroach.dm" +#include "code\modules\mob\living\basic\vermin\mouse.dm" #include "code\modules\mob\living\brain\brain.dm" #include "code\modules\mob\living\brain\brain_item.dm" #include "code\modules\mob\living\brain\brain_say.dm" @@ -3701,7 +3704,6 @@ #include "code\modules\mob\living\simple_animal\friendly\fox.dm" #include "code\modules\mob\living\simple_animal\friendly\gondola.dm" #include "code\modules\mob\living\simple_animal\friendly\lizard.dm" -#include "code\modules\mob\living\simple_animal\friendly\mouse.dm" #include "code\modules\mob\living\simple_animal\friendly\penguin.dm" #include "code\modules\mob\living\simple_animal\friendly\pet.dm" #include "code\modules\mob\living\simple_animal\friendly\rabbit.dm" diff --git a/tools/UpdatePaths/Scripts/70728_basic_mice.txt b/tools/UpdatePaths/Scripts/70728_basic_mice.txt new file mode 100644 index 00000000000..6efcca5cc68 --- /dev/null +++ b/tools/UpdatePaths/Scripts/70728_basic_mice.txt @@ -0,0 +1,2 @@ +/mob/living/simple_animal/hostile/rat : /mob/living/basic/mouse/rat +/mob/living/simple_animal/mouse/@SUBTYPES : /mob/living/basic/mouse/@SUBTYPES {@OLD}