[READY] Creates Datumized AI and applies it to monkeys (#55238)

New AI system, implemented for monkeys.
This commit is contained in:
Qustinnus
2020-12-13 13:19:54 +01:00
committed by GitHub
parent e06a1035ac
commit 12c1464bc3
39 changed files with 855 additions and 502 deletions
+2
View File
@@ -105,6 +105,8 @@
#define TR_KEEPSTUNS (1<<9)
#define TR_KEEPREAGENTS (1<<10)
#define TR_KEEPSTAMINADAMAGE (1<<11)
#define TR_KEEPAI (1<<12)
//species traits for mutantraces
#define MUTCOLORS 1
#define HAIR 2
+35
View File
@@ -0,0 +1,35 @@
#define GET_AI_BEHAVIOR(behavior_type) SSai_controllers.ai_behaviors[behavior_type]
#define HAS_AI_CONTROLLER_TYPE(thing, type) istype(thing?.ai_controller, type)
#define AI_STATUS_ON 1
#define AI_STATUS_OFF 2
///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))
///Max pathing attempts before auto-fail
#define MAX_PATHING_ATTEMPTS 30
///Flags for ai_behavior new()
#define AI_CONTROLLER_INCOMPATIBLE (1<<0)
///Does this task require movement from the AI before it can be performed?
#define AI_BEHAVIOR_REQUIRE_MOVEMENT (1<<0)
///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)
///Monkey AI controller blackboard keys
#define BB_MONKEY_AGRESSIVE "BB_monkey_agressive"
#define BB_MONKEY_BEST_FORCE_FOUND "BB_monkey_bestforcefound"
#define BB_MONKEY_ENEMIES "BB_monkey_enemies"
#define BB_MONKEY_BLACKLISTITEMS "BB_monkey_blacklistitems"
#define BB_MONKEY_PICKUPTARGET "BB_monkey_pickuptarget"
#define BB_MONKEY_PICKPOCKETING "BB_monkey_pickpocketing"
#define BB_MONKEY_CURRENT_ATTACK_TARGET "BB_monkey_current_attack_target"
#define BB_MONKEY_TARGET_DISPOSAL "BB_monkey_target_disposal"
#define BB_MONKEY_DISPOSING "BB_monkey_disposing"
#define BB_MONKEY_RECRUIT_COOLDOWN "BB_monkey_recruit_cooldown"
+7
View File
@@ -157,6 +157,8 @@
#define COMSIG_ATOM_ORBIT_STOP "atom_orbit_stop"
///from base of atom/set_opacity(): (new_opacity)
#define COMSIG_ATOM_SET_OPACITY "atom_set_opacity"
///from base of atom/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
#define COMSIG_ATOM_HITBY "atom_hitby"
//from base of atom/movable/on_enter_storage(): (datum/component/storage/concrete/master_storage)
#define COMSIG_STORAGE_ENTERED "storage_entered"
@@ -391,6 +393,9 @@
#define COMSIG_LIVING_REGENERATE_LIMBS "living_regen_limbs"
///from base of mob/living/set_buckled(): (new_buckled)
#define COMSIG_LIVING_SET_BUCKLED "living_set_buckled"
///From post-can inject check of syringe after attack (mob/user)
#define COMSIG_LIVING_TRY_SYRINGE "living_try_syringe"
///Sent when bloodcrawl ends in mob/living/phasein(): (phasein_decal)
#define COMSIG_LIVING_AFTERPHASEIN "living_phasein"
@@ -463,6 +468,8 @@
#define COMSIG_CARBON_EMBED_RIP "item_embed_start_rip"
///called when removing a given item from a mob, from mob/living/carbon/remove_embedded_object(mob/living/carbon/target, /obj/item)
#define COMSIG_CARBON_EMBED_REMOVAL "item_embed_remove_safe"
///Called when someone attempts to cuff a carbon
#define COMSIG_CARBON_CUFF_ATTEMPTED "carbon_attempt_cuff"
// /mob/living/simple_animal/hostile signals
#define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget"
+34 -30
View File
@@ -1,37 +1,41 @@
//Monkey defines, placed here so they can be read by other things!
//Mode defines
#define MONKEY_IDLE 0 // idle
#define MONKEY_HUNT 1 // found target, hunting
#define MONKEY_FLEE 2 // free from enemies
#define MONKEY_DISPOSE 3 // dump body in disposals
#define MONKEY_FLEE_HEALTH 50 // below this health value the monkey starts to flee from enemies
#define MONKEY_ENEMY_VISION 9 // how close an enemy must be to trigger aggression
#define MONKEY_FLEE_VISION 4 // how close an enemy must be before it triggers flee
#define MONKEY_ITEM_SNATCH_DELAY 25 // How long does it take the item to be taken from a mobs hand
#define MONKEY_CUFF_RETALIATION_PROB 20 // Probability monkey will aggro when cuffed
#define MONKEY_SYRINGE_RETALIATION_PROB 20 // Probability monkey will aggro when syringed
/// below this health value the monkey starts to flee from enemies
#define MONKEY_FLEE_HEALTH 50
/// how close an enemy must be to trigger aggression
#define MONKEY_ENEMY_VISION 9
/// how close an enemy must be before it triggers flee
#define MONKEY_FLEE_VISION 4
/// How long does it take the item to be taken from a mobs hand
#define MONKEY_ITEM_SNATCH_DELAY 25
/// Probability monkey will aggro when cuffed
#define MONKEY_CUFF_RETALIATION_PROB 20
/// Probability monkey will aggro when syringed
#define MONKEY_SYRINGE_RETALIATION_PROB 20
// Probability per Life tick that the monkey will:
#define MONKEY_RESIST_PROB 50 // resist out of restraints
// when the monkey is idle
#define MONKEY_PULL_AGGRO_PROB 5 // aggro against the mob pulling it
#define MONKEY_SHENANIGAN_PROB 5 // chance of getting into mischief, i.e. finding/stealing items
// when the monkey is hunting
#define MONKEY_ATTACK_DISARM_PROB 50 // disarm an armed attacker
#define MONKEY_WEAPON_PROB 20 // if not currently getting an item, search for a weapon around it
#define MONKEY_RECRUIT_PROB 25 // recruit a monkey near it
#define MONKEY_SWITCH_TARGET_PROB 25 // switch targets if it sees another enemy
/// probability that monkey resist out of restraints
#define MONKEY_RESIST_PROB 50
/// probability that monkey aggro against the mob pulling it
#define MONKEY_PULL_AGGRO_PROB 5
/// probability that monkey will get into mischief, i.e. finding/stealing items
#define MONKEY_SHENANIGAN_PROB 20
/// probability that monkey will disarm an armed attacker
#define MONKEY_ATTACK_DISARM_PROB 50
/// probability that monkey will get recruited when friend is attacked
#define MONKEY_RECRUIT_PROB 25
#define MONKEY_RETALIATE_HARM_PROB 95 // probability for the monkey to aggro when attacked with harm intent
#define MONKEY_RETALIATE_DISARM_PROB 20 // probability for the monkey to aggro when attacked with disarm intent
/// probability for the monkey to aggro when attacked with harm intent
#define MONKEY_RETALIATE_HARM_PROB 95
/// probability for the monkey to aggro when attacked with disarm intent
#define MONKEY_RETALIATE_DISARM_PROB 20
#define MONKEY_HATRED_AMOUNT 4 // amount of aggro to add to an enemy when they attack user
#define MONKEY_HATRED_REDUCTION_PROB 25 // probability of reducing aggro by one when the monkey attacks
/// amount of aggro to add to an enemy when they attack user
#define MONKEY_HATRED_AMOUNT 4
/// amount of aggro to add to an enemy when a monkey is recruited
#define MONKEY_RECRUIT_HATED_AMOUNT 2
/// probability of reducing aggro by one when the monkey attacks
#define MONKEY_HATRED_REDUCTION_PROB 20
// how many Life ticks the monkey will fail to:
#define MONKEY_HUNT_FRUSTRATION_LIMIT 8 // Chase after an enemy before giving up
#define MONKEY_DISPOSE_FRUSTRATION_LIMIT 16 // Dispose of a body before giving up
#define MONKEY_AGGRESSIVE_MVM_PROB 0 // If you mass edit monkies to be aggressive. there is a small chance of in-fighting
///Monkey recruit cooldown
#define MONKEY_RECRUIT_COOLDOWN 1 MINUTES
+1
View File
@@ -84,6 +84,7 @@
#define VV_HK_TRIGGER_EXPLOSION "explode"
#define VV_HK_AUTO_RENAME "auto_rename"
#define VV_HK_RADIATE "radiate"
#define VV_HK_ADD_AI "add_ai"
// /obj
#define VV_HK_OSAY "osay"
+7
View File
@@ -6,6 +6,13 @@ GLOBAL_LIST_EMPTY(deadmins) //all ckeys who have used the de-admin verb.
GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client
GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, for stealthmins
GLOBAL_LIST_INIT(dangerous_turfs, typecacheof(list(
/turf/open/lava,
/turf/open/chasm,
/turf/open/space,
/turf/open/openspace)))
//Since it didn't really belong in any other category, I'm putting this here
//This is for procs to replace all the goddamn 'in world's that are chilling around the code
@@ -0,0 +1,22 @@
/// The subsystem used to tick [/datum/ai_controllers] instances. Handling the re-checking of plans.
PROCESSING_SUBSYSTEM_DEF(ai_controllers)
name = "AI behavior"
flags = SS_POST_FIRE_TIMING|SS_BACKGROUND
priority = FIRE_PRIORITY_NPC
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
wait = 8 //Uses the value of CLICK_CD_MELEE because that seemed like a nice standard for the speed of AI behavior
///an assoc list of all ai_behaviors by type, to
var/list/ai_behaviors
/datum/controller/subsystem/processing/ai_controllers/Initialize(timeofday)
SetupAIBehaviors()
return ..()
/datum/controller/subsystem/processing/ai_controllers/proc/SetupAIBehaviors()
ai_behaviors = list()
for(var/i in subtypesof(/datum/ai_behavior))
var/datum/ai_behavior/ai_behavior = new i
ai_behaviors[i] = ai_behavior
+21
View File
@@ -0,0 +1,21 @@
# AI controllers
## Introduction
Our AI controller system is an attempt at making it possible to create modularized AI that stores its behavior in datums, while keeping state and decision making in a controller. This allows a more versatile way of creating AI that doesn't rely on OOP as much, and doesn't clutter up the Life() code in Mobs.
## AI Controllers
A datum that can be added to any atom in the game. Similarly to components, they might only support a given subtype (e.g. /mob/living), but the idea is that theoretically, you could apply a specific AI controller to a big a group of different types as possible and it would still work.
These datums handle both the normal movement of mobs, but also their decision making, deciding which actions they will take based on the checks you put into their SelectBehaviors proc.
If behaviors are selected, and the AI is in range, it will try to perform them. It runs all the behaviors it currently has in parallel; allowing for it to for example screech at someone while trying to attack them. Aslong as it has behaviors running, it will not try to generate new plans, making it not waste CPU when it already has an active goal.
They also hold data for any of the actions they might need to use, such as cooldowns, whether or not they're currently fighting, etcetera this is stored in the blackboard, more information on that below.
### Blackboard
The blackboard is an associated list keyed with strings and with values of whatever you want. These store information the mob has such as "Am I attacking someone", "Do I have a weapon". By using an associated list like this, no data needs to be stored on the actions themselves, and you could make actions that work on multiple ai controllers if you so pleased by making the key to use a variable.
## AI Behavior
AI behaviors are the actions an AI can take. These can range from "Do an emote" to "Attack this target until he is dead". They are singletons and should contain nothing but static data. Any dynamic data should be stored in the blackboard, to allow different controllers to use the same behaviors.
+17
View File
@@ -0,0 +1,17 @@
///Abstract class for an action an AI can take, can range from movement to grabbing a nearby weapon.
/datum/ai_behavior
///What distance you need to be from the target to perform the action
var/required_distance = 1
///Flags for extra behavior
var/behavior_flags = NONE
///Called by the AI controller when this action is performed
/datum/ai_behavior/proc/perform(delta_time, datum/ai_controller/controller)
return
///Called when the action is finished.
/datum/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded)
controller.current_behaviors.Remove(src)
if(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //If this was a movement task, reset our movement target.
controller.current_movement_target = null
return
+137
View File
@@ -0,0 +1,137 @@
/*
AI controllers are a datumized form of AI that simulates the input a player would otherwise give to a atom. What this means is that these datums
have ways of interacting with a specific atom and control it. They posses a blackboard with the information the AI knows and has, and will plan behaviors it will try to execute.
*/
/datum/ai_controller
///The atom this controller is controlling
var/atom/pawn
///Bitfield of traits for this AI to handle extra behavior
var/ai_traits
///Current actions being performed by the AI.
var/list/current_behaviors = list()
///Current status of AI (OFF/ON/IDLE)
var/ai_status
///Current movement target of the AI, generally set by decision making.
var/atom/current_movement_target
///Delay between atom movements, if this is not a multiplication of the delay in
var/move_delay
///This is a list of variables the AI uses and can be mutated by actions. When an action is performed you pass this list and any relevant keys for the variables it can mutate.
var/list/blackboard = list()
///Tracks recent pathing attempts, if we fail too many in a row we fail our current plans.
var/pathing_attempts
/datum/ai_controller/New(atom/new_pawn)
PossessPawn(new_pawn)
/datum/ai_controller/Destroy(force, ...)
set_ai_status(AI_STATUS_OFF)
UnpossessPawn()
return ..()
///Proc to move from one pawn to another, this will destroy the target's existing controller.
/datum/ai_controller/proc/PossessPawn(atom/new_pawn)
if(pawn) //Reset any old signals
UnpossessPawn()
if(istype(new_pawn.ai_controller)) //Existing AI, kill it.
QDEL_NULL(new_pawn.ai_controller)
if(TryPossessPawn(new_pawn) & AI_CONTROLLER_INCOMPATIBLE)
qdel(src)
CRASH("[src] attached to [new_pawn] but these are not compatible!")
pawn = new_pawn
pawn.ai_controller = src
set_ai_status(AI_STATUS_ON)
RegisterSignal(pawn, COMSIG_MOB_LOGIN, .proc/on_sentience_gained)
///Abstract proc for initializing the pawn to the new controller
/datum/ai_controller/proc/TryPossessPawn(atom/new_pawn)
return
///Proc for deinitializing the pawn to the old controller
/datum/ai_controller/proc/UnpossessPawn()
UnregisterSignal(pawn, COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT)
pawn.ai_controller = null
pawn = null
return
///Returns TRUE if the ai controller can actually run at the moment.
/datum/ai_controller/proc/able_to_run()
return TRUE
/// Generates a plan and see if our existing one is still valid.
/datum/ai_controller/process(delta_time)
if(!able_to_run())
return //this should remove them from processing in the future through event-based stuff.
if(!current_behaviors?.len)
SelectBehaviors(delta_time)
if(!current_behaviors?.len)
PerformIdleBehavior(delta_time) //Do some stupid shit while we have nothing to do
return
var/want_to_move = FALSE
for(var/i in current_behaviors)
var/datum/ai_behavior/current_behavior = i
if(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT && current_movement_target && current_behavior.required_distance < get_dist(pawn, current_movement_target)) //Move closer
want_to_move = TRUE
if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //Move and perform the action
current_behavior.perform(delta_time, src)
else //Perform the action
current_behavior.perform(delta_time, src)
if(want_to_move)
MoveTo(delta_time) //Need to add some code to check if we can perform the actions now without too much overhead
///Move somewhere using dumb movement (byond base)
/datum/ai_controller/proc/MoveTo(delta_time)
var/current_loc = get_turf(pawn)
if(!is_type_in_typecache(get_step(pawn, get_dir(pawn, current_movement_target)), GLOB.dangerous_turfs))
step_towards(pawn, current_movement_target)
if(current_loc == get_turf(pawn))
if(++pathing_attempts >= MAX_PATHING_ATTEMPTS)
CancelActions()
pathing_attempts = 0
///Perform some dumb idle behavior.
/datum/ai_controller/proc/PerformIdleBehavior(delta_time)
return
///This is where you decide what actions are taken by the AI.
/datum/ai_controller/proc/SelectBehaviors(delta_time)
SHOULD_NOT_SLEEP(TRUE) //Fuck you don't sleep in procs like this.
return
///This proc handles changing ai status, and starts/stops processing if required.
/datum/ai_controller/proc/set_ai_status(new_ai_status)
if(ai_status == new_ai_status)
return FALSE //no change
ai_status = new_ai_status
switch(ai_status)
if(AI_STATUS_ON)
START_PROCESSING(SSai_controllers, src)
if(AI_STATUS_OFF)
STOP_PROCESSING(SSai_controllers, src)
CancelActions()
/datum/ai_controller/proc/CancelActions()
for(var/i in current_behaviors)
var/datum/ai_behavior/current_behavior = i
current_behavior.finish_action(src, FALSE)
/datum/ai_controller/proc/on_sentience_gained()
UnregisterSignal(pawn, COMSIG_MOB_LOGIN)
set_ai_status(AI_STATUS_OFF) //Can't do anything while player is connected
RegisterSignal(pawn, COMSIG_MOB_LOGOUT, .proc/on_sentience_lost)
/datum/ai_controller/proc/on_sentience_lost()
UnregisterSignal(pawn, COMSIG_MOB_LOGOUT)
set_ai_status(AI_STATUS_ON) //Can't do anything while player is connected
RegisterSignal(pawn, COMSIG_MOB_LOGIN, .proc/on_sentience_gained)
+22
View File
@@ -0,0 +1,22 @@
/datum/ai_behavior/resist/perform(delta_time, datum/ai_controller/controller)
. = ..()
var/mob/living/living_pawn = controller.pawn
living_pawn.resist()
finish_action(controller, TRUE)
/datum/ai_behavior/battle_screech
///List of possible screeches the behavior has
var/list/screeches
/datum/ai_behavior/battle_screech/perform(delta_time, datum/ai_controller/controller)
var/mob/living/living_pawn = controller.pawn
INVOKE_ASYNC(living_pawn, /mob.proc/emote, pick(screeches))
finish_action(controller, TRUE)
+258
View File
@@ -0,0 +1,258 @@
/datum/ai_behavior/battle_screech/monkey
screeches = list("roar","screech")
/datum/ai_behavior/monkey_equip
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT
/datum/ai_behavior/monkey_equip/finish_action(datum/ai_controller/controller, success)
. = ..()
if(!success) //Don't try again on this item if we failed
var/list/item_blacklist = controller.blackboard[BB_MONKEY_BLACKLISTITEMS]
var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET]
item_blacklist[target] = TRUE
controller.blackboard[BB_MONKEY_PICKUPTARGET] = null
/datum/ai_behavior/monkey_equip/proc/equip_item(datum/ai_controller/controller)
var/mob/living/living_pawn = controller.pawn
var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET]
var/best_force = controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND]
if(!target)
finish_action(controller, FALSE)
return
if(target.anchored) //Can't pick it up, so stop trying.
finish_action(controller, FALSE)
return
// Strong weapon
else if(target.force > best_force)
living_pawn.put_in_hands(target)
controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND] = target.force
finish_action(controller, TRUE)
return
else if(target.slot_flags) //Clothing == top priority
living_pawn.dropItemToGround(target, TRUE)
living_pawn.update_icons()
if(!living_pawn.equip_to_appropriate_slot(target))
finish_action(controller, FALSE)
return //Already wearing something, in the future this should probably replace the current item but the code didn't actually do that, and I dont want to support it right now.
finish_action(controller, TRUE)
return
// EVERYTHING ELSE
else if(living_pawn.get_empty_held_index_for_side(LEFT_HANDS) || living_pawn.get_empty_held_index_for_side(RIGHT_HANDS))
living_pawn.put_in_hands(target)
finish_action(controller, TRUE)
return
finish_action(controller, FALSE)
/datum/ai_behavior/monkey_equip/ground
required_distance = 0
/datum/ai_behavior/monkey_equip/ground/perform(delta_time, datum/ai_controller/controller)
equip_item(controller)
/datum/ai_behavior/monkey_equip/pickpocket
/datum/ai_behavior/monkey_equip/pickpocket/perform(delta_time, datum/ai_controller/controller)
if(controller.blackboard[BB_MONKEY_PICKPOCKETING]) //We are pickpocketing, don't do ANYTHING!!!!
return
INVOKE_ASYNC(src, .proc/attempt_pickpocket, controller)
/datum/ai_behavior/monkey_equip/pickpocket/proc/attempt_pickpocket(datum/ai_controller/controller)
var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET]
var/mob/living/victim = target.loc
var/mob/living/living_pawn = controller.pawn
victim.visible_message("<span class='warning'>[living_pawn] starts trying to take [target] from [controller.current_movement_target]!</span>", "<span class='danger'>[living_pawn] tries to take [target]!</span>")
controller.blackboard[BB_MONKEY_PICKPOCKETING] = TRUE
var/success = FALSE
if(do_mob(living_pawn, victim, MONKEY_ITEM_SNATCH_DELAY) && target)
for(var/obj/item/I in victim.held_items)
if(I == target)
victim.visible_message("<span class='danger'>[living_pawn] snatches [target] from [victim].</span>", "<span class='userdanger'>[living_pawn] snatched [target]!</span>")
if(victim.temporarilyRemoveItemFromInventory(target))
if(!QDELETED(target) && !equip_item(controller))
target.forceMove(living_pawn.drop_location())
success = TRUE
break
else
victim.visible_message("<span class='danger'>[living_pawn] tried to snatch [target] from [victim], but failed!</span>", "<span class='userdanger'>[living_pawn] tried to grab [target]!</span>")
finish_action(controller, success) //We either fucked up or got the item.
/datum/ai_behavior/monkey_equip/pickpocket/finish_action(datum/ai_controller/controller, success)
. = ..()
controller.blackboard[BB_MONKEY_PICKPOCKETING] = FALSE
controller.blackboard[BB_MONKEY_PICKUPTARGET] = null
/datum/ai_behavior/monkey_flee
/datum/ai_behavior/monkey_flee/perform(delta_time, datum/ai_controller/controller)
. = ..()
var/mob/living/living_pawn = controller.pawn
if(living_pawn.health >= MONKEY_FLEE_HEALTH)
finish_action(controller, TRUE) //we're back in bussiness
var/mob/living/target = null
// flee from anyone who attacked us and we didn't beat down
for(var/mob/living/L in view(living_pawn, MONKEY_FLEE_VISION))
if(controller.blackboard[BB_MONKEY_ENEMIES][L] && L.stat == CONSCIOUS)
target = L
break
if(target)
walk_away(living_pawn, target, MONKEY_ENEMY_VISION, 5)
else
finish_action(controller, TRUE)
/datum/ai_behavior/monkey_attack_mob
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM //performs to increase frustration
/datum/ai_behavior/monkey_attack_mob/perform(delta_time, datum/ai_controller/controller)
. = ..()
var/mob/living/target = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]
var/mob/living/living_pawn = controller.pawn
if(!target || target.stat != CONSCIOUS)
finish_action(controller, TRUE) //Target == owned
if(living_pawn.Adjacent(target) && isturf(target.loc) && !IS_DEAD_OR_INCAP(living_pawn)) // if right next to perp
// check if target has a weapon
var/obj/item/W
for(var/obj/item/I in target.held_items)
if(!(I.item_flags & ABSTRACT))
W = I
break
// if the target has a weapon, chance to disarm them
if(W && DT_PROB(MONKEY_ATTACK_DISARM_PROB, delta_time))
living_pawn.a_intent = INTENT_DISARM
monkey_attack(controller, target, delta_time)
else
living_pawn.a_intent = INTENT_HARM
monkey_attack(controller, target, delta_time)
/datum/ai_behavior/monkey_attack_mob/finish_action(datum/ai_controller/controller, succeeded)
. = ..()
controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = null
/// attack using a held weapon otherwise bite the enemy, then if we are angry there is a chance we might calm down a little
/datum/ai_behavior/monkey_attack_mob/proc/monkey_attack(datum/ai_controller/controller, mob/living/target, delta_time)
var/mob/living/living_pawn = controller.pawn
if(living_pawn.next_move > world.time)
return
living_pawn.changeNext_move(CLICK_CD_MELEE) //We play fair
var/obj/item/weapon = locate(/obj/item) in living_pawn.held_items
living_pawn.face_atom(target)
// attack with weapon if we have one
if(weapon)
weapon.melee_attack_chain(living_pawn, target)
else
target.attack_paw(living_pawn)
// no de-aggro
if(controller.blackboard[BB_MONKEY_AGRESSIVE])
return
if(DT_PROB(MONKEY_HATRED_REDUCTION_PROB, delta_time))
controller.blackboard[BB_MONKEY_ENEMIES][target]--
// if we are not angry at our target, go back to idle
if(controller.blackboard[BB_MONKEY_ENEMIES][target] <= 0)
var/list/enemies = controller.blackboard[BB_MONKEY_ENEMIES]
enemies.Remove(target)
if(controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] == target)
finish_action(controller, TRUE)
/datum/ai_behavior/disposal_mob
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM //performs to increase frustration
/datum/ai_behavior/disposal_mob/finish_action(datum/ai_controller/controller, succeeded)
. = ..()
controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = null //Reset attack target
controller.blackboard[BB_MONKEY_DISPOSING] = FALSE //No longer disposing
controller.blackboard[BB_MONKEY_TARGET_DISPOSAL] = null //No target disposal
/datum/ai_behavior/disposal_mob/perform(delta_time, datum/ai_controller/controller)
. = ..()
if(controller.blackboard[BB_MONKEY_DISPOSING]) //We are disposing, don't do ANYTHING!!!!
return
var/mob/living/target = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]
var/mob/living/living_pawn = controller.pawn
controller.current_movement_target = target
if(target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(target.pulledby, /datum/ai_controller/monkey)) //Dont steal from my fellow monkeys.
if(living_pawn.Adjacent(target) && isturf(target.loc))
living_pawn.a_intent = INTENT_GRAB
target.grabbedby(living_pawn)
return //Do the rest next turn
var/obj/machinery/disposal/disposal = controller.blackboard[BB_MONKEY_TARGET_DISPOSAL]
controller.current_movement_target = disposal
if(living_pawn.Adjacent(disposal))
INVOKE_ASYNC(src, .proc/try_disposal_mob, controller) //put him in!
else //This means we might be getting pissed!
return
/datum/ai_behavior/disposal_mob/proc/try_disposal_mob(datum/ai_controller/controller)
var/mob/living/living_pawn = controller.pawn
var/mob/living/target = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]
var/obj/machinery/disposal/disposal = controller.blackboard[BB_MONKEY_TARGET_DISPOSAL]
controller.blackboard[BB_MONKEY_DISPOSING] = TRUE
if(target && disposal?.stuff_mob_in(target, living_pawn))
disposal.flush()
finish_action(controller, TRUE)
/datum/ai_behavior/recruit_monkeys/perform(delta_time, datum/ai_controller/controller)
. = ..()
controller.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] = world.time + MONKEY_RECRUIT_COOLDOWN
var/mob/living/living_pawn = controller.pawn
for(var/mob/living/L in view(living_pawn, MONKEY_ENEMY_VISION))
if(!HAS_AI_CONTROLLER_TYPE(L, /datum/ai_controller/monkey))
continue
if(!DT_PROB(MONKEY_RECRUIT_PROB, delta_time))
continue
var/datum/ai_controller/monkey/monkey_ai = L.ai_controller
var/atom/your_enemy = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]
var/list/enemies = L.ai_controller.blackboard[BB_MONKEY_ENEMIES]
enemies[your_enemy] = MONKEY_RECRUIT_HATED_AMOUNT
monkey_ai.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] = world.time + MONKEY_RECRUIT_COOLDOWN
finish_action(controller, TRUE)
+213
View File
@@ -0,0 +1,213 @@
/*
AI controllers are a datumized form of AI that simulates the input a player would otherwise give to a mob. What this means is that these datums
have ways of interacting with a specific mob and control it.
*/
///OOK OOK OOK
/datum/ai_controller/monkey
blackboard = list(BB_MONKEY_AGRESSIVE = FALSE,\
BB_MONKEY_BEST_FORCE_FOUND = 0,\
BB_MONKEY_ENEMIES = list(),\
BB_MONKEY_BLACKLISTITEMS = list(),\
BB_MONKEY_PICKUPTARGET = null,\
BB_MONKEY_PICKPOCKETING = FALSE,
BB_MONKEY_DISPOSING = FALSE,
BB_MONKEY_TARGET_DISPOSAL = null,
BB_MONKEY_CURRENT_ATTACK_TARGET = null,
BB_MONKEY_CURRENT_ATTACK_TARGET)
/datum/ai_controller/monkey/angry
/datum/ai_controller/monkey/angry/TryPossessPawn(atom/new_pawn)
. = ..()
if(. & AI_CONTROLLER_INCOMPATIBLE)
return
blackboard[BB_MONKEY_AGRESSIVE] = TRUE //Angry cunt
/datum/ai_controller/monkey/TryPossessPawn(atom/new_pawn)
if(!isliving(new_pawn))
return AI_CONTROLLER_INCOMPATIBLE
RegisterSignal(new_pawn, COMSIG_PARENT_ATTACKBY, .proc/on_attackby)
RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand)
RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_paw)
RegisterSignal(new_pawn, COMSIG_ATOM_BULLET_ACT, .proc/on_bullet_act)
RegisterSignal(new_pawn, COMSIG_ATOM_HITBY, .proc/on_hitby)
RegisterSignal(new_pawn, COMSIG_MOVABLE_CROSSED, .proc/on_Crossed)
RegisterSignal(new_pawn, COMSIG_LIVING_START_PULL, .proc/on_startpulling)
RegisterSignal(new_pawn, COMSIG_LIVING_TRY_SYRINGE, .proc/on_try_syringe)
RegisterSignal(new_pawn, COMSIG_ATOM_HULK_ATTACK, .proc/on_attack_hulk)
RegisterSignal(new_pawn, COMSIG_CARBON_CUFF_ATTEMPTED, .proc/on_attempt_cuff)
return ..() //Run parent at end
/datum/ai_controller/monkey/UnpossessPawn()
UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACK_PAW, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_HITBY, COMSIG_MOVABLE_CROSSED, COMSIG_LIVING_START_PULL,\
COMSIG_LIVING_TRY_SYRINGE, COMSIG_ATOM_HULK_ATTACK, COMSIG_CARBON_CUFF_ATTEMPTED))
return ..() //Run parent at end
/datum/ai_controller/monkey/able_to_run()
var/mob/living/living_pawn = pawn
if(IS_DEAD_OR_INCAP(living_pawn))
return FALSE
return ..()
/datum/ai_controller/monkey/SelectBehaviors(delta_time)
current_behaviors = list()
var/mob/living/living_pawn = pawn
if(SHOULD_RESIST(living_pawn) && DT_PROB(MONKEY_RESIST_PROB, delta_time))
current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/resist) //BRO IM ON FUCKING FIRE BRO
return //IM NOT DOING ANYTHING ELSE BUT EXTUINGISH MYSELF, GOOD GOD HAVE MERCY.
var/list/enemies = blackboard[BB_MONKEY_ENEMIES]
if(HAS_TRAIT(pawn, TRAIT_PACIFISM)) //Not a pacifist? lets try some combat behavior.
return
if(length(enemies) || blackboard[BB_MONKEY_AGRESSIVE]) //We have enemies or are pissed
var/mob/living/selected_enemy
for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, living_pawn))
if(possible_enemy == living_pawn || (!enemies[possible_enemy] && (!blackboard[BB_MONKEY_AGRESSIVE] || HAS_AI_CONTROLLER_TYPE(possible_enemy, /datum/ai_controller/monkey)))) //Are they an enemy? (And do we even care?)
continue
selected_enemy = possible_enemy
break
if(selected_enemy)
if(!selected_enemy.stat) //He's up, get him!
if(living_pawn.health < MONKEY_FLEE_HEALTH) //Time to skeddadle
blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy
current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_flee)
return //I'm running fuck you guys
if(TryFindWeapon()) //Getting a weapon is higher priority if im not fleeing.
return
blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy
current_movement_target = selected_enemy
if(blackboard[BB_MONKEY_RECRUIT_COOLDOWN] < world.time)
current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/recruit_monkeys)
current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/battle_screech/monkey)
current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_attack_mob)
return //Focus on this
else //He's down, can we disposal him?
var/obj/machinery/disposal/bodyDisposal = locate(/obj/machinery/disposal/) in view(MONKEY_ENEMY_VISION, living_pawn)
if(bodyDisposal)
blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] = selected_enemy
blackboard[BB_MONKEY_TARGET_DISPOSAL] = bodyDisposal
current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/disposal_mob)
return
return //Too busy fighting to steal atm.
else if(DT_PROB(MONKEY_SHENANIGAN_PROB, delta_time))
if(TryFindWeapon()) //Found a better weapon, let's grab it first.
return
///re-used behavior pattern by monkeys for finding a weapon
/datum/ai_controller/monkey/proc/TryFindWeapon()
var/mob/living/living_pawn = pawn
if(!locate(/obj/item) in living_pawn.held_items)
blackboard[BB_MONKEY_BEST_FORCE_FOUND] = 0
var/obj/item/W = locate(/obj/item) in oview(2, living_pawn)
if(W && !blackboard[BB_MONKEY_BLACKLISTITEMS][W] && W.force > blackboard[BB_MONKEY_BEST_FORCE_FOUND])
blackboard[BB_MONKEY_PICKUPTARGET] = W
current_movement_target = W
current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_equip/ground)
return TRUE
else
var/mob/living/carbon/human/H = locate(/mob/living/carbon/human/) in oview(2,living_pawn)
if(H)
W = pick(H.held_items)
if(W && !blackboard[BB_MONKEY_BLACKLISTITEMS][W] && W.force > blackboard[BB_MONKEY_BEST_FORCE_FOUND])
blackboard[BB_MONKEY_PICKUPTARGET] = W
current_movement_target = W
current_behaviors += GET_AI_BEHAVIOR(/datum/ai_behavior/monkey_equip/pickpocket)
return TRUE
//When idle just kinda fuck around.
/datum/ai_controller/monkey/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)
step(living_pawn, pick(GLOB.cardinals))
else if(DT_PROB(5, delta_time))
INVOKE_ASYNC(living_pawn, /mob.proc/emote, pick("screech"))
else if(DT_PROB(1, delta_time))
INVOKE_ASYNC(living_pawn, /mob.proc/emote, pick("scratch","jump","roll","tail"))
///Reactive events to being hit
/datum/ai_controller/monkey/proc/retaliate(mob/living/L)
var/list/enemies = blackboard[BB_MONKEY_ENEMIES]
enemies[L] += MONKEY_HATRED_AMOUNT
/datum/ai_controller/monkey/proc/on_attackby(datum/source, obj/item/I, mob/user)
SIGNAL_HANDLER
if(I.force && I.damtype != STAMINA)
retaliate(user)
/datum/ai_controller/monkey/proc/on_attack_hand(datum/source, mob/living/L)
SIGNAL_HANDLER
if(L.a_intent == INTENT_HARM && prob(MONKEY_RETALIATE_HARM_PROB))
retaliate(L)
else if(L.a_intent == INTENT_DISARM && prob(MONKEY_RETALIATE_DISARM_PROB))
retaliate(L)
/datum/ai_controller/monkey/proc/on_attack_paw(datum/source, mob/living/L)
SIGNAL_HANDLER
if(L.a_intent == INTENT_HARM && prob(MONKEY_RETALIATE_HARM_PROB))
retaliate(L)
else if(L.a_intent == INTENT_DISARM && prob(MONKEY_RETALIATE_DISARM_PROB))
retaliate(L)
/datum/ai_controller/monkey/proc/on_bullet_act(datum/source, obj/projectile/Proj)
SIGNAL_HANDLER
var/mob/living/living_pawn = pawn
if(istype(Proj , /obj/projectile/beam)||istype(Proj, /obj/projectile/bullet))
if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE))
if(!Proj.nodamage && Proj.damage < living_pawn.health && isliving(Proj.firer))
retaliate(Proj.firer)
/datum/ai_controller/monkey/proc/on_hitby(datum/source, atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum)
SIGNAL_HANDLER
if(istype(AM, /obj/item))
var/mob/living/living_pawn = pawn
var/obj/item/I = AM
if(I.throwforce < living_pawn.health && ishuman(I.thrownby))
var/mob/living/carbon/human/H = I.thrownby
retaliate(H)
/datum/ai_controller/monkey/proc/on_Crossed(datum/source, atom/movable/AM)
SIGNAL_HANDLER
var/mob/living/living_pawn = pawn
if(!IS_DEAD_OR_INCAP(living_pawn) && ismob(AM))
var/mob/living/in_the_way_mob = AM
in_the_way_mob.knockOver(living_pawn)
return
/datum/ai_controller/monkey/proc/on_startpulling(datum/source, atom/movable/puller, state, force)
SIGNAL_HANDLER
var/mob/living/living_pawn = pawn
if(!IS_DEAD_OR_INCAP(living_pawn) && prob(MONKEY_PULL_AGGRO_PROB)) // nuh uh you don't pull me!
retaliate(living_pawn.pulledby)
return TRUE
/datum/ai_controller/monkey/proc/on_try_syringe(datum/source, mob/user)
SIGNAL_HANDLER
// chance of monkey retaliation
if(prob(MONKEY_SYRINGE_RETALIATION_PROB))
retaliate(user)
/datum/ai_controller/monkey/proc/on_attack_hulk(datum/source, mob/user)
SIGNAL_HANDLER
retaliate(user)
/datum/ai_controller/monkey/proc/on_attempt_cuff(datum/source, mob/user)
SIGNAL_HANDLER
// chance of monkey retaliation
if(prob(MONKEY_CUFF_RETALIATION_PROB))
retaliate(user)
+2 -2
View File
@@ -180,11 +180,11 @@
/datum/mutation/human/race/on_acquiring(mob/living/carbon/human/owner)
if(..())
return
. = owner.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE)
. = owner.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE | TR_KEEPAI)
/datum/mutation/human/race/on_losing(mob/living/carbon/monkey/owner)
if(owner && istype(owner) && owner.stat != DEAD && (owner.dna.mutations.Remove(src)))
. = owner.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE)
. = owner.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE | TR_KEEPAI)
/datum/mutation/human/glow
name = "Glowy"
+27
View File
@@ -146,6 +146,8 @@
var/list/canSmoothWith = null
///Reference to atom being orbited
var/atom/orbit_target
///AI controller that controls this atom. type on init, then turned into an instance during runtime
var/datum/ai_controller/ai_controller
/**
* Called when an atom is created in byond (built in engine proc)
@@ -236,6 +238,7 @@
set_custom_materials(custom_materials)
ComponentInitialize()
InitializeAIController()
return INITIALIZE_HINT_NORMAL
@@ -281,6 +284,7 @@
LAZYCLEARLIST(overlays)
QDEL_NULL(light)
QDEL_NULL(ai_controller)
if(smoothing_flags & SMOOTH_QUEUED)
SSicon_smooth.remove_from_queues(src)
@@ -714,6 +718,7 @@
* throw lots of items around - singularity being a notable example)
*/
/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
SEND_SIGNAL(src, COMSIG_ATOM_HITBY, AM, skipcatch, hitpush, blocked, throwingdatum)
if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
addtimer(CALLBACK(src, .proc/hitby_react, AM), 2)
@@ -1097,6 +1102,7 @@
VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse")
VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion")
VV_DROPDOWN_OPTION(VV_HK_RADIATE, "Radiate")
VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller")
/atom/vv_do_topic(list/href_list)
. = ..()
@@ -1141,6 +1147,16 @@
var/strength = input(usr, "Choose the radiation strength.", "Choose the strength.") as num|null
if(!isnull(strength))
AddComponent(/datum/component/radioactive, strength, src)
if(href_list[VV_HK_ADD_AI])
if(!check_rights(R_VAREDIT))
return
var/result = input(usr, "Choose the AI controller to apply to this atom WARNING: Not all AI works on all atoms.", "AI controller") as null|anything in subtypesof(/datum/ai_controller)
if(!result)
return
ai_controller = new result(src)
if(href_list[VV_HK_MODIFY_TRANSFORM] && check_rights(R_VAREDIT))
var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate")
var/matrix/M = transform
@@ -1687,3 +1703,14 @@
var/atom/atom_orbiter = o
output += atom_orbiter.get_all_orbiters(processed, source = FALSE)
return output
/**
* Instantiates the AI controller of this atom. Override this if you want to assign variables first.
*
* This will work fine without manually passing arguments.
+*/
/atom/proc/InitializeAIController()
if(ai_controller)
ai_controller = new ai_controller(src)
+1 -1
View File
@@ -746,7 +746,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
. = ""
/obj/item/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
return
return SEND_SIGNAL(src, COMSIG_ATOM_HITBY, AM, skipcatch, hitpush, blocked, throwingdatum)
/obj/item/attack_hulk(mob/living/carbon/human/user)
return FALSE
+2 -6
View File
@@ -46,17 +46,13 @@
if(!istype(C))
return
SEND_SIGNAL(C, COMSIG_CARBON_CUFF_ATTEMPTED, user)
if(iscarbon(user) && (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)))
to_chat(user, "<span class='warning'>Uh... how do those things work?!</span>")
apply_cuffs(user,user)
return
// chance of monkey retaliation
if(ismonkey(C) && prob(MONKEY_CUFF_RETALIATION_PROB))
var/mob/living/carbon/monkey/M
M = C
M.retaliate(user)
if(!C.handcuffed)
if(C.canBeHandcuffed())
C.visible_message("<span class='danger'>[user] is trying to put [src.name] on [C]!</span>", \
@@ -107,7 +107,7 @@
C.real_name = NewDNA.real_name
NewDNA.transfer_identity(C)
if(ismonkey(C))
C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG)
C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG | TR_KEEPAI)
C.updateappearance(mutcolor_update=1)
+1 -1
View File
@@ -457,7 +457,7 @@
if(prob(10))
switch(rand(1,4))
if(1) //blood rage
magnification.aggressive = TRUE
magnification.ai_controller.blackboard[BB_MONKEY_AGRESSIVE] = TRUE
if(2) //brain death
magnification.apply_damage(500,BRAIN,BODY_ZONE_HEAD,FALSE,FALSE,FALSE)
if(3) //primal gene (gorilla)
@@ -142,6 +142,8 @@
//ATTACK HAND IGNORING PARENT RETURN VALUE
/mob/living/carbon/attack_hand(mob/living/carbon/human/user)
if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user) & COMPONENT_CANCEL_ATTACK_CHAIN)
. = TRUE
for(var/thing in diseases)
var/datum/disease/D = thing
if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN)
+15 -1
View File
@@ -77,7 +77,21 @@
key = "screech"
key_third_person = "screeches"
message = "screeches."
mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien)
mob_type_allowed_typecache = list(/mob/living/carbon/monkey)
emote_type = EMOTE_AUDIBLE
/datum/emote/living/carbon/screech/get_sound(mob/living/user)
return pick('sound/creatures/monkey/monkey_screech_1.ogg',
'sound/creatures/monkey/monkey_screech_2.ogg',
'sound/creatures/monkey/monkey_screech_3.ogg',
'sound/creatures/monkey/monkey_screech_4.ogg',
'sound/creatures/monkey/monkey_screech_5.ogg',
'sound/creatures/monkey/monkey_screech_6.ogg',
'sound/creatures/monkey/monkey_screech_7.ogg')
/datum/emote/living/carbon/screech/roar
key = "roar"
key_third_person = "roars"
message = "roars."
/datum/emote/living/carbon/sign
key = "sign"
@@ -6,6 +6,7 @@
var/t_him = p_them()
var/t_has = p_have()
var/t_is = p_are()
var/t_es = p_es()
var/obscure_name
if(isliving(user))
@@ -354,6 +355,8 @@
if(HAS_TRAIT(src, TRAIT_DUMB))
msg += "[t_He] [t_has] a stupid expression on [t_his] face.\n"
if(getorgan(/obj/item/organ/brain))
if(ai_controller?.ai_status == AI_STATUS_ON)
msg += "<span class='deadsay'>[t_He] do[t_es]n't appear to be [t_him]self.</span>\n"
if(!key)
msg += "<span class='deadsay'>[t_He] [t_is] totally catatonic. The stresses of life in deep-space must have been too much for [t_him]. Any recovery is unlikely.</span>\n"
else if(!client)
@@ -1168,6 +1168,9 @@
return FALSE
return ..()
/mob/living/carbon/human/monkeybrain
ai_controller = /datum/ai_controller/monkey
/mob/living/carbon/human/species
var/race = null
@@ -1,426 +0,0 @@
#define MAX_RANGE_FIND 32
/mob/living/carbon/monkey
var/aggressive=0 // set to 1 using VV for an angry monkey
var/frustration=0
var/pickupTimer=0
var/list/enemies = list()
var/mob/living/target
var/obj/item/pickupTarget
var/mode = MONKEY_IDLE
var/list/myPath = list()
var/list/blacklistItems = list()
var/maxStepsTick = 6
var/best_force = 0
var/martial_art = new/datum/martial_art
var/resisting = FALSE
var/pickpocketing = FALSE
var/disposing_body = FALSE
var/obj/machinery/disposal/bodyDisposal = null
var/next_battle_screech = 0
var/battle_screech_cooldown = 50
/mob/living/carbon/monkey/proc/IsStandingStill()
return resisting || pickpocketing || disposing_body
// blocks
// taken from /mob/living/carbon/human/interactive/
/mob/living/carbon/monkey/proc/walk2derpless(target)
if(!target || IsStandingStill())
return FALSE
if(myPath.len <= 0)
myPath = get_path_to(src, get_turf(target), /turf/proc/Distance, MAX_RANGE_FIND + 1, 250,1)
if(myPath)
if(myPath.len > 0)
for(var/i = 0; i < maxStepsTick; ++i)
if(!IsDeadOrIncap())
if(myPath.len >= 1)
walk_to(src,myPath[1],0,5)
myPath -= myPath[1]
return TRUE
// failed to path correctly so just try to head straight for a bit
walk_to(src,get_turf(target),0,5)
sleep(1)
walk_to(src,0)
return FALSE
// taken from /mob/living/carbon/human/interactive/
/mob/living/carbon/monkey/proc/IsDeadOrIncap()
return HAS_TRAIT(src, TRAIT_INCAPACITATED) || HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)
/mob/living/carbon/monkey/proc/battle_screech()
if(next_battle_screech < world.time)
emote(pick("roar","screech"))
for(var/mob/living/carbon/monkey/M in view(7,src))
M.next_battle_screech = world.time + battle_screech_cooldown
/mob/living/carbon/monkey/proc/equip_item(obj/item/I)
if(I.loc == src)
return TRUE
if(I.anchored)
blacklistItems[I] ++
return FALSE
// WEAPONS
if(istype(I, /obj/item))
var/obj/item/W = I
if(W.force >= best_force)
put_in_hands(W)
best_force = W.force
return TRUE
// CLOTHING
else if(istype(I, /obj/item/clothing))
var/obj/item/clothing/C = I
monkeyDrop(C)
addtimer(CALLBACK(src, .proc/pickup_and_wear, C), 5)
return TRUE
// EVERYTHING ELSE
else
if(!get_item_for_held_index(1) || !get_item_for_held_index(2))
put_in_hands(I)
return TRUE
blacklistItems[I] ++
return FALSE
/mob/living/carbon/monkey/proc/pickup_and_wear(obj/item/clothing/C)
if(!equip_to_appropriate_slot(C))
monkeyDrop(get_item_by_slot(C)) // remove the existing item if worn
addtimer(CALLBACK(src, .proc/equip_to_appropriate_slot, C), 5)
/mob/living/carbon/monkey/resist_restraints()
var/obj/item/I = null
if(handcuffed)
I = handcuffed
else if(legcuffed)
I = legcuffed
if(I)
changeNext_move(CLICK_CD_BREAKOUT)
last_special = world.time + CLICK_CD_BREAKOUT
cuff_resist(I)
/mob/living/carbon/monkey/proc/should_target(mob/living/L)
if(HAS_TRAIT(src, TRAIT_PACIFISM))
return FALSE
if(enemies[L])
return TRUE
// target non-monkey mobs when aggressive, with a small probability of monkey v monkey
if(aggressive && (!istype(L, /mob/living/carbon/monkey/) || prob(MONKEY_AGGRESSIVE_MVM_PROB)))
return TRUE
return FALSE
/mob/living/carbon/monkey/proc/handle_combat()
if(pickupTarget)
if(IsDeadOrIncap() || blacklistItems[pickupTarget] || HAS_TRAIT(pickupTarget, TRAIT_NODROP))
pickupTarget = null
else
pickupTimer++
if(pickupTimer >= 4)
blacklistItems[pickupTarget] ++
pickupTarget = null
pickupTimer = 0
else
INVOKE_ASYNC(src, .proc/walk2derpless, pickupTarget.loc)
if(Adjacent(pickupTarget) || Adjacent(pickupTarget.loc)) // next to target
drop_all_held_items() // who cares about these items, i want that one!
if(isturf(pickupTarget.loc)) // on floor
equip_item(pickupTarget)
pickupTarget = null
pickupTimer = 0
else if(ismob(pickupTarget.loc)) // in someones hand
var/mob/M = pickupTarget.loc
if(!pickpocketing)
pickpocketing = TRUE
M.visible_message("<span class='warning'>[src] starts trying to take [pickupTarget] from [M]!</span>", "<span class='danger'>[src] tries to take [pickupTarget]!</span>")
INVOKE_ASYNC(src, .proc/pickpocket, M)
return TRUE
switch(mode)
if(MONKEY_IDLE) // idle
if(enemies.len)
var/list/around = view(src, MONKEY_ENEMY_VISION) // scan for enemies
for(var/mob/living/L in around)
if( should_target(L) )
if(L.stat == CONSCIOUS)
battle_screech()
retaliate(L)
return TRUE
else
bodyDisposal = locate(/obj/machinery/disposal/) in around
if(bodyDisposal)
target = L
mode = MONKEY_DISPOSE
return TRUE
// pickup any nearby objects
if(!pickupTarget)
var/obj/item/I = locate(/obj/item/) in oview(2,src)
if(I && !blacklistItems[I])
pickupTarget = I
else
var/mob/living/carbon/human/H = locate(/mob/living/carbon/human/) in oview(2,src)
if(H)
pickupTarget = pick(H.held_items)
if(MONKEY_HUNT) // hunting for attacker
if(health < MONKEY_FLEE_HEALTH)
mode = MONKEY_FLEE
return TRUE
if(target != null)
INVOKE_ASYNC(src, .proc/walk2derpless, target)
// pickup any nearby weapon
if(!pickupTarget && prob(MONKEY_WEAPON_PROB))
var/obj/item/W = locate(/obj/item/) in oview(2,src)
if(!locate(/obj/item) in held_items)
best_force = 0
if(W && !blacklistItems[W] && W.force > best_force)
pickupTarget = W
// recruit other monkies
var/list/around = view(src, MONKEY_ENEMY_VISION)
for(var/mob/living/carbon/monkey/M in around)
if(M.mode == MONKEY_IDLE && prob(MONKEY_RECRUIT_PROB))
M.battle_screech()
M.target = target
M.mode = MONKEY_HUNT
// switch targets
for(var/mob/living/L in around)
if(L != target && should_target(L) && L.stat == CONSCIOUS && prob(MONKEY_SWITCH_TARGET_PROB))
target = L
return TRUE
// if can't reach target for long enough, go idle
if(frustration >= MONKEY_HUNT_FRUSTRATION_LIMIT)
back_to_idle()
return TRUE
if(target && target.stat == CONSCIOUS) // make sure target exists
if(Adjacent(target) && isturf(target.loc) && !IsDeadOrIncap()) // if right next to perp
// check if target has a weapon
var/obj/item/W
for(var/obj/item/I in target.held_items)
if(!(I.item_flags & ABSTRACT))
W = I
break
// if the target has a weapon, chance to disarm them
if(W && prob(MONKEY_ATTACK_DISARM_PROB))
pickupTarget = W
a_intent = INTENT_DISARM
monkey_attack(target)
else
a_intent = INTENT_HARM
monkey_attack(target)
return TRUE
else // not next to perp
var/turf/olddist = get_dist(src, target)
if((get_dist(src, target)) >= (olddist))
frustration++
else
frustration = 0
else
back_to_idle()
if(MONKEY_FLEE)
var/list/around = view(src, MONKEY_FLEE_VISION)
target = null
// flee from anyone who attacked us and we didn't beat down
for(var/mob/living/L in around)
if( enemies[L] && L.stat == CONSCIOUS )
target = L
if(target != null)
walk_away(src, target, MONKEY_ENEMY_VISION, 5)
else
back_to_idle()
return TRUE
if(MONKEY_DISPOSE)
// if can't dispose of body go back to idle
if(!target || !bodyDisposal || frustration >= MONKEY_DISPOSE_FRUSTRATION_LIMIT)
back_to_idle()
return TRUE
if(target.pulledby != src && !istype(target.pulledby, /mob/living/carbon/monkey/))
INVOKE_ASYNC(src, .proc/walk2derpless, target.loc)
if(Adjacent(target) && isturf(target.loc))
a_intent = INTENT_GRAB
target.grabbedby(src)
else
var/turf/olddist = get_dist(src, target)
if((get_dist(src, target)) >= (olddist))
frustration++
else
frustration = 0
else if(!disposing_body)
INVOKE_ASYNC(src, .proc/walk2derpless, bodyDisposal.loc)
if(Adjacent(bodyDisposal))
disposing_body = TRUE
addtimer(CALLBACK(src, .proc/stuff_mob_in), 5)
else
var/turf/olddist = get_dist(src, bodyDisposal)
if((get_dist(src, bodyDisposal)) >= (olddist))
frustration++
else
frustration = 0
return TRUE
return IsStandingStill()
/mob/living/carbon/monkey/proc/pickpocket(mob/M)
if(do_mob(src, M, MONKEY_ITEM_SNATCH_DELAY) && pickupTarget)
for(var/obj/item/I in M.held_items)
if(I == pickupTarget)
M.visible_message("<span class='danger'>[src] snatches [pickupTarget] from [M].</span>", "<span class='userdanger'>[src] snatched [pickupTarget]!</span>")
if(M.temporarilyRemoveItemFromInventory(pickupTarget))
if(!QDELETED(pickupTarget) && !equip_item(pickupTarget))
pickupTarget.forceMove(drop_location())
else
M.visible_message("<span class='danger'>[src] tried to snatch [pickupTarget] from [M], but failed!</span>", "<span class='userdanger'>[src] tried to grab [pickupTarget]!</span>")
pickpocketing = FALSE
pickupTarget = null
pickupTimer = 0
/mob/living/carbon/monkey/proc/stuff_mob_in()
if(bodyDisposal && target && Adjacent(bodyDisposal))
bodyDisposal.stuff_mob_in(target, src)
disposing_body = FALSE
back_to_idle()
/mob/living/carbon/monkey/proc/back_to_idle()
if(pulling)
stop_pulling()
mode = MONKEY_IDLE
target = null
a_intent = INTENT_HELP
frustration = 0
walk_to(src,0)
// attack using a held weapon otherwise bite the enemy, then if we are angry there is a chance we might calm down a little
/mob/living/carbon/monkey/proc/monkey_attack(mob/living/L)
var/obj/item/Weapon = locate(/obj/item) in held_items
// attack with weapon if we have one
if(Weapon)
Weapon.melee_attack_chain(src, L)
else
L.attack_paw(src)
// no de-aggro
if(aggressive)
return
// if we arn't enemies, we were likely recruited to attack this target, jobs done if we calm down so go back to idle
if(!enemies[L])
if( target == L && prob(MONKEY_HATRED_REDUCTION_PROB) )
back_to_idle()
return // already de-aggroed
if(prob(MONKEY_HATRED_REDUCTION_PROB))
enemies[L] --
// if we are not angry at our target, go back to idle
if(enemies[L] <= 0)
enemies.Remove(L)
if( target == L )
back_to_idle()
// get angry are a mob
/mob/living/carbon/monkey/proc/retaliate(mob/living/L)
mode = MONKEY_HUNT
target = L
if(L != src)
enemies[L] += MONKEY_HATRED_AMOUNT
if(a_intent != INTENT_HARM)
battle_screech()
a_intent = INTENT_HARM
/mob/living/carbon/monkey/attack_hand(mob/living/L)
if(L.a_intent == INTENT_HARM && prob(MONKEY_RETALIATE_HARM_PROB))
retaliate(L)
else if(L.a_intent == INTENT_DISARM && prob(MONKEY_RETALIATE_DISARM_PROB))
retaliate(L)
return ..()
/mob/living/carbon/monkey/attack_paw(mob/living/L)
if(L.a_intent == INTENT_HARM && prob(MONKEY_RETALIATE_HARM_PROB))
retaliate(L)
else if(L.a_intent == INTENT_DISARM && prob(MONKEY_RETALIATE_DISARM_PROB))
retaliate(L)
return ..()
/mob/living/carbon/monkey/attackby(obj/item/W, mob/user, params)
..()
if((W.force) && (!target) && (W.damtype != STAMINA) )
retaliate(user)
/mob/living/carbon/monkey/bullet_act(obj/projectile/Proj)
if(istype(Proj , /obj/projectile/beam)||istype(Proj, /obj/projectile/bullet))
if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE))
if(!Proj.nodamage && Proj.damage < src.health && isliving(Proj.firer))
retaliate(Proj.firer)
. = ..()
/mob/living/carbon/monkey/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum)
if(istype(AM, /obj/item))
var/obj/item/I = AM
if(I.throwforce < src.health && I.thrownby && ishuman(I.thrownby))
var/mob/living/carbon/human/H = I.thrownby
retaliate(H)
..()
/mob/living/carbon/monkey/Crossed(atom/movable/AM)
if(!IsDeadOrIncap() && ismob(AM) && target)
var/mob/living/carbon/monkey/M = AM
if(!istype(M) || !M)
return
knockOver(M)
return
..()
/mob/living/carbon/monkey/proc/monkeyDrop(obj/item/A)
if(A)
dropItemToGround(A, TRUE)
update_icons()
/mob/living/carbon/monkey/grabbedby(mob/living/carbon/user)
. = ..()
if(!IsDeadOrIncap() && pulledby && (mode != MONKEY_IDLE || prob(MONKEY_PULL_AGGRO_PROB))) // nuh uh you don't pull me!
if(Adjacent(pulledby))
a_intent = INTENT_DISARM
monkey_attack(pulledby)
retaliate(pulledby)
return TRUE
#undef MAX_RANGE_FIND
@@ -2,30 +2,6 @@
/mob/living/carbon/monkey
/mob/living/carbon/monkey/Life()
if (notransform)
return
if(..() && !IS_IN_STASIS(src))
if(!client)
if(stat == CONSCIOUS)
if(on_fire || buckled || HAS_TRAIT(src, TRAIT_RESTRAINED) || (pulledby && pulledby.grab_state > GRAB_PASSIVE))
if(!resisting && prob(MONKEY_RESIST_PROB))
resisting = TRUE
walk_to(src,0)
resist()
else if(resisting)
resisting = FALSE
else if((mode == MONKEY_IDLE && !pickupTarget && !prob(MONKEY_SHENANIGAN_PROB)) || !handle_combat())
if(prob(25) && (mobility_flags & MOBILITY_MOVE) && isturf(loc) && !pulledby)
step(src, pick(GLOB.cardinals))
else if(prob(1))
emote(pick("scratch","jump","roll","tail"))
else
walk_to(src,0)
/mob/living/carbon/monkey/handle_mutations_and_radiation()
if(radiation)
if(radiation > RAD_MOB_KNOCKDOWN && prob(RAD_MOB_KNOCKDOWN_PROB))
@@ -21,6 +21,8 @@
/obj/item/bodypart/l_leg/monkey,
)
hud_type = /datum/hud/monkey
ai_controller = /datum/ai_controller/monkey
faction = list("neutral", "monkey")
/mob/living/carbon/monkey/Initialize(mapload, cubespawned=FALSE, mob/spawner)
add_verb(src, /mob/living/proc/mob_sleep)
@@ -164,10 +166,10 @@
return 1
/mob/living/carbon/monkey/angry
aggressive = TRUE
/mob/living/carbon/monkey/angry/Initialize()
. = ..()
ai_controller.blackboard[BB_MONKEY_AGRESSIVE] = TRUE
if(prob(10))
var/obj/item/clothing/head/helmet/justice/escape/helmet = new(src)
equip_to_slot_or_del(helmet,ITEM_SLOT_HEAD)
@@ -159,7 +159,6 @@
"<span class='userdanger'>[user] [hulk_verb]ed [src]!</span>", "<span class='hear'>You hear a sickening sound of flesh hitting flesh!</span>", null, user)
to_chat(user, "<span class='danger'>You [hulk_verb] [src]!</span>")
apply_damage(15, BRUTE, wound_bonus=10)
retaliate(user)
/mob/living/carbon/monkey/acid_act(acidpwr, acid_volume, bodyzone_hit)
. = TRUE
@@ -18,7 +18,7 @@
else if(prob(10))
name = pick(list("Professor Bobo", "Deempisi's Revenge", "Furious George", "King Louie", "Dr. Zaius", "Jimmy Rustles", "Dinner", "Lanky"))
if(name == "Furious George")
aggressive = TRUE // Furious George is PISSED
ai_controller = /datum/ai_controller/monkey/angry //hes always mad
. = ..()
//These have to be after the parent new to ensure that the monkey
+8 -2
View File
@@ -1,6 +1,6 @@
#define TRANSFORMATION_DURATION 22
/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG | TR_KEEPSTAMINADAMAGE))
/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG | TR_KEEPSTAMINADAMAGE | TR_KEEPAI))
if (notransform || transformation_timer)
return
@@ -151,6 +151,9 @@
if(tr_flags & TR_KEEPSTAMINADAMAGE)
O.adjustStaminaLoss(getStaminaLoss())
if(tr_flags & TR_KEEPAI)
ai_controller.PossessPawn(O)
if (tr_flags & TR_DEFAULTMSG)
to_chat(O, "<B>You are now a monkey.</B>")
@@ -167,7 +170,7 @@
////////////////////////// Humanize //////////////////////////////
//Could probably be merged with monkeyize but other transformations got their own procs, too
/mob/living/carbon/proc/humanize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG))
/mob/living/carbon/proc/humanize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG | TR_KEEPAI))
if (notransform || transformation_timer)
return
@@ -324,6 +327,9 @@
changeling.purchasedpowers -= HF
changeling.regain_powers()
if(tr_flags & TR_KEEPAI)
ai_controller.PossessPawn(O)
O.a_intent = INTENT_HELP
if (tr_flags & TR_DEFAULTMSG)
to_chat(O, "<B>You are now a human.</B>")
@@ -70,10 +70,7 @@
return
// chance of monkey retaliation
if(ismonkey(target) && prob(MONKEY_SYRINGE_RETALIATION_PROB))
var/mob/living/carbon/monkey/M
M = target
M.retaliate(user)
SEND_SIGNAL(target, COMSIG_LIVING_TRY_SYRINGE, user)
switch(mode)
if(SYRINGE_DRAW)
+2
View File
@@ -142,10 +142,12 @@
target.forceMove(src)
if(user == target)
user.visible_message("<span class='warning'>[user] climbs into [src].</span>", "<span class='notice'>You climb into [src].</span>")
. = TRUE
else
target.visible_message("<span class='danger'>[user] places [target] in [src].</span>", "<span class='userdanger'>[user] places you in [src].</span>")
log_combat(user, target, "stuffed", addition="into [src]")
target.LAssailant = user
. = TRUE
update_icon()
/obj/machinery/disposal/relaymove(mob/living/user, direction)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7 -1
View File
@@ -26,6 +26,7 @@
#include "code\__DEFINES\acid.dm"
#include "code\__DEFINES\actionspeed_modification.dm"
#include "code\__DEFINES\admin.dm"
#include "code\__DEFINES\ai.dm"
#include "code\__DEFINES\antagonists.dm"
#include "code\__DEFINES\atmospherics.dm"
#include "code\__DEFINES\atom_hud.dm"
@@ -331,6 +332,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_controllers.dm"
#include "code\controllers\subsystem\processing\fastprocess.dm"
#include "code\controllers\subsystem\processing\fields.dm"
#include "code\controllers\subsystem\processing\fluids.dm"
@@ -393,6 +395,11 @@
#include "code\datums\achievements\misc_achievements.dm"
#include "code\datums\achievements\skill_achievements.dm"
#include "code\datums\actions\beam_rifle.dm"
#include "code\datums\ai\_ai_behavior.dm"
#include "code\datums\ai\_ai_controller.dm"
#include "code\datums\ai\generic_actions.dm"
#include "code\datums\ai\monkey\monkey_controller.dm"
#include "code\datums\ai\monkey\monkey_behaviors.dm"
#include "code\datums\atmosphere\_atmosphere.dm"
#include "code\datums\atmosphere\planetary.dm"
#include "code\datums\brain_damage\brain_trauma.dm"
@@ -2372,7 +2379,6 @@
#include "code\modules\mob\living\carbon\human\species_types\synths.dm"
#include "code\modules\mob\living\carbon\human\species_types\vampire.dm"
#include "code\modules\mob\living\carbon\human\species_types\zombies.dm"
#include "code\modules\mob\living\carbon\monkey\combat.dm"
#include "code\modules\mob\living\carbon\monkey\death.dm"
#include "code\modules\mob\living\carbon\monkey\inventory.dm"
#include "code\modules\mob\living\carbon\monkey\life.dm"