diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 94653899928..5ee03370076 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -3676,7 +3676,7 @@ }, /area/maintenance/port/fore) "awu" = ( -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/wood, /area/maintenance/port/fore) "awv" = ( @@ -6646,7 +6646,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/plating, /area/maintenance/port/fore) "aXI" = ( @@ -7277,7 +7277,7 @@ }, /area/security/prison) "bdo" = ( -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/plating, /area/maintenance/port/fore) "bdN" = ( @@ -31021,7 +31021,7 @@ /turf/open/floor/wood, /area/security/detectives_office/private_investigators_office) "dJE" = ( -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/wood, /area/security/detectives_office/private_investigators_office) "dJF" = ( @@ -47470,7 +47470,7 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/iron, /area/maintenance/starboard/aft) "hEB" = ( @@ -47916,7 +47916,7 @@ /turf/open/floor/iron/white, /area/hallway/primary/central/aft) "hLr" = ( -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/plating, /area/maintenance/port) "hLN" = ( @@ -47990,7 +47990,7 @@ "hMB" = ( /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/wood, /area/service/theater/abandoned) "hMJ" = ( @@ -58698,7 +58698,7 @@ /turf/open/floor/iron, /area/cargo/storage) "kXP" = ( -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/iron/grimy, /area/commons/vacant_room/office) "kYd" = ( @@ -59359,7 +59359,7 @@ /obj/effect/decal/cleanable/dirt, /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/wood{ icon_state = "wood-broken5" }, @@ -65626,7 +65626,7 @@ /obj/effect/decal/cleanable/dirt, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/wood, /area/service/abandoned_gambling_den) "mZk" = ( @@ -81413,7 +81413,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/plating, /area/maintenance/port/aft) "rIc" = ( @@ -82297,7 +82297,7 @@ /obj/effect/turf_decal/tile/neutral{ dir = 8 }, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/iron/dark, /area/service/abandoned_gambling_den) "rVD" = ( @@ -85869,7 +85869,7 @@ /turf/open/floor/iron/dark, /area/engineering/transit_tube) "sXl" = ( -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/wood, /area/service/library/abandoned) "sXr" = ( @@ -95018,7 +95018,7 @@ "vKU" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/wood, /area/commons/vacant_room/office) "vKW" = ( @@ -97965,7 +97965,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/plating, /area/maintenance/starboard) "wGN" = ( @@ -100908,7 +100908,7 @@ /turf/open/floor/iron, /area/medical/medbay/central) "xCG" = ( -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/plating, /area/maintenance/starboard/fore) "xCH" = ( @@ -101734,7 +101734,7 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/iron/dark, /area/service/abandoned_gambling_den) "xRb" = ( diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index c8c8d006710..fc228957d13 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -52699,7 +52699,7 @@ /obj/structure/lattice/catwalk, /obj/effect/decal/cleanable/dirt, /obj/structure/disposalpipe/segment, -/mob/living/simple_animal/hostile/cockroach, +/mob/living/basic/cockroach, /turf/open/floor/plating, /area/maintenance/tram/mid) "sbo" = ( diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index a1a32b30d44..e90ace09004 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -7,7 +7,7 @@ ///Monkey checks #define SHOULD_RESIST(source) (source.on_fire || source.buckled || HAS_TRAIT(source, TRAIT_RESTRAINED) || (source.pulledby && source.pulledby.grab_state > GRAB_PASSIVE)) -#define IS_DEAD_OR_INCAP(source) (HAS_TRAIT(source, TRAIT_INCAPACITATED) || HAS_TRAIT(source, TRAIT_HANDS_BLOCKED) || IS_IN_STASIS(source) || source.stat) +#define IS_DEAD_OR_INCAP(source) (source.incapacitated() || source.stat) ///For JPS pathing, the maximum length of a path we'll try to generate. Should be modularized depending on what we're doing later on #define AI_MAX_PATH_LENGTH 30 // 30 is possibly overkill since by default we lose interest after 14 tiles of distance, but this gives wiggle room for weaving around obstacles @@ -24,11 +24,17 @@ ///Does this task let you perform the action while you move closer? (Things like moving and shooting) #define AI_BEHAVIOR_MOVE_AND_PERFORM (1<<1) +///AI flags +#define STOP_MOVING_WHEN_PULLED (1<<0) + ///Subtree defines ///This subtree should cancel any further planning, (Including from other subtrees) #define SUBTREE_RETURN_FINISH_PLANNING 1 +//Generic BB keys +#define BB_CURRENT_MIN_MOVE_DISTANCE "min_move_distance" + // Monkey AI controller blackboard keys #define BB_MONKEY_AGRESSIVE "BB_monkey_agressive" @@ -163,3 +169,18 @@ ///bane ai #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_HUNTING_COOLDOWN "BB_HUNTING_COOLDOWN" + +///Basic Mob Keys + +///Targetting subtrees +#define BB_BASIC_MOB_CURRENT_TARGET "BB_basic_current_target" +#define BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION "BB_basic_current_target_hiding_location" +#define BB_TARGETTING_DATUM "targetting_datum" diff --git a/code/__DEFINES/basic_mobs.dm b/code/__DEFINES/basic_mobs.dm new file mode 100644 index 00000000000..a12b4e4c2ed --- /dev/null +++ b/code/__DEFINES/basic_mobs.dm @@ -0,0 +1,5 @@ +#define BASIC_MOB_MAX_STAMINALOSS 200 + +///Basic mob flags +#define DEL_ON_DEATH (1<<0) + diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 9522134a3a6..61d8a12371e 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -120,6 +120,8 @@ #define EXAMINE_POSITION_BEFORE (1<<1) //End positions #define COMPONENT_EXNAME_CHANGED (1<<0) +//from base of atom/attack_basic_mob(): (/mob/user) +#define COMSIG_ATOM_ATTACK_BASIC_MOB "attack_basic_mob" ///from base of [/atom/proc/update_appearance]: (updates) #define COMSIG_ATOM_UPDATE_APPEARANCE "atom_update_appearance" @@ -347,6 +349,11 @@ /// Can't pick up #define COMPONENT_LIVING_CANT_PUT_IN_HAND (1<<0) +///Basic mob signals +///Called on /basic when updating its speed, from base of /mob/living/basic/update_basic_mob_varspeed(): () +#define POST_BASIC_MOB_UPDATE_VARSPEED "post_basic_mob_update_varspeed" + + /// from /datum/component/singularity/proc/can_move(), as well as /obj/energy_ball/proc/can_move() /// if a callback returns `SINGULARITY_TRY_MOVE_BLOCK`, then the singularity will not move to that turf #define COMSIG_ATOM_SINGULARITY_TRY_MOVE "atom_singularity_try_move" @@ -586,6 +593,8 @@ #define COMSIG_LIVING_SET_BODY_POSITION "living_set_body_position" ///From post-can inject check of syringe after attack (mob/user) #define COMSIG_LIVING_TRY_SYRINGE "living_try_syringe" +///From living/Life(). (deltatime, times_fired) +#define COMSIG_LIVING_LIFE "living_life" ///from base of element/bane/activate(): (item/weapon, mob/user) #define COMSIG_LIVING_BANED "living_baned" @@ -630,6 +639,8 @@ #define COMPONENT_CANT_TRACK (1<<0) ///from end of fully_heal(): (admin_revive) #define COMSIG_LIVING_POST_FULLY_HEAL "living_post_fully_heal" +/// from start of /mob/living/handle_breathing(): (delta_time, times_fired) +#define COMSIG_LIVING_HANDLE_BREATHING "living_handle_breathing" ///Called on user, from base of /datum/strippable_item/try_(un)equip() (atom/target, obj/item/equipping?) ///also from /mob/living/stripPanel(Un)equip)() @@ -1307,6 +1318,7 @@ #define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" + // Aquarium related signals #define COMSIG_AQUARIUM_BEFORE_INSERT_CHECK "aquarium_about_to_be_inserted" #define COMSIG_AQUARIUM_SURFACE_CHANGED "aquarium_surface_changed" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 2d5518ad19b..c0328b993b9 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -104,6 +104,9 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list( #define ispAI(A) (istype(A, /mob/living/silicon/pai)) +// basic mobs +#define isbasicmob(A) (istype(A, /mob/living/basic)) + //Simple animals #define isanimal(A) (istype(A, /mob/living/simple_animal)) diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 846a431636f..fe7711208b7 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -545,14 +545,20 @@ GLOBAL_LIST_EMPTY(species_list) mob_spawn_meancritters += T if(FRIENDLY_SPAWN) mob_spawn_nicecritters += T + for(var/mob/living/basic/basic_mob as anything in typesof(/mob/living/basic)) + switch(initial(basic_mob.gold_core_spawnable)) + if(HOSTILE_SPAWN) + mob_spawn_meancritters += basic_mob + if(FRIENDLY_SPAWN) + mob_spawn_nicecritters += basic_mob var/chosen if(mob_class == FRIENDLY_SPAWN) chosen = pick(mob_spawn_nicecritters) else chosen = pick(mob_spawn_meancritters) - var/mob/living/simple_animal/C = new chosen(spawn_location) - return C + var/mob/living/spawned_mob = new chosen(spawn_location) + return spawned_mob /proc/passtable_on(target, source) var/mob/living/L = target diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 124ee777360..079423925ed 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -258,6 +258,12 @@ else return ..() +/mob/living/basic/attacked_by(obj/item/I, mob/living/user) + if(!attack_threshold_check(I.force, I.damtype, MELEE, FALSE)) + playsound(loc, 'sound/weapons/tap.ogg', I.get_clamped_volume(), TRUE, -1) + else + return ..() + /** * Last proc in the [/obj/item/proc/melee_attack_chain] * diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 7571e6f3876..427f580ba1b 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -132,6 +132,9 @@ /atom/proc/attack_animal(mob/user, list/modifiers) SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_ANIMAL, user) +/atom/proc/attack_basic_mob(mob/user, list/modifiers) + SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_BASIC_MOB, user) + ///Attacked by monkey /atom/proc/attack_paw(mob/user, list/modifiers) if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_PAW, user) & COMPONENT_CANCEL_ATTACK_CHAIN) diff --git a/code/controllers/subsystem/minor_mapping.dm b/code/controllers/subsystem/minor_mapping.dm index 355d9916462..7ce2a6fd2ec 100644 --- a/code/controllers/subsystem/minor_mapping.dm +++ b/code/controllers/subsystem/minor_mapping.dm @@ -14,10 +14,15 @@ SUBSYSTEM_DEF(minor_mapping) var/list/exposed_wires = find_exposed_wires() var/mob/living/simple_animal/mouse/mouse - var/turf/proposed_turf + var/turf/open/proposed_turf + while((num_mice > 0) && exposed_wires.len) proposed_turf = pick_n_take(exposed_wires) + + if(!istype(proposed_turf)) + continue + if(prob(PROB_MOUSE_SPAWN)) if(!mouse) mouse = new(proposed_turf) @@ -25,7 +30,7 @@ SUBSYSTEM_DEF(minor_mapping) mouse.forceMove(proposed_turf) else mouse = new /mob/living/simple_animal/hostile/regalrat/controlled(proposed_turf) - if(mouse.environment_air_is_safe()) + if(proposed_turf.air.has_gas(/datum/gas/oxygen, 5)) num_mice -= 1 mouse = null diff --git a/code/controllers/subsystem/processing/ai_basic_avoidance.dm b/code/controllers/subsystem/processing/ai_basic_avoidance.dm new file mode 100644 index 00000000000..2a3fe992f44 --- /dev/null +++ b/code/controllers/subsystem/processing/ai_basic_avoidance.dm @@ -0,0 +1,4 @@ +PROCESSING_SUBSYSTEM_DEF(basic_avoidance) + name = "Basic Avoidance" + flags = SS_NO_INIT + wait = 2 SECONDS diff --git a/code/controllers/subsystem/traumas.dm b/code/controllers/subsystem/traumas.dm index 4823695c9fe..088c90db696 100644 --- a/code/controllers/subsystem/traumas.dm +++ b/code/controllers/subsystem/traumas.dm @@ -60,7 +60,7 @@ SUBSYSTEM_DEF(traumas) /mob/living/simple_animal/pet/penguin, )), "anime" = typecacheof(list(/mob/living/simple_animal/hostile/guardian)), - "insects" = typecacheof(list(/mob/living/simple_animal/hostile/cockroach, /mob/living/simple_animal/hostile/bee)) + "insects" = typecacheof(list(/mob/living/basic/cockroach, /mob/living/simple_animal/hostile/bee)) ) phobia_objs = list("snakes" = typecacheof(list(/obj/item/rod_of_asclepius, /obj/item/toy/plush/snakeplushie)), diff --git a/code/datums/ai/_ai_behavior.dm b/code/datums/ai/_ai_behavior.dm index eb91a647744..ebec87b18c4 100644 --- a/code/datums/ai/_ai_behavior.dm +++ b/code/datums/ai/_ai_behavior.dm @@ -17,8 +17,8 @@ controller.behavior_cooldowns[src] = world.time + action_cooldown return -///Called when the action is finished. -/datum/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded) +///Called when the action is finished. This needs the same args as perform besides the default ones +/datum/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded, ...) LAZYREMOVE(controller.current_behaviors, src) controller.behavior_args -= type if(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //If this was a movement task, reset our movement target. diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 403ef59d204..a53e0828cfe 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -141,18 +141,22 @@ multiple modular subtrees with behaviors for(var/i in current_behaviors) var/datum/ai_behavior/current_behavior = i - if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown - continue // Convert the current behaviour action cooldown to realtime seconds from deciseconds.current_behavior // Then pick the max of this and the delta_time passed to ai_controller.process() // Action cooldowns cannot happen faster than delta_time, so delta_time should be the value used in this scenario. var/action_delta_time = max(current_behavior.action_cooldown * 0.1, delta_time) - if(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT && current_movement_target) //Might need to move closer + if(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //Might need to move closer + if(!current_movement_target) + stack_trace("[pawn] wants to perform action type [current_behavior.type] which requires movement, but has no current movement target!") + return //This can cause issues, so don't let these slide. if(current_behavior.required_distance >= get_dist(pawn, current_movement_target)) ///Are we close enough to engage? - if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop.else + if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop. ai_movement.stop_moving_towards(src) + + if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown + continue ProcessBehavior(action_delta_time, current_behavior) return @@ -160,9 +164,13 @@ multiple modular subtrees with behaviors ai_movement.start_moving_towards(src, current_movement_target, current_behavior.required_distance) //Then start moving if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //If we can move and perform then do so. + if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown + continue ProcessBehavior(action_delta_time, current_behavior) return else //No movement required + if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown + continue ProcessBehavior(action_delta_time, current_behavior) return @@ -229,7 +237,11 @@ multiple modular subtrees with behaviors return for(var/i in current_behaviors) var/datum/ai_behavior/current_behavior = i - current_behavior.finish_action(src, FALSE) + var/list/arguments = list(src, FALSE) + var/list/stored_arguments = behavior_args[current_behavior.type] + if(stored_arguments) + arguments += stored_arguments + current_behavior.finish_action(arglist(arguments)) /datum/ai_controller/proc/on_sentience_gained() SIGNAL_HANDLER diff --git a/code/datums/ai/_ai_planning_subtree.dm b/code/datums/ai/_ai_planning_subtree.dm index 8f186d586a4..ec69cd3e3e6 100644 --- a/code/datums/ai/_ai_planning_subtree.dm +++ b/code/datums/ai/_ai_planning_subtree.dm @@ -1,6 +1,7 @@ ///A subtree is attached to a controller and is occasionally called by /ai_controller/SelectBehaviors(), this mainly exists to act as a way to subtype and modify SelectBehaviors() without needing to subtype the ai controller itself /datum/ai_planning_subtree + ///Determines what behaviors should the controller try processing; if this returns SUBTREE_RETURN_FINISH_PLANNING then the controller won't go through the other subtrees should multiple exist in controller.planning_subtrees /datum/ai_planning_subtree/proc/SelectBehaviors(datum/ai_controller/controller, delta_time) return diff --git a/code/datums/ai/_item_behaviors.dm b/code/datums/ai/_item_behaviors.dm index 763d1a8cac6..de255cd448a 100644 --- a/code/datums/ai/_item_behaviors.dm +++ b/code/datums/ai/_item_behaviors.dm @@ -45,7 +45,7 @@ reset_blackboard(controller, succeeded, target_key, throw_count_key) /datum/ai_behavior/item_move_close_and_attack/proc/reset_blackboard(datum/ai_controller/controller, succeeded, target_key, throw_count_key) - controller.blackboard[target_key] = null + controller.blackboard -= target_key controller.blackboard[throw_count_key] = 0 /datum/ai_behavior/item_move_close_and_attack/haunted diff --git a/code/datums/ai/basic_mobs/base_basic_controller.dm b/code/datums/ai/basic_mobs/base_basic_controller.dm new file mode 100644 index 00000000000..cc74b412105 --- /dev/null +++ b/code/datums/ai/basic_mobs/base_basic_controller.dm @@ -0,0 +1,26 @@ +/datum/ai_controller/basic_controller + movement_delay = 0.4 SECONDS + +/datum/ai_controller/basic_controller/TryPossessPawn(atom/new_pawn) + if(!isbasicmob(new_pawn)) + return AI_CONTROLLER_INCOMPATIBLE + var/mob/living/basic/basic_mob = new_pawn + + update_speed(basic_mob) + + RegisterSignal(basic_mob, POST_BASIC_MOB_UPDATE_VARSPEED, .proc/update_speed) + + return ..() //Run parent at end + + +/datum/ai_controller/basic_controller/able_to_run() + . = ..() + if(isliving(pawn)) + var/mob/living/living_pawn = pawn + if(IS_DEAD_OR_INCAP(living_pawn)) + return FALSE + return TRUE + +/datum/ai_controller/basic_controller/proc/update_speed(mob/living/basic/basic_mob) + SIGNAL_HANDLER + movement_delay = basic_mob.cached_multiplicative_slowdown diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm new file mode 100644 index 00000000000..624cf0d60d6 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm @@ -0,0 +1,67 @@ +/datum/ai_behavior/basic_melee_attack + action_cooldown = 0.6 SECONDS + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + +/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) + . = ..() + controller.current_movement_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] //Hiding location is priority + +/datum/ai_behavior/basic_melee_attack/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) + . = ..() + var/mob/living/basic/basic_mob = controller.pawn + var/atom/target = controller.blackboard[target_key] + var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] + + if(!targetting_datum.can_attack(basic_mob, target)) + finish_action(controller, FALSE, target_key) + return + + var/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! + + controller.blackboard[hiding_location_key] = hiding_target + + if(hiding_target) //Slap it! + basic_mob.melee_attack(hiding_target) + else + basic_mob.melee_attack(target) + + +/datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targetting_datum_key, hiding_location_key) + . = ..() + if(!succeeded) + controller.blackboard -= target_key + +/datum/ai_behavior/basic_ranged_attack + action_cooldown = 0.6 SECONDS + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM + required_distance = 3 + +/datum/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) + . = ..() + controller.current_movement_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] //Hiding location is priority + + +/datum/ai_behavior/basic_ranged_attack/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) + . = ..() + var/mob/living/basic/basic_mob = controller.pawn + var/atom/target = controller.blackboard[target_key] + var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] + + + if(!targetting_datum.can_attack(basic_mob, target)) + finish_action(controller, FALSE, target_key) + return + + var/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! + + controller.blackboard[hiding_location_key] = hiding_target + + if(hiding_target) //Shoot it! + basic_mob.RangedAttack(hiding_target) + else + basic_mob.RangedAttack(target) + +/datum/ai_behavior/basic_ranged_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targetting_datum_key, hiding_location_key) + . = ..() + if(!succeeded) + controller.blackboard -= target_key diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm new file mode 100644 index 00000000000..c683032db0b --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm @@ -0,0 +1,45 @@ +/datum/ai_behavior/find_potential_targets + action_cooldown = 2 SECONDS + var/vision_range = 9 + ///List of potentially dangerous objs + var/static/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) + . = ..() + var/list/potential_targets + var/mob/living/living_mob = controller.pawn + var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] + + if(!targetting_datum) + CRASH("No target datum was supplied in the blackboard for [controller.pawn]") + + potential_targets = hearers(vision_range, controller.pawn) - living_mob //Remove self, so we don't suicide + + for(var/HM in typecache_filter_list(range(vision_range, living_mob), hostile_machines)) //Can we see any hostile machines? + if(can_see(living_mob, HM, vision_range)) + potential_targets += HM + + if(!potential_targets.len) + finish_action(controller, FALSE) + return + + var/list/filtered_targets = list() + + for(var/atom/pot_target in potential_targets) + if(targetting_datum.can_attack(living_mob, pot_target))//Can we attack it? + filtered_targets += pot_target + continue + + if(!filtered_targets.len) + finish_action(controller, FALSE) + return + + var/atom/target = pick(filtered_targets) + controller.blackboard[target_key] = target + + var/atom/potential_hiding_location = targetting_datum.find_hidden_mobs(living_mob, target) + + if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially. + controller.blackboard[hiding_location_key] = potential_hiding_location + + finish_action(controller, TRUE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm new file mode 100644 index 00000000000..633fe679992 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm @@ -0,0 +1,25 @@ +/datum/ai_planning_subtree/basic_melee_attack_subtree + var/datum/ai_behavior/basic_melee_attack/melee_attack_behavior = /datum/ai_behavior/basic_melee_attack + +/datum/ai_planning_subtree/basic_melee_attack_subtree/SelectBehaviors(datum/ai_controller/controller, delta_time) + . = ..() + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(!target || QDELETED(target)) + return + controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. + +//If you give this to something without the element you are a dumbass. +/datum/ai_planning_subtree/basic_ranged_attack_subtree + var/datum/ai_behavior/basic_ranged_attack/ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack + +/datum/ai_planning_subtree/basic_ranged_attack_subtree/SelectBehaviors(datum/ai_controller/controller, delta_time) + . = ..() + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(!target || QDELETED(target)) + return + controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. + + + 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 new file mode 100644 index 00000000000..e6edaaf05fa --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm @@ -0,0 +1,8 @@ +/datum/ai_planning_subtree/simple_find_target + +/datum/ai_planning_subtree/simple_find_target/SelectBehaviors(datum/ai_controller/controller, delta_time) + . = ..() + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(target && !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 new file mode 100644 index 00000000000..ad4555149cd --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm @@ -0,0 +1,39 @@ +/datum/ai_planning_subtree/random_speech + //The chance of an emote occuring each second + var/speech_chance = 0 + ///Hearable emotes + var/list/emote_hear = list() + ///Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps + var/list/emote_see = list() + ///Possible lines of speech the AI can have + var/list/speak = list() + +/datum/ai_planning_subtree/random_speech/New() + . = ..() + if(speak) + speak = string_list(speak) + if(emote_hear) + emote_hear = string_list(emote_hear) + if(emote_see) + emote_see = string_list(emote_see) + +/datum/ai_planning_subtree/random_speech/SelectBehaviors(datum/ai_controller/controller, delta_time) + if(DT_PROB(speech_chance, delta_time)) + var/audible_emotes_length = emote_hear?.len + var/non_audible_emotes_length = emote_see?.len + var/speak_lines_length = speak?.len + + var/total_choices_length = audible_emotes_length + non_audible_emotes_length + speak_lines_length + + var/random_number_in_range = rand(1, total_choices_length) + + if(random_number_in_range <= audible_emotes_length) + controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_hear)) + else if(random_number_in_range <= (audible_emotes_length + non_audible_emotes_length)) + controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_see)) + else + controller.queue_behavior(/datum/ai_behavior/perform_speech, pick(speak)) + +/datum/ai_planning_subtree/random_speech/cockroach + speech_chance = 5 + emote_hear = list("chitters") 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 new file mode 100644 index 00000000000..a9690524964 --- /dev/null +++ b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm @@ -0,0 +1,55 @@ +///Datum for basic mobs to define what they can attack. +/datum/targetting_datum + +///Returns true or false depending on if the target can be attacked by the mob +/datum/targetting_datum/proc/can_attack(mob/living/living_mob, atom/target) + return + +///Returns something the target might be hiding inside of +/datum/targetting_datum/proc/find_hidden_mobs(mob/living/living_mob, atom/target) + var/atom/target_hiding_location + if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper)) + target_hiding_location = target.loc + return target_hiding_location + +/datum/targetting_datum/basic + +/datum/targetting_datum/basic/can_attack(mob/living/living_mob, atom/the_target) + if(isturf(the_target) || !the_target) // bail out on invalids + return FALSE + + if(ismob(the_target)) //Target is in godmode, ignore it. + var/mob/M = the_target + if(M.status_flags & GODMODE) + return FALSE + + if(living_mob.see_invisible < the_target.invisibility)//Target's invisible to us, forget it + return FALSE + + if(living_mob.z != the_target.z) + return FALSE + + if(isliving(the_target)) //Targetting vs living mobs + var/mob/living/L = the_target + var/faction_check = living_mob.faction_check_mob(L) + if(faction_check || L.stat) + return FALSE + return TRUE + + if(ismecha(the_target)) //Targetting vs mechas + var/obj/vehicle/sealed/mecha/M = the_target + for(var/occupant in M.occupants) + if(can_attack(living_mob, occupant)) //Can we attack any of the occupants? + return TRUE + + if(istype(the_target, /obj/machinery/porta_turret)) //Cringe turret! kill it! + var/obj/machinery/porta_turret/P = the_target + if(P.in_faction(living_mob)) //Don't attack if the turret is in the same faction + return FALSE + if(P.has_cover && !P.raised) //Don't attack invincible turrets + return FALSE + if(P.machine_stat & BROKEN) //Or turrets that are already broken + return FALSE + return TRUE + + return FALSE diff --git a/code/datums/ai/dog/dog_behaviors.dm b/code/datums/ai/dog/dog_behaviors.dm index 06c711bbf63..c4cd2176c43 100644 --- a/code/datums/ai/dog/dog_behaviors.dm +++ b/code/datums/ai/dog/dog_behaviors.dm @@ -98,7 +98,7 @@ controller.pawn.visible_message(span_notice("[controller.pawn] delivers [carried_item] to [return_target].")) carried_item.forceMove(get_turf(return_target)) - controller.blackboard[BB_SIMPLE_CARRY_ITEM] = null + controller.blackboard -= BB_SIMPLE_CARRY_ITEM return TRUE /// This behavior involves either eating a snack we can reach, or begging someone holding a snack diff --git a/code/datums/ai/generic_actions.dm b/code/datums/ai/generic_actions.dm index 6b9cc68fabd..25476f27b43 100644 --- a/code/datums/ai/generic_actions.dm +++ b/code/datums/ai/generic_actions.dm @@ -259,3 +259,25 @@ /datum/ai_behavior/follow/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.blackboard[BB_FOLLOW_TARGET] = null + + + +/datum/ai_behavior/perform_emote + +/datum/ai_behavior/perform_emote/perform(delta_time, datum/ai_controller/controller, emote) + var/mob/living/living_pawn = controller.pawn + if(!istype(living_pawn)) + return + living_pawn.manual_emote(emote) + finish_action(controller, TRUE) + +/datum/ai_behavior/perform_speech + +/datum/ai_behavior/perform_speech/perform(delta_time, datum/ai_controller/controller, speech) + var/mob/living/living_pawn = controller.pawn + if(!istype(living_pawn)) + return + living_pawn.say(speech, forced = "AI Controller") + finish_action(controller, TRUE) + + diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm new file mode 100644 index 00000000000..ed4208dfab6 --- /dev/null +++ b/code/datums/ai/hunting_behavior/hunting_behaviors.dm @@ -0,0 +1,73 @@ +/datum/ai_planning_subtree/find_and_hunt_target + var/list/hunt_targets = list(/obj/effect/decal/cleanable/ants) + +/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(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) + else + controller.queue_behavior(/datum/ai_behavior/hunt_target, BB_CURRENT_HUNTING_TARGET, BB_HUNTING_COOLDOWN) + return SUBTREE_RETURN_FINISH_PLANNING //If we're hunting we're too busy for anything else + + +///Literaly for hunting specific mobs. +/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) + . = ..() + + 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 + + finish_action(controller, FALSE) +/datum/ai_behavior/hunt_target + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT + +/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] + +/datum/ai_behavior/hunt_target/perform(delta_time, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) + . = ..() + + if(!controller.blackboard[hunting_target_key]) //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] + + 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 // 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 + else if(hunting_target_key) + controller.blackboard[hunting_target_key] = null diff --git a/code/datums/ai/movement/_ai_movement.dm b/code/datums/ai/movement/_ai_movement.dm index a3f78fcb3d8..540dccae849 100644 --- a/code/datums/ai/movement/_ai_movement.dm +++ b/code/datums/ai/movement/_ai_movement.dm @@ -7,8 +7,9 @@ ///How many times a given controller can fail on their route before they just give up var/max_pathing_attempts -/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target) +/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) controller.pathing_attempts = 0 + controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE] = min_distance if(!moving_controllers.len && requires_processing) START_PROCESSING(SSai_movement, src) moving_controllers[controller] = current_movement_target diff --git a/code/datums/ai/movement/ai_movement_basic_avoidance.dm b/code/datums/ai/movement/ai_movement_basic_avoidance.dm index 25b868d6f44..99b3c852592 100644 --- a/code/datums/ai/movement/ai_movement_basic_avoidance.dm +++ b/code/datums/ai/movement/ai_movement_basic_avoidance.dm @@ -1,12 +1,33 @@ ///Uses Byond's basic obstacle avoidance mvovement /datum/ai_movement/basic_avoidance - requires_processing = FALSE + requires_processing = TRUE + max_pathing_attempts = 10 -/datum/ai_movement/basic_avoidance/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) - . = ..() - walk_to(controller.pawn, current_movement_target, min_distance, controller.movement_delay) +///Put your movement behavior in here! +/datum/ai_movement/basic_avoidance/process(delta_time) + for(var/datum/ai_controller/controller as anything in moving_controllers) + if(!COOLDOWN_FINISHED(controller, movement_cooldown)) + continue + COOLDOWN_START(controller, movement_cooldown, controller.movement_delay) + var/atom/movable/movable_pawn = controller.pawn -/datum/ai_movement/basic_avoidance/stop_moving_towards(datum/ai_controller/controller) - . = ..() - walk_to(controller.pawn, src) + var/can_move = TRUE + + if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && movable_pawn.pulledby) + can_move = FALSE + + if(!isturf(movable_pawn.loc)) //No moving if not on a turf + can_move = FALSE + + var/current_loc = get_turf(movable_pawn) + + var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target) + + if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move) + step_to(movable_pawn, controller.current_movement_target, controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE], controller.movement_delay) + + if(current_loc == get_turf(movable_pawn)) //Did we even move after trying to move? + controller.pathing_attempts++ + if(controller.pathing_attempts >= max_pathing_attempts) + controller.CancelActions() diff --git a/code/datums/ai/movement/ai_movement_dumb.dm b/code/datums/ai/movement/ai_movement_dumb.dm index 0ce64669d37..b94d4422081 100644 --- a/code/datums/ai/movement/ai_movement_dumb.dm +++ b/code/datums/ai/movement/ai_movement_dumb.dm @@ -11,14 +11,19 @@ var/atom/movable/movable_pawn = controller.pawn + var/can_move = TRUE + + if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && movable_pawn.pulledby) + can_move = FALSE + if(!isturf(movable_pawn.loc)) //No moving if not on a turf - continue + can_move = FALSE var/current_loc = get_turf(movable_pawn) var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target) - if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs)) + if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs) && can_move) movable_pawn.Move(target_turf, get_dir(current_loc, target_turf)) if(current_loc == get_turf(movable_pawn)) //Did we even move after trying to move? diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index ea05b0fc899..fe13df126ee 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -15,6 +15,9 @@ if(!isturf(movable_pawn.loc)) //No moving if not on a turf continue + if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && movable_pawn.pulledby) + continue + var/minimum_distance = controller.max_target_distance // right now I'm just taking the shortest minimum distance of our current behaviors, at some point in the future // we should let whatever sets the current_movement_target also set the min distance and max path length diff --git a/code/datums/ai/hostile/hostile_controller.dm b/code/datums/ai/oldhostile/hostile_tameable.dm similarity index 99% rename from code/datums/ai/hostile/hostile_controller.dm rename to code/datums/ai/oldhostile/hostile_tameable.dm index d66b57e9f82..4ab634f6f5d 100644 --- a/code/datums/ai/hostile/hostile_controller.dm +++ b/code/datums/ai/oldhostile/hostile_tameable.dm @@ -1,3 +1,5 @@ +///This code needs to be removed at some point as it doesn't actually utilize the AI. + /datum/ai_controller/hostile_friend blackboard = list( BB_HOSTILE_ORDER_MODE = null, @@ -132,6 +134,7 @@ return set_command_mode(clicker, choice) + /datum/ai_controller/hostile_friend/proc/check_menu(mob/user) if(!istype(user)) CRASH("A non-mob is trying to issue an order to [pawn].") diff --git a/code/datums/elements/atmos_requirements.dm b/code/datums/elements/atmos_requirements.dm new file mode 100644 index 00000000000..ce07e848ccc --- /dev/null +++ b/code/datums/elements/atmos_requirements.dm @@ -0,0 +1,75 @@ + ///Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage + ///Leaving something at 0 means it's off - has no maximum. + + ///This damage is taken when atmos doesn't fit all the requirements above. + + +/** + * ## atmos requirements element! + * + * bespoke element that deals damage to the attached mob when the atmos requirements aren't satisfied + */ +/datum/element/atmos_requirements + element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH + id_arg_index = 2 + var/list/atmos_requirements + var/unsuitable_atmos_damage + +/datum/element/atmos_requirements/Attach(datum/target, list/atmos_requirements, unsuitable_atmos_damage) + . = ..() + if(!isliving(target)) + return ELEMENT_INCOMPATIBLE + src.atmos_requirements = string_assoc_list(atmos_requirements) + RegisterSignal(target, COMSIG_LIVING_HANDLE_BREATHING, .proc/on_non_stasis_life) + +/datum/element/atmos_requirements/Detach(datum/target) + . = ..() + UnregisterSignal(target, COMSIG_LIVING_HANDLE_BREATHING) + +///signal called by the living mob's life() while non stasis +/datum/element/atmos_requirements/proc/on_non_stasis_life(mob/living/target, delta_time = SSMOBS_DT) + SIGNAL_HANDLER + if(is_breathable_atmos(target)) + target.clear_alert("not_enough_oxy") + return + target.adjustBruteLoss(unsuitable_atmos_damage * delta_time) + target.throw_alert("not_enough_oxy", /atom/movable/screen/alert/not_enough_oxy) + +/datum/element/atmos_requirements/proc/is_breathable_atmos(mob/living/target) + if(target.pulledby && target.pulledby.grab_state >= GRAB_KILL && atmos_requirements["min_oxy"]) + return FALSE + + if(!isopenturf(target.loc)) + return TRUE + + var/turf/open/open_turf = target.loc + if(!open_turf.air && (atmos_requirements["min_oxy"] || atmos_requirements["min_tox"] || atmos_requirements["min_n2"] || atmos_requirements["min_co2"])) + return FALSE + + var/open_turf_gases = open_turf.air.gases + open_turf.air.assert_gases(arglist(GLOB.hardcoded_gases)) + + var/plas = open_turf_gases[/datum/gas/plasma][MOLES] + var/oxy = open_turf_gases[/datum/gas/oxygen][MOLES] + var/n2 = open_turf_gases[/datum/gas/nitrogen][MOLES] + var/co2 = open_turf_gases[/datum/gas/carbon_dioxide][MOLES] + + open_turf.air.garbage_collect() + + . = TRUE + if(atmos_requirements["min_oxy"] && oxy < atmos_requirements["min_oxy"]) + . = FALSE + else if(atmos_requirements["max_oxy"] && oxy > atmos_requirements["max_oxy"]) + . = FALSE + else if(atmos_requirements["min_plas"] && plas < atmos_requirements["min_plas"]) + . = FALSE + else if(atmos_requirements["max_plas"] && plas > atmos_requirements["max_plas"]) + . = FALSE + else if(atmos_requirements["min_n2"] && n2 < atmos_requirements["min_n2"]) + . = FALSE + else if(atmos_requirements["max_n2"] && n2 > atmos_requirements["max_n2"]) + . = FALSE + else if(atmos_requirements["min_co2"] && co2 < atmos_requirements["min_co2"]) + . = FALSE + else if(atmos_requirements["max_co2"] && co2 > atmos_requirements["max_co2"]) + . = FALSE diff --git a/code/datums/elements/basic_body_temp_sensitive.dm b/code/datums/elements/basic_body_temp_sensitive.dm new file mode 100644 index 00000000000..30dd0ed41a2 --- /dev/null +++ b/code/datums/elements/basic_body_temp_sensitive.dm @@ -0,0 +1,65 @@ +/** + * When attached to a basic mob, it gives it the ability to be hurt by cold body temperatures + */ +/datum/element/basic_body_temp_sensetive + element_flags = ELEMENT_BESPOKE + id_arg_index = 2 + + ///Min body temp + var/min_body_temp = 250 + ///Max body temp + var/max_body_temp = 350 + ////Damage when below min temp + var/cold_damage = 1 + ///Damage when above max temp + var/heat_damage = 1 + +/datum/element/basic_body_temp_sensetive/Attach(datum/target, min_body_temp, max_body_temp, cold_damage, heat_damage) + . = ..() + if(!isbasicmob(target)) + return ELEMENT_INCOMPATIBLE + + if(min_body_temp) + src.min_body_temp = min_body_temp + if(max_body_temp) + src.max_body_temp = max_body_temp + if(cold_damage) + src.cold_damage = cold_damage + if(heat_damage) + src.heat_damage = heat_damage + RegisterSignal(target, COMSIG_LIVING_LIFE, .proc/on_life) + +/datum/element/basic_body_temp_sensetive/Detach(datum/source) + if(source) + UnregisterSignal(source, COMSIG_LIVING_LIFE) + return ..() + + +/datum/element/basic_body_temp_sensetive/proc/on_life(datum/target, delta_time, times_fired) + var/mob/living/basic/basic_mob = target + var/gave_alert = FALSE + + if(basic_mob.bodytemperature < min_body_temp) + basic_mob.adjust_health(cold_damage * delta_time) + switch(cold_damage) + if(1 to 5) + basic_mob.throw_alert("temp", /atom/movable/screen/alert/cold, 1) + if(5 to 10) + basic_mob.throw_alert("temp", /atom/movable/screen/alert/cold, 2) + if(10 to INFINITY) + basic_mob.throw_alert("temp", /atom/movable/screen/alert/cold, 3) + gave_alert = TRUE + + else if(basic_mob.bodytemperature > max_body_temp) + basic_mob.adjust_health(heat_damage * delta_time) + switch(heat_damage) + if(1 to 5) + basic_mob.throw_alert("temp", /atom/movable/screen/alert/hot, 1) + if(5 to 10) + basic_mob.throw_alert("temp", /atom/movable/screen/alert/hot, 2) + if(10 to INFINITY) + basic_mob.throw_alert("temp", /atom/movable/screen/alert/hot, 3) + gave_alert = TRUE + + if(!gave_alert) + basic_mob.clear_alert("temp") diff --git a/code/datums/elements/death_drops.dm b/code/datums/elements/death_drops.dm new file mode 100644 index 00000000000..a8093493812 --- /dev/null +++ b/code/datums/elements/death_drops.dm @@ -0,0 +1,34 @@ +/** + * ## death drops element! + * + * bespoke element that spawns loot when a mob is killed + */ +/datum/element/death_drops + element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH + id_arg_index = 2 + ///what items the target drops when killed + var/list/loot + +/datum/element/death_drops/Attach(datum/target, list/loot) + . = ..() + if(!isliving(target)) + return ELEMENT_INCOMPATIBLE + if(!loot) + stack_trace("death drops element added to [target] with NO LOOT") + if(!src.loot) + src.loot = loot.Copy() + RegisterSignal(target, COMSIG_LIVING_DEATH, .proc/on_death) + +/datum/element/death_drops/Detach(datum/target) + . = ..() + UnregisterSignal(target, COMSIG_LIVING_DEATH) + +///signal called by the stat of the target changing +/datum/element/death_drops/proc/on_death(mob/living/target, gibbed) + SIGNAL_HANDLER + for(var/thing_to_spawn in loot) + if(loot[thing_to_spawn]) //If this is an assoc list, use the value of that to get the right amount + for(var/index in 1 to loot[thing_to_spawn]) + new thing_to_spawn(target.drop_location()) + else + new thing_to_spawn(target.drop_location()) diff --git a/code/datums/elements/ranged_attacks.dm b/code/datums/elements/ranged_attacks.dm new file mode 100644 index 00000000000..0b09496e24e --- /dev/null +++ b/code/datums/elements/ranged_attacks.dm @@ -0,0 +1,50 @@ +///This proc is used by basic mobs to give them a simple ranged attack! In theory this could be extended to +/datum/element/ranged_attacks + element_flags = ELEMENT_DETACH | ELEMENT_BESPOKE + id_arg_index = 2 + var/casingtype = /obj/item/ammo_casing/glockroach + var/projectilesound = 'sound/weapons/gun/pistol/shot.ogg' + var/projectiletype + +/datum/element/ranged_attacks/Attach(atom/movable/target, casingtype, projectilesound, projectiletype) + . = ..() + if(!isbasicmob(target)) + return COMPONENT_INCOMPATIBLE + + src.casingtype = casingtype + src.projectilesound = projectilesound + src.projectiletype = projectiletype + + RegisterSignal(target, COMSIG_MOB_ATTACK_RANGED, .proc/fire_ranged_attack) + + if(casingtype && projectiletype) + CRASH("Set both casing type and projectile type in [target]'s ranged attacks element! uhoh! stinky!") + +/datum/element/ranged_attacks/Detach(datum/target) + UnregisterSignal(target, COMSIG_MOB_ATTACK_RANGED) + return ..() + +/datum/element/ranged_attacks/proc/fire_ranged_attack(mob/living/basic/firer, atom/target, modifiers) + SIGNAL_HANDLER + INVOKE_ASYNC(src, .proc/async_fire_ranged_attack, firer, target, modifiers) + + +/datum/element/ranged_attacks/proc/async_fire_ranged_attack(mob/living/basic/firer, atom/target, modifiers) + var/turf/startloc = get_turf(firer) + + if(casingtype) + var/obj/item/ammo_casing/casing = new casingtype(startloc) + playsound(firer, projectilesound, 100, TRUE) + casing.fire_casing(target, firer, null, null, null, ran_zone(), 0, firer) + + else if(projectiletype) + var/obj/projectile/P = new projectiletype(startloc) + playsound(firer, projectilesound, 100, TRUE) + P.starting = startloc + P.firer = firer + P.fired_from = firer + P.yo = target.y - startloc.y + P.xo = target.x - startloc.x + P.original = target + P.preparePixelProjectile(target, firer) + P.fire() diff --git a/code/game/objects/effects/spawners/scatter.dm b/code/game/objects/effects/spawners/scatter.dm index 1318a0943b5..a89a476d8b6 100644 --- a/code/game/objects/effects/spawners/scatter.dm +++ b/code/game/objects/effects/spawners/scatter.dm @@ -37,7 +37,7 @@ name = "trash and grime scatterer" max_spawns = 5 loot_table = list(/obj/effect/spawner/lootdrop/garbage_spawner = 30, - /mob/living/simple_animal/hostile/cockroach = 25, + /mob/living/basic/cockroach = 25, /obj/effect/decal/cleanable/garbage = 20, /obj/effect/decal/cleanable/vomit/old = 15, /obj/effect/spawner/lootdrop/cigbutt = 10) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 80f24130a30..afedbb737d7 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -794,6 +794,13 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e return ..() return 0 +/obj/item/attack_basic_mob(mob/living/basic/user, list/modifiers) + if (obj_flags & CAN_BE_HIT) + return ..() + return 0 + +attack_basic_mob + /obj/item/burn() if(!QDELETED(src)) var/turf/T = get_turf(src) diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index c58ccbdb710..1befd79fb64 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -718,7 +718,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 strong_against = typecacheof(list( /mob/living/simple_animal/hostile/bee/, /mob/living/simple_animal/butterfly, - /mob/living/simple_animal/hostile/cockroach, + /mob/living/basic/cockroach, /obj/item/queen_bee, /obj/structure/spider/spiderling )) diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index f1479ddc04d..c767802f53a 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -143,6 +143,18 @@ if(attack_generic(user, 60, BRUTE, MELEE, 0)) playsound(src.loc, 'sound/weapons/slash.ogg', 100, TRUE) +/obj/attack_basic_mob(mob/living/basic/user, list/modifiers) + if(!user.melee_damage_upper && !user.obj_damage) //No damage + user.emote("custom", message = "[user.friendly_verb_continuous] [src].") + return FALSE + else + if(user.obj_damage) + . = attack_generic(user, user.obj_damage, user.melee_damage_type, MELEE, TRUE, user.armour_penetration) + else + . = attack_generic(user, rand(user.melee_damage_lower,user.melee_damage_upper), user.melee_damage_type, MELEE,TRUE, user.armour_penetration) + if(.) + playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) + /obj/attack_animal(mob/living/simple_animal/user, list/modifiers) if(!user.melee_damage_upper && !user.obj_damage) user.emote("custom", message = "[user.friendly_verb_continuous] [src].") diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index 8dd248eaaf5..936b5557102 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -55,7 +55,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/simple_animal/hostile/cockroach, /datum/micro_organism/cell_line/mouse) + possible_types = list(/mob/living/basic/cockroach, /datum/micro_organism/cell_line/mouse) /datum/experiment/scanning/random/cytology/medium name = "Advanced Cytology Scanning Experiment" diff --git a/code/modules/experisci/experiment/types/scanning_vatgrown.dm b/code/modules/experisci/experiment/types/scanning_vatgrown.dm index 63bdf171a8a..72f7214000f 100644 --- a/code/modules/experisci/experiment/types/scanning_vatgrown.dm +++ b/code/modules/experisci/experiment/types/scanning_vatgrown.dm @@ -3,7 +3,7 @@ description = "Base experiment for scanning atoms that were vatgrown" exp_tag = "Cytology Scan" total_requirement = 1 - possible_types = list(/mob/living/simple_animal/hostile/cockroach) + possible_types = list(/mob/living/basic/cockroach) traits = EXPERIMENT_TRAIT_DESTRUCTIVE /datum/experiment/scanning/random/cytology/final_contributing_index_checks(atom/target, typepath) diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm index e21110678a6..becd950c48c 100644 --- a/code/modules/mining/abandoned_crates.dm +++ b/code/modules/mining/abandoned_crates.dm @@ -201,7 +201,7 @@ new /obj/item/dnainjector/wackymut(src) if(91) for(var/i in 1 to 30) - new /mob/living/simple_animal/hostile/cockroach(src) + new /mob/living/basic/cockroach(src) if(92) new /obj/item/katana(src) if(93) diff --git a/code/modules/mob/living/basic/basic.dm b/code/modules/mob/living/basic/basic.dm new file mode 100644 index 00000000000..a955f90dde3 --- /dev/null +++ b/code/modules/mob/living/basic/basic.dm @@ -0,0 +1,144 @@ +///Simple animals 2.0, This time, let's really try to keep it simple. This basetype should purely be used as a base-level for implementing simplified behaviours for things such as damage and attacks. Everything else should be in components or AI behaviours. +/mob/living/basic + name = "basic mob" + icon = 'icons/mob/animal.dmi' + health = 20 + maxHealth = 20 + gender = PLURAL + living_flags = MOVES_ON_ITS_OWN + status_flags = CANPUSH + + var/basic_mob_flags = NONE + + ///Defines how fast the basic mob can move. This is a multiplier + var/speed = 1 + ///How much stamina the mob recovers per second + var/stamina_recovery = 5 + + ///how much damage this basic mob does to objects, if any. + var/obj_damage = 0 + ///How much armour they ignore, as a flat reduction from the targets armour value. + var/armour_penetration = 0 + ///Damage type of a simple mob's melee attack, should it do damage. + var/melee_damage_type = BRUTE + ///How much wounding power it has + var/wound_bonus = CANT_WOUND + ///How much bare wounding power it has + var/bare_wound_bonus = 0 + ///If the attacks from this are sharp + var/sharpness = NONE + + /// Sound played when the critter attacks. + var/attack_sound + /// Override for the visual attack effect shown on 'do_attack_animation()'. + var/attack_vis_effect + ///Played when someone punches the creature. + var/attacked_sound = "punch" //This should be an element + + ///What kind of objects this mob can smash. + var/environment_smash = ENVIRONMENT_SMASH_NONE + + /// 1 for full damage , 0 for none , -1 for 1:1 heal from that source. + var/list/damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) + + ///Verbs used for speaking e.g. "Says" or "Chitters". This can be elementized + var/list/speak_emote = list() + + ///When someone interacts with the simple animal. + ///Help-intent verb in present continuous tense. + var/response_help_continuous = "pokes" + ///Help-intent verb in present simple tense. + var/response_help_simple = "poke" + ///Disarm-intent verb in present continuous tense. + var/response_disarm_continuous = "shoves" + ///Disarm-intent verb in present simple tense. + var/response_disarm_simple = "shove" + ///Harm-intent verb in present continuous tense. + var/response_harm_continuous = "hits" + ///Harm-intent verb in present simple tense. + var/response_harm_simple = "hit" + + ///Basic mob's own attacks verbs, + ///Attacking verb in present continuous tense. + var/attack_verb_continuous = "attacks" + ///Attacking verb in present simple tense. + var/attack_verb_simple = "attack" + ///Attacking, but without damage, verb in present continuous tense. + var/friendly_verb_continuous = "nuzzles" + ///Attacking, but without damage, verb in present simple tense. + var/friendly_verb_simple = "nuzzle" + + ////////THIS SECTION COULD BE ITS OWN ELEMENT + ///Icon to use + var/icon_living = "" + ///Icon when the animal is dead. Don't use animated icons for this. + var/icon_dead = "" + ///We only try to show a gibbing animation if this exists. + var/icon_gib = null + ///Flip the sprite upside down on death. Mostly here for things lacking custom dead sprites. + var/flip_on_death = FALSE + + ///If the mob can be spawned with a gold slime core. HOSTILE_SPAWN are spawned with plasma, FRIENDLY_SPAWN are spawned with blood. + var/gold_core_spawnable = NO_SPAWN + ///Sentience type, for slime potions. SHOULD BE AN ELEMENT BUT I DONT CARE ABOUT IT FOR NOW + var/sentience_type = SENTIENCE_ORGANIC + + + +/mob/living/basic/Initialize(mapload) + . = ..() + + if(gender == PLURAL) + gender = pick(MALE,FEMALE) + + if(!real_name) + real_name = name + + if(!loc) + stack_trace("Basic mob being instantiated in nullspace") + + update_basic_mob_varspeed() + + if(speak_emote) + speak_emote = string_list(speak_emote) + +/mob/living/basic/Life(delta_time = SSMOBS_DT, times_fired) + . = ..() + ///Automatic stamina re-gain + if(staminaloss > 0) + adjustStaminaLoss(-stamina_recovery * delta_time, FALSE, TRUE) + +/mob/living/basic/say_mod(input, list/message_mods = list()) + if(length(speak_emote)) + verb_say = pick(speak_emote) + return ..() + +/mob/living/basic/death(gibbed) + . = ..() + if(basic_mob_flags & DEL_ON_DEATH) + qdel(src) + else + health = 0 + icon_state = icon_dead + if(flip_on_death) + transform = transform.Turn(180) + set_density(FALSE) + +/mob/living/basic/proc/melee_attack(atom/target) + src.face_atom(target) + if(SEND_SIGNAL(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, target) & COMPONENT_HOSTILE_NO_ATTACK) + return FALSE //but more importantly return before attack_animal called + var/result = target.attack_basic_mob(src) + SEND_SIGNAL(src, COMSIG_HOSTILE_POST_ATTACKINGTARGET, target, result) + return result + +/mob/living/basic/proc/set_varspeed(var_value) + speed = var_value + update_basic_mob_varspeed() + +/mob/living/basic/proc/update_basic_mob_varspeed() + if(speed == 0) + remove_movespeed_modifier(/datum/movespeed_modifier/simplemob_varspeed) + add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/simplemob_varspeed, multiplicative_slowdown = speed) + SEND_SIGNAL(src, POST_BASIC_MOB_UPDATE_VARSPEED) + diff --git a/code/modules/mob/living/basic/basic_defense.dm b/code/modules/mob/living/basic/basic_defense.dm new file mode 100644 index 00000000000..5c8e8ae4254 --- /dev/null +++ b/code/modules/mob/living/basic/basic_defense.dm @@ -0,0 +1,186 @@ +/mob/living/basic/attack_hand(mob/living/carbon/human/user, list/modifiers) + // so that martial arts don't double dip + if (..()) + return TRUE + + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + user.do_attack_animation(src, ATTACK_EFFECT_DISARM) + playsound(src, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) + var/shove_dir = get_dir(user, src) + if(!Move(get_step(src, shove_dir), shove_dir)) + log_combat(user, src, "shoved", "failing to move it") + user.visible_message(span_danger("[user.name] shoves [src]!"), + span_danger("You shove [src]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, list(src)) + to_chat(src, span_userdanger("You're shoved by [user.name]!")) + return TRUE + log_combat(user, src, "shoved", "pushing it") + user.visible_message(span_danger("[user.name] shoves [src], pushing [p_them()]!"), + span_danger("You shove [src], pushing [p_them()]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, list(src)) + to_chat(src, span_userdanger("You're pushed by [user.name]!")) + return TRUE + + if(!user.combat_mode) + if (stat == DEAD) + return + visible_message(span_notice("[user] [response_help_continuous] [src]."), \ + span_notice("[user] [response_help_continuous] you."), null, null, user) + to_chat(user, span_notice("You [response_help_simple] [src].")) + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) + else + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, span_warning("You don't want to hurt [src]!")) + return + user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) + visible_message(span_danger("[user] [response_harm_continuous] [src]!"),\ + span_userdanger("[user] [response_harm_continuous] you!"), null, COMBAT_MESSAGE_RANGE, user) + to_chat(user, span_danger("You [response_harm_simple] [src]!")) + playsound(loc, attacked_sound, 25, TRUE, -1) + + var/damage = rand(user.dna.species.punchdamagelow, user.dna.species.punchdamagehigh) + + attack_threshold_check(damage) + log_combat(user, src, "attacked") + updatehealth() + return TRUE + +/mob/living/basic/attack_hulk(mob/living/carbon/human/user) + . = ..() + if(!.) + return + playsound(loc, "punch", 25, TRUE, -1) + visible_message(span_danger("[user] punches [src]!"), \ + span_userdanger("You're punched by [user]!"), null, COMBAT_MESSAGE_RANGE, user) + to_chat(user, span_danger("You punch [src]!")) + adjustBruteLoss(15) + +/mob/living/basic/attack_paw(mob/living/carbon/human/user, list/modifiers) + if(..()) //successful monkey bite. + if(stat != DEAD) + var/damage = rand(1, 3) + attack_threshold_check(damage) + return 1 + if (!user.combat_mode) + if (health > 0) + visible_message(span_notice("[user.name] [response_help_continuous] [src]."), \ + span_notice("[user.name] [response_help_continuous] you."), null, COMBAT_MESSAGE_RANGE, user) + to_chat(user, span_notice("You [response_help_simple] [src].")) + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) + + +/mob/living/basic/attack_alien(mob/living/carbon/alien/humanoid/user, list/modifiers) + if(..()) //if harm or disarm intent. + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + playsound(loc, 'sound/weapons/pierce.ogg', 25, TRUE, -1) + visible_message(span_danger("[user] [response_disarm_continuous] [name]!"), \ + span_userdanger("[user] [response_disarm_continuous] you!"), null, COMBAT_MESSAGE_RANGE, user) + to_chat(user, span_danger("You [response_disarm_simple] [name]!")) + log_combat(user, src, "disarmed") + else + var/damage = rand(15, 30) + visible_message(span_danger("[user] slashes at [src]!"), \ + span_userdanger("You're slashed at by [user]!"), null, COMBAT_MESSAGE_RANGE, user) + to_chat(user, span_danger("You slash at [src]!")) + playsound(loc, 'sound/weapons/slice.ogg', 25, TRUE, -1) + attack_threshold_check(damage) + log_combat(user, src, "attacked") + return 1 + +/mob/living/basic/attack_larva(mob/living/carbon/alien/larva/L) + . = ..() + if(. && stat != DEAD) //successful larva bite + var/damage = rand(5, 10) + . = attack_threshold_check(damage) + if(.) + L.amount_grown = min(L.amount_grown + damage, L.max_grown) + +/mob/living/basic/attack_basic_mob(mob/living/basic/user, list/modifiers) + . = ..() + if(.) + var/damage = rand(user.melee_damage_lower, user.melee_damage_upper) + return attack_threshold_check(damage, user.melee_damage_type) + +/mob/living/basic/attack_animal(mob/living/simple_animal/user, list/modifiers) + . = ..() + if(.) + var/damage = rand(user.melee_damage_lower, user.melee_damage_upper) + return attack_threshold_check(damage, user.melee_damage_type) + +/mob/living/basic/attack_slime(mob/living/simple_animal/slime/M) + if(..()) //successful slime attack + var/damage = rand(15, 25) + if(M.is_adult) + damage = rand(20, 35) + return attack_threshold_check(damage) + +/mob/living/basic/attack_drone(mob/living/simple_animal/drone/M) + if(M.combat_mode) //No kicking dogs even as a rogue drone. Use a weapon. + return + return ..() + +/mob/living/basic/proc/attack_threshold_check(damage, damagetype = BRUTE, armorcheck = MELEE, actuallydamage = TRUE) + var/temp_damage = damage + if(!damage_coeff[damagetype]) + temp_damage = 0 + else + temp_damage *= damage_coeff[damagetype] + + if(actuallydamage) + apply_damage(damage, damagetype, null, getarmor(null, armorcheck)) + return TRUE + +/mob/living/basic/bullet_act(obj/projectile/Proj, def_zone, piercing_hit = FALSE) + apply_damage(Proj.damage, Proj.damage_type) + Proj.on_hit(src, 0, piercing_hit) + return BULLET_ACT_HIT + +/mob/living/basic/ex_act(severity, target, origin) + if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) + return FALSE + + . = ..() + if(QDELETED(src)) + return + var/bomb_armor = getarmor(null, BOMB) + switch (severity) + if (EXPLODE_DEVASTATE) + if(prob(bomb_armor)) + adjustBruteLoss(500) + else + gib() + return + if (EXPLODE_HEAVY) + var/bloss = 60 + if(prob(bomb_armor)) + bloss = bloss / 1.5 + adjustBruteLoss(bloss) + + if (EXPLODE_LIGHT) + var/bloss = 30 + if(prob(bomb_armor)) + bloss = bloss / 1.5 + adjustBruteLoss(bloss) + +/mob/living/basic/blob_act(obj/structure/blob/B) + adjustBruteLoss(20) + return + +/mob/living/basic/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect) + if(!no_effect && !visual_effect_icon && melee_damage_upper) + if(attack_vis_effect && !iswallturf(A)) // override the standard visual effect. + visual_effect_icon = attack_vis_effect + else if(melee_damage_upper < 10) + visual_effect_icon = ATTACK_EFFECT_PUNCH + else + visual_effect_icon = ATTACK_EFFECT_SMASH + ..() + + +/mob/living/basic/update_stat() + if(status_flags & GODMODE) + return + if(stat != DEAD) + if(health <= 0) + death() + else + set_stat(CONSCIOUS) + med_hud_set_status() diff --git a/code/modules/mob/living/basic/health_adjustment.dm b/code/modules/mob/living/basic/health_adjustment.dm new file mode 100644 index 00000000000..8354d1424ef --- /dev/null +++ b/code/modules/mob/living/basic/health_adjustment.dm @@ -0,0 +1,56 @@ +/** + * Adjusts the health of a simple mob by a set amount and wakes AI if its idle to react + * + * Arguments: + * * amount The amount that will be used to adjust the mob's health + * * updating_health If the mob's health should be immediately updated to the new value + * * forced If we should force update the adjustment of the mob's health no matter the restrictions, like GODMODE + */ +/mob/living/basic/proc/adjust_health(amount, updating_health = TRUE, forced = FALSE) + . = FALSE + if(forced || !(status_flags & GODMODE)) + bruteloss = round(clamp(bruteloss + amount, 0, maxHealth * 2), DAMAGE_PRECISION) + if(updating_health) + updatehealth() + . = amount + if(ckey || stat) + return + //if(AIStatus == AI_IDLE) + // toggle_ai(AI_ON) + +/mob/living/basic/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) + if(forced) + . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) + else if(damage_coeff[BRUTE]) + . = adjust_health(amount * damage_coeff[BRUTE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) + +/mob/living/basic/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE) + if(forced) + . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) + else if(damage_coeff[BURN]) + . = adjust_health(amount * damage_coeff[BURN] * CONFIG_GET(number/damage_multiplier), updating_health, forced) + +/mob/living/basic/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE) + if(forced) + . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) + else if(damage_coeff[OXY]) + . = adjust_health(amount * damage_coeff[OXY] * CONFIG_GET(number/damage_multiplier), updating_health, forced) + +/mob/living/basic/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE) + if(forced) + . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) + else if(damage_coeff[TOX]) + . = adjust_health(amount * damage_coeff[TOX] * CONFIG_GET(number/damage_multiplier), updating_health, forced) + +/mob/living/basic/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE) + if(forced) + . = adjust_health(amount * CONFIG_GET(number/damage_multiplier), updating_health, forced) + else if(damage_coeff[CLONE]) + . = adjust_health(amount * damage_coeff[CLONE] * CONFIG_GET(number/damage_multiplier), updating_health, forced) + +/mob/living/basic/adjustStaminaLoss(amount, updating_health = FALSE, forced = FALSE) + if(forced) + staminaloss = max(0, min(BASIC_MOB_MAX_STAMINALOSS, staminaloss + amount)) + else + staminaloss = max(0, min(BASIC_MOB_MAX_STAMINALOSS, staminaloss + (amount * damage_coeff[STAMINA]))) + update_stamina() diff --git a/code/modules/mob/living/basic/vermin/cockroach.dm b/code/modules/mob/living/basic/vermin/cockroach.dm new file mode 100644 index 00000000000..aa301b36f89 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/cockroach.dm @@ -0,0 +1,150 @@ +/mob/living/basic/cockroach + name = "cockroach" + desc = "This station is just crawling with bugs." + icon_state = "cockroach" + icon_dead = "cockroach" //Make this work + density = FALSE + mob_biotypes = MOB_ORGANIC|MOB_BUG + mob_size = MOB_SIZE_TINY + health = 1 + maxHealth = 1 + speed = 1.25 + gold_core_spawnable = FRIENDLY_SPAWN + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB + + verb_say = "chitters" + verb_ask = "chitters inquisitively" + verb_exclaim = "chitters loudly" + verb_yell = "chitters loudly" + response_disarm_continuous = "shoos" + response_disarm_simple = "shoo" + response_harm_continuous = "splats" + response_harm_simple = "splat" + speak_emote = list("chitters") + + basic_mob_flags = DEL_ON_DEATH + faction = list("hostile") + + ai_controller = /datum/ai_controller/basic_controller/cockroach + +/mob/living/basic/cockroach/Initialize() + . = ..() + AddElement(/datum/element/death_drops, list(/obj/effect/decal/cleanable/insectguts)) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_COCKROACH, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 7) + AddElement(/datum/element/basic_body_temp_sensetive, 270, INFINITY) + AddComponent(/datum/component/squashable, squash_chance = 50, squash_damage = 1) + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + +/mob/living/basic/cockroach/death(gibbed) + if(GLOB.station_was_nuked) //If the nuke is going off, then cockroaches are invincible. Keeps the nuke from killing them, cause cockroaches are immune to nukes. + return + ..() + +/mob/living/basic/cockroach/ex_act() //Explosions are a terrible way to handle a cockroach. + return FALSE + + +/datum/ai_controller/basic_controller/cockroach + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic() + ) + + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + planning_subtrees = list( + /datum/ai_planning_subtree/random_speech/cockroach, + /datum/ai_planning_subtree/find_and_hunt_target +) + + +/datum/ai_controller/basic_controller/cockroach/PerformIdleBehavior(delta_time) + . = ..() + var/mob/living/living_pawn = pawn + + if(DT_PROB(25, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) + var/move_dir = pick(GLOB.alldirs) + living_pawn.Move(get_step(living_pawn, move_dir), move_dir) + + +/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 + gold_core_spawnable = HOSTILE_SPAWN + faction = list("hostile") + ai_controller = /datum/ai_controller/basic_controller/cockroach/glockroach + +/mob/living/basic/cockroach/glockroach/Initialize() + . = ..() + AddElement(/datum/element/ranged_attacks, /obj/item/ammo_casing/glockroach) + +/datum/ai_controller/basic_controller/cockroach/glockroach + planning_subtrees = list( + /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/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 + +/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?" + icon_state = "hauberoach" + attack_verb_continuous = "rams its spike into" + attack_verb_simple = "ram your spike into" + melee_damage_lower = 2.5 + melee_damage_upper = 10 + obj_damage = 10 + gold_core_spawnable = HOSTILE_SPAWN + attack_sound = 'sound/weapons/bladeslice.ogg' + attack_vis_effect = ATTACK_EFFECT_SLASH + faction = list("hostile") + sharpness = SHARP_POINTY + ai_controller = /datum/ai_controller/basic_controller/cockroach/hauberoach + +/mob/living/basic/cockroach/hauberoach/Initialize() + . = ..() + AddComponent(/datum/component/caltrop, min_damage = 10, max_damage = 15, flags = (CALTROP_BYPASS_SHOES | CALTROP_SILENT)) + AddComponent(/datum/component/squashable, squash_chance = 100, squash_damage = 1, squash_callback = /mob/living/basic/cockroach/hauberoach/.proc/on_squish) + +///Proc used to override the squashing behavior of the normal cockroach. +/mob/living/basic/cockroach/hauberoach/proc/on_squish(mob/living/cockroach, mob/living/living_target) + if(!istype(living_target)) + return FALSE //We failed to run the invoke. Might be because we're a structure. Let the squashable element handle it then! + if(!HAS_TRAIT(living_target, TRAIT_PIERCEIMMUNE)) + living_target.visible_message(span_danger("[living_target] steps onto [cockroach]'s spike!"), span_userdanger("You step onto [cockroach]'s spike!")) + return TRUE + 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/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/basic_melee_attack_subtree/hauberoach + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/hauberoach + +/datum/ai_behavior/basic_melee_attack/hauberoach //Slightly slower, as this is being made in feature freeze ;) + action_cooldown = 1 SECONDS diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 7fc991fe811..40d9ae8fbf2 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -335,6 +335,23 @@ apply_damage(damage, BRUTE, affecting, armor_block) +/mob/living/carbon/human/attack_basic_mob(mob/living/basic/user, list/modifiers) + . = ..() + if(!.) + return + var/damage = rand(user.melee_damage_lower, user.melee_damage_upper) + if(check_shields(user, damage, "the [user.name]", MELEE_ATTACK, user.armour_penetration)) + return FALSE + var/dam_zone = dismembering_strike(user, pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + if(!dam_zone) //Dismemberment successful + return TRUE + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor = run_armor_check(affecting, MELEE, armour_penetration = user.armour_penetration) + apply_damage(damage, user.melee_damage_type, affecting, armor, wound_bonus = user.wound_bonus, bare_wound_bonus = user.bare_wound_bonus, sharpness = user.sharpness) + + /mob/living/carbon/human/attack_animal(mob/living/simple_animal/user, list/modifiers) . = ..() if(!.) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index fa7f26b5ccd..006ab551f18 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -12,6 +12,8 @@ /mob/living/proc/Life(delta_time = SSMOBS_DT, times_fired) set waitfor = FALSE + SEND_SIGNAL(src, COMSIG_LIVING_LIFE, delta_time, times_fired) + if (client) var/turf/T = get_turf(src) if(!T) @@ -78,6 +80,7 @@ return 1 /mob/living/proc/handle_breathing(delta_time, times_fired) + SEND_SIGNAL(src, COMSIG_LIVING_HANDLE_BREATHING, delta_time, times_fired) return /mob/living/proc/handle_mutations_and_radiation(delta_time, times_fired) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index d5e77c03c73..be385b32264 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -218,6 +218,26 @@ to_chat(M, span_danger("You glomp [src]!")) return TRUE +/mob/living/attack_basic_mob(mob/living/basic/user, list/modifiers) + if(user.melee_damage_upper == 0) + if(user != src) + visible_message(span_notice("\The [user] [user.friendly_verb_continuous] [src]!"), \ + span_notice("\The [user] [user.friendly_verb_continuous] you!"), null, COMBAT_MESSAGE_RANGE, user) + to_chat(user, span_notice("You [user.friendly_verb_simple] [src]!")) + return FALSE + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, span_warning("You don't want to hurt anyone!")) + return FALSE + + if(user.attack_sound) + playsound(loc, user.attack_sound, 50, TRUE, TRUE) + user.do_attack_animation(src) + visible_message(span_danger("\The [user] [user.attack_verb_continuous] [src]!"), \ + span_userdanger("\The [user] [user.attack_verb_continuous] you!"), null, COMBAT_MESSAGE_RANGE, user) + to_chat(user, span_danger("You [user.attack_verb_simple] [src]!")) + log_combat(user, src, "attacked") + return TRUE + /mob/living/attack_animal(mob/living/simple_animal/user, list/modifiers) . = ..() user.face_atom(src) diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm index 3ef57128e10..bf938c2dccd 100644 --- a/code/modules/mob/living/simple_animal/animal_defense.dm +++ b/code/modules/mob/living/simple_animal/animal_defense.dm @@ -90,6 +90,12 @@ if(.) L.amount_grown = min(L.amount_grown + damage, L.max_grown) +/mob/living/simple_animal/attack_basic_mob(mob/living/basic/user, list/modifiers) + . = ..() + if(.) + var/damage = rand(user.melee_damage_lower, user.melee_damage_upper) + return attack_threshold_check(damage, user.melee_damage_type) + /mob/living/simple_animal/attack_animal(mob/living/simple_animal/user, list/modifiers) . = ..() if(.) diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm index 0bc368f0dc7..4ef6ca445a0 100644 --- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm +++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm @@ -304,7 +304,7 @@ target_types += /obj/effect/decal/cleanable/trail_holder if(pests) - target_types += /mob/living/simple_animal/hostile/cockroach + target_types += /mob/living/basic/cockroach target_types += /mob/living/simple_animal/mouse if(drawn) @@ -336,12 +336,12 @@ playsound(src, 'sound/effects/spray2.ogg', 50, TRUE, -6) A.acid_act(75, 10) target = null - else if(istype(A, /mob/living/simple_animal/hostile/cockroach) || istype(A, /mob/living/simple_animal/mouse)) - var/mob/living/simple_animal/M = target - if(!M.stat) - visible_message(span_danger("[src] smashes [target] with its mop!")) - M.death() - target = null + else if(istype(A, /mob/living/basic/cockroach) || istype(A, /mob/living/simple_animal/mouse)) + var/mob/living/living_target = target + if(!living_target.stat) + visible_message(span_danger("[src] smashes [living_target] with its mop!")) + living_target.death() + living_target = null else if(emagged == 2) //Emag functions if(istype(A, /mob/living/carbon)) diff --git a/code/modules/mob/living/simple_animal/friendly/ant.dm b/code/modules/mob/living/simple_animal/friendly/ant.dm index 3e246ec4a02..f0dd57a41e6 100644 --- a/code/modules/mob/living/simple_animal/friendly/ant.dm +++ b/code/modules/mob/living/simple_animal/friendly/ant.dm @@ -43,7 +43,7 @@ turns_since_scan++ if(turns_since_scan > time_to_hunt) turns_since_scan = 0 - var/list/target_types = list(/mob/living/simple_animal/hostile/cockroach) + var/list/target_types = list(/mob/living/basic/cockroach) for(var/mob/living/simple_animal/hostile/potential_target in view(2, get_turf(src))) if(potential_target.type in target_types) hunt(potential_target) diff --git a/code/modules/mob/living/simple_animal/friendly/lizard.dm b/code/modules/mob/living/simple_animal/friendly/lizard.dm index 8ff911aabbc..c5002ea4a65 100644 --- a/code/modules/mob/living/simple_animal/friendly/lizard.dm +++ b/code/modules/mob/living/simple_animal/friendly/lizard.dm @@ -25,7 +25,7 @@ gold_core_spawnable = FRIENDLY_SPAWN obj_damage = 0 environment_smash = ENVIRONMENT_SMASH_NONE - var/static/list/edibles = typecacheof(list(/mob/living/simple_animal/butterfly, /mob/living/simple_animal/hostile/cockroach)) //list of atoms, however turfs won't affect AI, but will affect consumption. + var/static/list/edibles = typecacheof(list(/mob/living/simple_animal/butterfly, /mob/living/basic/cockroach)) //list of atoms, however turfs won't affect AI, but will affect consumption. /mob/living/simple_animal/hostile/lizard/Initialize() . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/cockroach.dm b/code/modules/mob/living/simple_animal/hostile/cockroach.dm deleted file mode 100644 index 0d0b820ee16..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/cockroach.dm +++ /dev/null @@ -1,125 +0,0 @@ -/mob/living/simple_animal/hostile/cockroach - name = "cockroach" - desc = "This station is just crawling with bugs." - icon_state = "cockroach" - icon_dead = "cockroach" - health = 1 - maxHealth = 1 - turns_per_move = 5 - loot = list(/obj/effect/decal/cleanable/insectguts) - atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 270 - maxbodytemp = INFINITY - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - mob_size = MOB_SIZE_TINY - mob_biotypes = MOB_ORGANIC|MOB_BUG - response_disarm_continuous = "shoos" - response_disarm_simple = "shoo" - response_harm_continuous = "splats" - response_harm_simple = "splat" - speak_emote = list("chitters") - density = FALSE - melee_damage_lower = 0 - melee_damage_upper = 0 - obj_damage = 0 - gold_core_spawnable = FRIENDLY_SPAWN - verb_say = "chitters" - verb_ask = "chitters inquisitively" - verb_exclaim = "chitters loudly" - verb_yell = "chitters loudly" - del_on_death = TRUE - environment_smash = ENVIRONMENT_SMASH_NONE - faction = list("neutral") - var/squish_chance = 50 - // Randomizes hunting intervals, minumum 5 turns - var/time_to_hunt = 5 - - -/mob/living/simple_animal/hostile/cockroach/Initialize() - . = ..() - add_cell_sample() - make_squashable() - ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) - time_to_hunt = rand(5,10) - -/mob/living/simple_animal/hostile/cockroach/proc/make_squashable() - AddComponent(/datum/component/squashable, squash_chance = 50, squash_damage = 1) - -/mob/living/simple_animal/hostile/cockroach/add_cell_sample() - AddElement(/datum/element/swabable, CELL_LINE_TABLE_COCKROACH, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 7) - -/mob/living/simple_animal/hostile/cockroach/Life(delta_time = SSMOBS_DT, times_fired) // Cockroaches are predators to space ants - . = ..() - turns_since_scan++ - if(turns_since_scan > time_to_hunt) - turns_since_scan = 0 - var/list/target_types = list(/obj/effect/decal/cleanable/ants) - for(var/obj/effect/decal/cleanable/ants/potential_target in view(2, get_turf(src))) - if(potential_target.type in target_types) - hunt(potential_target) - return - - -/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/simple_animal/hostile/cockroach/glockroach - name = "glockroach" - desc = "HOLY SHIT, THAT COCKROACH HAS A GUN!" - icon_state = "glockroach" - melee_damage_lower = 5 - melee_damage_upper = 5 - obj_damage = 20 - gold_core_spawnable = HOSTILE_SPAWN - projectilesound = 'sound/weapons/gun/pistol/shot.ogg' - projectiletype = /obj/projectile/glockroachbullet - casingtype = /obj/item/ammo_casing/glockroach - ranged = TRUE - faction = list("hostile") - -/mob/living/simple_animal/hostile/cockroach/death(gibbed) - if(GLOB.station_was_nuked) //If the nuke is going off, then cockroaches are invincible. Keeps the nuke from killing them, cause cockroaches are immune to nukes. - return - ..() - -/mob/living/simple_animal/hostile/cockroach/ex_act() //Explosions are a terrible way to handle a cockroach. - return FALSE - -/mob/living/simple_animal/hostile/cockroach/hauberoach - name = "hauberoach" - desc = "Is that cockroach wearing a tiny yet immaculate replica 19th century Prussian spiked helmet? ...Is that a bad thing?" - icon_state = "hauberoach" - attack_verb_continuous = "rams its spike into" - attack_verb_simple = "ram your spike into" - melee_damage_lower = 5 - melee_damage_upper = 20 - obj_damage = 20 - gold_core_spawnable = HOSTILE_SPAWN - attack_sound = 'sound/weapons/bladeslice.ogg' - attack_vis_effect = ATTACK_EFFECT_SLASH - faction = list("hostile") - sharpness = SHARP_POINTY - squish_chance = 0 // manual squish if relevant - -/mob/living/simple_animal/hostile/cockroach/hauberoach/Initialize() - . = ..() - AddComponent(/datum/component/caltrop, min_damage = 10, max_damage = 15, flags = (CALTROP_BYPASS_SHOES | CALTROP_SILENT)) - -/mob/living/simple_animal/hostile/cockroach/hauberoach/make_squashable() - AddComponent(/datum/component/squashable, squash_chance = 100, squash_damage = 1, squash_callback = /mob/living/simple_animal/hostile/cockroach/hauberoach/.proc/on_squish) - -///Proc used to override the squashing behavior of the normal cockroach. -/mob/living/simple_animal/hostile/cockroach/hauberoach/proc/on_squish(mob/living/cockroach, mob/living/living_target) - if(!istype(living_target)) - return FALSE //We failed to run the invoke. Might be because we're a structure. Let the squashable element handle it then! - if(!HAS_TRAIT(living_target, TRAIT_PIERCEIMMUNE)) - living_target.visible_message(span_danger("[living_target] steps onto [cockroach]'s spike!"), span_userdanger("You step onto [cockroach]'s spike!")) - return TRUE - 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 diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index f58a2ac4721..90f2cb7d200 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -628,7 +628,7 @@ mobcheck = TRUE break if(!mobcheck) - new /mob/living/simple_animal/hostile/cockroach(get_step(src,dir)) //Just in case there aren't any animals on the station, this will leave you with a terrible option to possess if you feel like it //i found it funny that in the file for a giant angel beast theres a cockroach + new /mob/living/basic/cockroach(get_step(src,dir)) //Just in case there aren't any animals on the station, this will leave you with a terrible option to possess if you feel like it //i found it funny that in the file for a giant angel beast theres a cockroach /obj/structure/closet/stasis name = "quantum entanglement stasis warp field" diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm index f8451b9b990..2dac1db55b3 100644 --- a/code/modules/reagents/chemistry/recipes.dm +++ b/code/modules/reagents/chemistry/recipes.dm @@ -251,15 +251,15 @@ C.flash_act() for(var/i in 1 to amount_to_spawn) - var/mob/living/simple_animal/S + var/mob/living/spawned_mob if(random) - S = create_random_mob(get_turf(holder.my_atom), mob_class) + spawned_mob = create_random_mob(get_turf(holder.my_atom), mob_class) else - S = new mob_class(get_turf(holder.my_atom))//Spawn our specific mob_class - S.faction |= mob_faction + spawned_mob = new mob_class(get_turf(holder.my_atom))//Spawn our specific mob_class + spawned_mob.faction |= mob_faction if(prob(50)) for(var/j = 1, j <= rand(1, 3), j++) - step(S, pick(NORTH,SOUTH,EAST,WEST)) + step(spawned_mob, pick(NORTH,SOUTH,EAST,WEST)) /** * Magical move-wooney that happens sometimes. diff --git a/code/modules/research/xenobiology/crossbreeding/burning.dm b/code/modules/research/xenobiology/crossbreeding/burning.dm index e48b32afa33..99cc7805377 100644 --- a/code/modules/research/xenobiology/crossbreeding/burning.dm +++ b/code/modules/research/xenobiology/crossbreeding/burning.dm @@ -247,11 +247,11 @@ Burning extracts: /obj/item/slimecross/burning/gold/do_effect(mob/user) user.visible_message(span_danger("[src] shudders violently, and summons an army for [user]!")) for(var/i in 1 to 3) //Less than gold normally does, since it's safer and faster. - var/mob/living/simple_animal/S = create_random_mob(get_turf(user), HOSTILE_SPAWN) - S.faction |= "[REF(user)]" + var/mob/living/spawned_mob = create_random_mob(get_turf(user), HOSTILE_SPAWN) + spawned_mob.faction |= "[REF(user)]" if(prob(50)) for(var/j = 1, j <= rand(1, 3), j++) - step(S, pick(NORTH,SOUTH,EAST,WEST)) + step(spawned_mob, pick(NORTH,SOUTH,EAST,WEST)) ..() /obj/item/slimecross/burning/oil 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 3cad8ef5286..ec8614c1507 100644 --- a/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm +++ b/code/modules/research/xenobiology/vatgrowing/samples/cell_lines/common.dm @@ -333,7 +333,7 @@ /datum/reagent/consumable/ethanol/bug_spray = -4) virus_suspectibility = 0 - resulting_atoms = list(/mob/living/simple_animal/hostile/cockroach = 5) + resulting_atoms = list(/mob/living/basic/cockroach = 5) /datum/micro_organism/cell_line/pine desc = "Coniferous plant cells" diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index 68f07a8b71b..93fb4bc6cc4 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -125,22 +125,22 @@ if(SLIME_ACTIVATE_MINOR) user.visible_message(span_warning("[user] starts shaking!"),span_notice("Your [name] starts pulsing gently...")) if(do_after(user, 40, target = user)) - var/mob/living/simple_animal/S = create_random_mob(user.drop_location(), FRIENDLY_SPAWN) - S.faction |= "neutral" + var/mob/living/spawned_mob = create_random_mob(user.drop_location(), FRIENDLY_SPAWN) + spawned_mob.faction |= "neutral" playsound(user, 'sound/effects/splat.ogg', 50, TRUE) - user.visible_message(span_warning("[user] spits out [S]!"), span_notice("You spit out [S]!")) + user.visible_message(span_warning("[user] spits out [spawned_mob]!"), span_notice("You spit out [spawned_mob]!")) return 300 if(SLIME_ACTIVATE_MAJOR) user.visible_message(span_warning("[user] starts shaking violently!"),span_warning("Your [name] starts pulsing violently...")) if(do_after(user, 50, target = user)) - var/mob/living/simple_animal/S = create_random_mob(user.drop_location(), HOSTILE_SPAWN) + var/mob/living/spawned_mob = create_random_mob(user.drop_location(), HOSTILE_SPAWN) if(!user.combat_mode) - S.faction |= "neutral" + spawned_mob.faction |= "neutral" else - S.faction |= "slime" + spawned_mob.faction |= "slime" playsound(user, 'sound/effects/splat.ogg', 50, TRUE) - user.visible_message(span_warning("[user] spits out [S]!"), span_warning("You spit out [S]!")) + user.visible_message(span_warning("[user] spits out [spawned_mob]!"), span_warning("You spit out [spawned_mob]!")) return 600 /obj/item/slime_extract/silver diff --git a/tgstation.dme b/tgstation.dme index 6eda4fa316c..f650d23d51a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -35,6 +35,7 @@ #include "code\__DEFINES\aquarium.dm" #include "code\__DEFINES\art.dm" #include "code\__DEFINES\atom_hud.dm" +#include "code\__DEFINES\basic_mobs.dm" #include "code\__DEFINES\bitfields.dm" #include "code\__DEFINES\blackmarket.dm" #include "code\__DEFINES\blob_defines.dm" @@ -386,6 +387,7 @@ #include "code\controllers\subsystem\vote.dm" #include "code\controllers\subsystem\weather.dm" #include "code\controllers\subsystem\processing\acid.dm" +#include "code\controllers\subsystem\processing\ai_basic_avoidance.dm" #include "code\controllers\subsystem\processing\ai_behaviors.dm" #include "code\controllers\subsystem\processing\ai_movement.dm" #include "code\controllers\subsystem\processing\clock_component.dm" @@ -468,6 +470,13 @@ #include "code\datums\ai\bane\bane_behaviors.dm" #include "code\datums\ai\bane\bane_controller.dm" #include "code\datums\ai\bane\bane_subtrees.dm" +#include "code\datums\ai\basic_mobs\base_basic_controller.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\targetting.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" +#include "code\datums\ai\basic_mobs\targetting_datums\basic_targetting_datum.dm" #include "code\datums\ai\cursed\cursed_behaviors.dm" #include "code\datums\ai\cursed\cursed_controller.dm" #include "code\datums\ai\cursed\cursed_subtrees.dm" @@ -476,7 +485,7 @@ #include "code\datums\ai\dog\dog_subtrees.dm" #include "code\datums\ai\hauntium\haunted_controller.dm" #include "code\datums\ai\hauntium\hauntium_subtrees.dm" -#include "code\datums\ai\hostile\hostile_controller.dm" +#include "code\datums\ai\hunting_behavior\hunting_behaviors.dm" #include "code\datums\ai\monkey\monkey_behaviors.dm" #include "code\datums\ai\monkey\monkey_controller.dm" #include "code\datums\ai\monkey\monkey_subtrees.dm" @@ -486,6 +495,7 @@ #include "code\datums\ai\movement\ai_movement_jps.dm" #include "code\datums\ai\objects\vending_machines\vending_machine_behaviors.dm" #include "code\datums\ai\objects\vending_machines\vending_machine_controller.dm" +#include "code\datums\ai\oldhostile\hostile_tameable.dm" #include "code\datums\ai\robot_customer\robot_customer_behaviors.dm" #include "code\datums\ai\robot_customer\robot_customer_controller.dm" #include "code\datums\ai\robot_customer\robot_customer_subtrees.dm" @@ -708,9 +718,11 @@ #include "code\datums\elements\_element.dm" #include "code\datums\elements\animal_variety.dm" #include "code\datums\elements\art.dm" +#include "code\datums\elements\atmos_requirements.dm" #include "code\datums\elements\atmos_sensitive.dm" #include "code\datums\elements\backblast.dm" #include "code\datums\elements\bane.dm" +#include "code\datums\elements\basic_body_temp_sensitive.dm" #include "code\datums\elements\beauty.dm" #include "code\datums\elements\bed_tucking.dm" #include "code\datums\elements\bsa_blocker.dm" @@ -721,6 +733,7 @@ #include "code\datums\elements\crackable.dm" #include "code\datums\elements\curse_announcement.dm" #include "code\datums\elements\cursed.dm" +#include "code\datums\elements\death_drops.dm" #include "code\datums\elements\deferred_aquarium_content.dm" #include "code\datums\elements\delete_on_drop.dm" #include "code\datums\elements\digitalcamo.dm" @@ -751,6 +764,7 @@ #include "code\datums\elements\plant_backfire.dm" #include "code\datums\elements\point_of_interest.dm" #include "code\datums\elements\rad_insulation.dm" +#include "code\datums\elements\ranged_attacks.dm" #include "code\datums\elements\ridable.dm" #include "code\datums\elements\rust.dm" #include "code\datums\elements\selfknockback.dm" @@ -2689,6 +2703,10 @@ #include "code\modules\mob\living\status_procs.dm" #include "code\modules\mob\living\taste.dm" #include "code\modules\mob\living\ventcrawling.dm" +#include "code\modules\mob\living\basic\basic.dm" +#include "code\modules\mob\living\basic\basic_defense.dm" +#include "code\modules\mob\living\basic\health_adjustment.dm" +#include "code\modules\mob\living\basic\vermin\cockroach.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" @@ -2901,7 +2919,6 @@ #include "code\modules\mob\living\simple_animal\hostile\bees.dm" #include "code\modules\mob\living\simple_animal\hostile\carp.dm" #include "code\modules\mob\living\simple_animal\hostile\cat_butcher.dm" -#include "code\modules\mob\living\simple_animal\hostile\cockroach.dm" #include "code\modules\mob\living\simple_animal\hostile\dark_wizard.dm" #include "code\modules\mob\living\simple_animal\hostile\eyeballs.dm" #include "code\modules\mob\living\simple_animal\hostile\faithless.dm"