Basic Mobs: the cooler simple mobs that run on datum AI. (With reworked cockroach AI as proof of concept) (#7867)

Co-authored-by: AMonkeyThatCodes <20987591+AMonkeyThatCodes@users.noreply.github.com>
Co-authored-by: Gandalf <jzo123@hotmail.com>
This commit is contained in:
SkyratBot
2021-09-01 17:47:39 +02:00
committed by GitHub
parent 9fd2679667
commit 02ad59791b
59 changed files with 28993 additions and 27639 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -57155,7 +57155,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)
"sbH" = (

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
#define BASIC_MOB_MAX_STAMINALOSS 200
///Basic mob flags
#define DEL_ON_DEATH (1<<0)

View File

@@ -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)()
@@ -1294,6 +1305,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"

View File

@@ -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))

View File

@@ -542,14 +542,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

View File

@@ -262,6 +262,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]
*

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1,4 @@
PROCESSING_SUBSYSTEM_DEF(basic_avoidance)
name = "Basic Avoidance"
flags = SS_NO_INIT
wait = 2 SECONDS

View File

@@ -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)),

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -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)

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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?

View File

@@ -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

View File

@@ -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].")

View File

@@ -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

View File

@@ -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")

View File

@@ -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())

View File

@@ -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()

View File

@@ -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)

View File

@@ -809,6 +809,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)

View File

@@ -737,7 +737,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
))

View File

@@ -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].")

View File

@@ -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"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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(!.)

View File

@@ -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)

View File

@@ -239,6 +239,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)

View File

@@ -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(.)

View File

@@ -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))

View File

@@ -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)

View File

@@ -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()
. = ..()

View File

@@ -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"

View File

@@ -258,15 +258,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.

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -36,6 +36,7 @@
#include "code\__DEFINES\art.dm"
#include "code\__DEFINES\atmospherics.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"
@@ -417,6 +418,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"
@@ -499,6 +501,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"
@@ -507,7 +516,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"
@@ -517,6 +526,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"
@@ -738,9 +748,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"
@@ -751,6 +763,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"
@@ -781,6 +794,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"
@@ -2720,6 +2734,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"
@@ -2932,7 +2950,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"