mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
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:
File diff suppressed because it is too large
Load Diff
@@ -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" = (
|
||||
|
||||
@@ -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"
|
||||
|
||||
5
code/__DEFINES/basic_mobs.dm
Normal file
5
code/__DEFINES/basic_mobs.dm
Normal file
@@ -0,0 +1,5 @@
|
||||
#define BASIC_MOB_MAX_STAMINALOSS 200
|
||||
|
||||
///Basic mob flags
|
||||
#define DEL_ON_DEATH (1<<0)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
*
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
PROCESSING_SUBSYSTEM_DEF(basic_avoidance)
|
||||
name = "Basic Avoidance"
|
||||
flags = SS_NO_INIT
|
||||
wait = 2 SECONDS
|
||||
@@ -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)),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
26
code/datums/ai/basic_mobs/base_basic_controller.dm
Normal file
26
code/datums/ai/basic_mobs/base_basic_controller.dm
Normal 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
|
||||
@@ -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
|
||||
45
code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm
Normal file
45
code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm
Normal 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)
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
39
code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm
Normal file
39
code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm
Normal 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")
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
73
code/datums/ai/hunting_behavior/hunting_behaviors.dm
Normal file
73
code/datums/ai/hunting_behavior/hunting_behaviors.dm
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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].")
|
||||
75
code/datums/elements/atmos_requirements.dm
Normal file
75
code/datums/elements/atmos_requirements.dm
Normal 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
|
||||
65
code/datums/elements/basic_body_temp_sensitive.dm
Normal file
65
code/datums/elements/basic_body_temp_sensitive.dm
Normal 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")
|
||||
34
code/datums/elements/death_drops.dm
Normal file
34
code/datums/elements/death_drops.dm
Normal 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())
|
||||
50
code/datums/elements/ranged_attacks.dm
Normal file
50
code/datums/elements/ranged_attacks.dm
Normal 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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
))
|
||||
|
||||
@@ -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].")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
144
code/modules/mob/living/basic/basic.dm
Normal file
144
code/modules/mob/living/basic/basic.dm
Normal 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)
|
||||
|
||||
186
code/modules/mob/living/basic/basic_defense.dm
Normal file
186
code/modules/mob/living/basic/basic_defense.dm
Normal 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()
|
||||
56
code/modules/mob/living/basic/health_adjustment.dm
Normal file
56
code/modules/mob/living/basic/health_adjustment.dm
Normal 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()
|
||||
150
code/modules/mob/living/basic/vermin/cockroach.dm
Normal file
150
code/modules/mob/living/basic/vermin/cockroach.dm
Normal 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
|
||||
@@ -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(!.)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(.)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
. = ..()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user