diff --git a/beestation.dme b/beestation.dme index 46a935d389a..2d00085e41f 100644 --- a/beestation.dme +++ b/beestation.dme @@ -23,6 +23,7 @@ #include "code\__DEFINES\_tick.dm" #include "code\__DEFINES\access.dm" #include "code\__DEFINES\admin.dm" +#include "code\__DEFINES\ai.dm" #include "code\__DEFINES\antagonists.dm" #include "code\__DEFINES\areas.dm" #include "code\__DEFINES\atmospherics.dm" @@ -309,6 +310,11 @@ #include "code\controllers\subsystem\vis_overlays.dm" #include "code\controllers\subsystem\vote.dm" #include "code\controllers\subsystem\weather.dm" +<<<<<<< HEAD +======= +#include "code\controllers\subsystem\processing\ai_controllers.dm" +#include "code\controllers\subsystem\processing\clock_component.dm" +>>>>>>> 23504ea087 ([PORT] Datumized AI + implemented for monkeys from TG (#4670)) #include "code\controllers\subsystem\processing\fastprocess.dm" #include "code\controllers\subsystem\processing\fields.dm" #include "code\controllers\subsystem\processing\fluids.dm" @@ -364,6 +370,11 @@ #include "code\datums\world_topic.dm" #include "code\datums\actions\beam_rifle.dm" #include "code\datums\actions\ninja.dm" +#include "code\datums\ai\_ai_behaviour.dm" +#include "code\datums\ai\_ai_controller.dm" +#include "code\datums\ai\generic_actions.dm" +#include "code\datums\ai\monkey\monkey_behaviours.dm" +#include "code\datums\ai\monkey\monkey_controller.dm" #include "code\datums\announcers\_announcer.dm" #include "code\datums\announcers\default_announcer.dm" #include "code\datums\announcers\intern_announcer.dm" @@ -2356,7 +2367,6 @@ #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\human\verbs\give.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" diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm index 52fa17f6e7f..02b2d312cdd 100644 --- a/code/__DEFINES/DNA.dm +++ b/code/__DEFINES/DNA.dm @@ -96,7 +96,7 @@ #define TR_KEEPSE (1<<5) // changelings shouldn't edit the DNA's SE when turning into a monkey #define TR_DEFAULTMSG (1<<6) #define TR_KEEPORGANS (1<<8) - +#define TR_KEEPAI (1<<9) #define CLONER_FRESH_CLONE "fresh" #define CLONER_MATURE_CLONE "mature" diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm new file mode 100644 index 00000000000..6b8d163a54e --- /dev/null +++ b/code/__DEFINES/ai.dm @@ -0,0 +1,34 @@ +#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 || source.restrained() || (source.pulledby && source.pulledby.grab_state > GRAB_PASSIVE)) + +///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" diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index cbdf074ccb3..3bc6656769c 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -97,6 +97,13 @@ #define COMSIG_ATOM_INTERCEPT_TELEPORT "intercept_teleport" //! called when teleporting into a protected turf: (channel, turf/origin) #define COMPONENT_BLOCK_TELEPORT 1 ///////////////// +/* Attack signals. They should share the returned flags, to standardize the attack chain. */ +/// tool_act -> pre_attack -> target.attackby (item.attack) -> afterattack + ///Ends the attack chain. If sent early might cause posterior attacks not to happen. + #define COMPONENT_CANCEL_ATTACK_CHAIN (1<<0) + ///Skips the specific attack step, continuing for the next one to happen. + #define COMPONENT_SKIP_ATTACK (1<<1) + #define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost" //! from base of atom/attack_ghost(): (mob/dead/observer/ghost) #define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand" //! from base of atom/attack_hand(): (mob/user) #define COMSIG_ATOM_ATTACK_PAW "atom_attack_paw" //! from base of atom/attack_paw(): (mob/user) @@ -106,6 +113,10 @@ #define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE "atom_init_success" +///from base of atom/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) +#define COMSIG_ATOM_HITBY "atom_hitby" + + ///////////////// #define COMSIG_AREA_POWER_CHANGE "area_power_change" //! from base of area/proc/power_change(): () @@ -162,6 +173,7 @@ // /mob signals #define COMSIG_MOB_LOGIN "mob_login" +#define COMSIG_MOB_LOGOUT "mob_logout" ///from base of /mob/Logout(): () #define COMSIG_MOB_DEATH "mob_death" //! from base of mob/death(): (gibbed) #define COMSIG_MOB_STATCHANGE "mob_statchange" //from base of mob/set_stat(): (new_stat) #define COMSIG_MOB_CLICKON "mob_clickon" //! from base of mob/clickon(): (atom/A, params) @@ -203,6 +215,8 @@ #define COMSIG_PROCESS_BORGCHARGER_OCCUPANT "living_charge" //! sent from borg recharge stations: (amount, repairs) #define COMSIG_BORG_SAFE_DECONSTRUCT "borg_safe_decon" //sent from borg mobs to itself, for tools to catch an upcoming destroy() due to safe decon (rather than detonation) #define COMSIG_MOB_CLIENT_LOGIN "comsig_mob_client_login" +#define COMSIG_LIVING_TRY_SYRINGE "living_try_syringe" ///From post-can inject check of syringe after attack (mob/user) +#define COMSIG_LIVING_START_PULL "living_start_pull" ///called on /living when someone starts pulling (atom/movable/pulled, state, force) //ALL OF THESE DO NOT TAKE INTO ACCOUNT WHETHER AMOUNT IS 0 OR LOWER AND ARE SENT REGARDLESS! #define COMSIG_LIVING_STATUS_STUN "living_stun" //! from base of mob/living/Stun() (amount, update, ignore) @@ -220,6 +234,7 @@ #define COMSIG_CARBON_LOSE_ORGAN "carbon_lose_organ" //from /item/organ/proc/Remove() (/obj/item/organ/) #define COMSIG_CARBON_EMBED_RIP "item_embed_start_rip" // defined twice, in carbon and human's topics, fired when interacting with a valid embedded_object to pull it out (mob/living/carbon/target, /obj/item, /obj/item/bodypart/L) #define COMSIG_CARBON_EMBED_REMOVAL "item_embed_remove_safe" // 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_CUFF_ATTEMPTED "carbon_attempt_cuff" ///Called when someone attempts to cuff a carbon // /mob/living/simple_animal/hostile signals #define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget" diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm index a67d5e13f22..b32d7b701d7 100644 --- a/code/__DEFINES/inventory.dm +++ b/code/__DEFINES/inventory.dm @@ -74,6 +74,10 @@ #define NECK (1<<11) #define FULL_BODY (~0) +//defines for the index of hands +#define LEFT_HANDS 1 +#define RIGHT_HANDS 2 + //flags for female outfits: How much the game can safely "take off" the uniform without it looking weird #define NO_FEMALE_UNIFORM 0 #define FEMALE_UNIFORM_FULL 1 diff --git a/code/__DEFINES/monkeys.dm b/code/__DEFINES/monkeys.dm index 187bd7842c4..48f79350cfa 100644 --- a/code/__DEFINES/monkeys.dm +++ b/code/__DEFINES/monkeys.dm @@ -1,37 +1,43 @@ //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 -#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 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_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 +/// 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 -// 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 +/// 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 -#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 diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index b26e7adce92..afc123d685d 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -113,6 +113,8 @@ #define INIT_ORDER_EVENTS 70 #define INIT_ORDER_JOBS 65 #define INIT_ORDER_QUIRKS 60 +#define INIT_ORDER_AI_MOVEMENT 56 //We need the movement setup +#define INIT_ORDER_AI_CONTROLLERS 55 //So the controller can get the ref #define INIT_ORDER_TICKER 55 #define INIT_ORDER_MAPPING 50 #define INIT_ORDER_NETWORKS 45 @@ -152,6 +154,7 @@ #define FIRE_PRIORITY_WET_FLOORS 20 #define FIRE_PRIORITY_AIR 20 #define FIRE_PRIORITY_NPC 20 +#define FIRE_PRIORITY_NPC_MOVEMENT 21 #define FIRE_PRIORITY_PROCESS 25 #define FIRE_PRIORITY_THROWING 25 #define FIRE_PRIORITY_SPACEDRIFT 30 diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 81992dbe983..87c434dbdfc 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -85,6 +85,7 @@ #define VV_HK_TRIGGER_EMP "empulse" #define VV_HK_TRIGGER_EXPLOSION "explode" #define VV_HK_AUTO_RENAME "auto_rename" +#define VV_HK_ADD_AI "add_ai" // /obj #define VV_HK_OSAY "osay" diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index ae08f496fd0..ec44dc6dae8 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -6,6 +6,14 @@ 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 diff --git a/code/controllers/subsystem/processing/ai_controllers.dm b/code/controllers/subsystem/processing/ai_controllers.dm new file mode 100644 index 00000000000..8a563bdc335 --- /dev/null +++ b/code/controllers/subsystem/processing/ai_controllers.dm @@ -0,0 +1,21 @@ +/// 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 + init_order = INIT_ORDER_AI_CONTROLLERS + 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 diff --git a/code/datums/ai/README.md b/code/datums/ai/README.md new file mode 100644 index 00000000000..f219b11bb24 --- /dev/null +++ b/code/datums/ai/README.md @@ -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. diff --git a/code/datums/ai/_ai_behaviour.dm b/code/datums/ai/_ai_behaviour.dm new file mode 100644 index 00000000000..3c5154f42a0 --- /dev/null +++ b/code/datums/ai/_ai_behaviour.dm @@ -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 diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm new file mode 100644 index 00000000000..55c74b2d4e0 --- /dev/null +++ b/code/datums/ai/_ai_controller.dm @@ -0,0 +1,138 @@ +/* +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) + + var/get_step = get_step(pawn, get_dir(pawn, current_movement_target)) + if(!is_type_in_typecache(get_step, 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) diff --git a/code/datums/ai/generic_actions.dm b/code/datums/ai/generic_actions.dm new file mode 100644 index 00000000000..3edab925ac0 --- /dev/null +++ b/code/datums/ai/generic_actions.dm @@ -0,0 +1,14 @@ +/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) diff --git a/code/datums/ai/monkey/monkey_behaviours.dm b/code/datums/ai/monkey/monkey_behaviours.dm new file mode 100644 index 00000000000..b2e4b94ca02 --- /dev/null +++ b/code/datums/ai/monkey/monkey_behaviours.dm @@ -0,0 +1,260 @@ +/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(!isturf(living_pawn.loc)) + finish_action(controller, FALSE) + return + + 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("[living_pawn] starts trying to take [target] from [controller.current_movement_target]!", "[living_pawn] tries to take [target]!") + + 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("[living_pawn] snatches [target] from [victim].", "[living_pawn] snatched [target]!") + if(victim.temporarilyRemoveItemFromInventory(target)) + if(!QDELETED(target) && !equip_item(controller)) + target.forceMove(living_pawn.drop_location()) + success = TRUE + break + else + victim.visible_message("[living_pawn] tried to snatch [target] from [victim], but failed!", "[living_pawn] tried to grab [target]!") + + 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) && !living_pawn.incapacitated()) // 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! + +/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) diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm new file mode 100644 index 00000000000..4bfaad51387 --- /dev/null +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -0,0 +1,211 @@ +/* +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(living_pawn.incapacitated()) + 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/item/projectile/Proj) + SIGNAL_HANDLER + var/mob/living/living_pawn = pawn + if(istype(Proj , /obj/item/projectile/beam)||istype(Proj, /obj/item/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) + var/mob/living/living_pawn = pawn + if(!living_pawn.incapacitated() && ismob(AM) && (!ismonkey(AM) || prob(15))) + 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(!living_pawn.incapacitated() && 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) diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index 3feab1892ab..4df21bf57a9 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -175,11 +175,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_KEEPSE) + . = owner.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | 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_KEEPSE) + . = owner.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSE | TR_KEEPAI) /datum/mutation/human/glow name = "Glowy" diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 770230b5064..a08a71dac24 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -92,6 +92,9 @@ ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy() var/list/targeted_by + ///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) * @@ -184,6 +187,7 @@ set_custom_materials(temp_list) ComponentInitialize() + InitializeAIController() return INITIALIZE_HINT_NORMAL @@ -238,6 +242,7 @@ targeted_by = null QDEL_NULL(light) + QDEL_NULL(ai_controller) return ..() @@ -585,6 +590,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) @@ -936,6 +942,7 @@ VV_DROPDOWN_OPTION(VV_HK_ADD_REAGENT, "Add Reagent") VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse") VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion") + VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller") /atom/vv_do_topic(list/href_list) . = ..() @@ -998,6 +1005,13 @@ var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text if(newname) vv_auto_rename(newname) + 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) /atom/vv_get_header() . = ..() @@ -1280,6 +1294,7 @@ /atom/proc/setClosed() return +<<<<<<< HEAD ///Proc for being washed by a shower /atom/proc/washed(var/atom/washer) . = SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) @@ -1293,3 +1308,13 @@ qdel(healthy_green_glow) return healthy_green_glow.strength -= max(0, (healthy_green_glow.strength - (RAD_BACKGROUND_RADIATION * 2)) * 0.2) +======= +/** +* 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) +>>>>>>> 23504ea087 ([PORT] Datumized AI + implemented for monkeys from TG (#4670)) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 320681b517a..fbc07f5b114 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -820,7 +820,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) . = "" /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 0 diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm index 9aa3ce1c8fa..a1284d176cc 100644 --- a/code/game/objects/items/handcuffs.dm +++ b/code/game/objects/items/handcuffs.dm @@ -44,17 +44,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, "Uh... how do those things work?!") 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.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()) C.visible_message("[user] is trying to put [src.name] on [C]!", \ diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm index cfa5017e489..df25b26ba05 100644 --- a/code/modules/antagonists/changeling/powers/tiny_prick.dm +++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm @@ -108,7 +108,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_DEFAULTMSG) + C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_DEFAULTMSG | TR_KEEPAI) C.updateappearance(mutcolor_update=1) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index c87803a230c..d81b8b47197 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -120,6 +120,9 @@ //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) diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm index 614510b611d..d5217a45615 100644 --- a/code/modules/mob/living/carbon/emote.dm +++ b/code/modules/mob/living/carbon/emote.dm @@ -65,7 +65,22 @@ 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" diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index b7b62dfb988..ada1b619f88 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -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)) @@ -114,10 +115,13 @@ if(hellbound) . += "[t_His] soul seems to have been ripped out of [t_his] body. Revival is impossible." . += "" - if(getorgan(/obj/item/organ/brain) && !key && !get_ghost(FALSE, TRUE)) - . += "[t_He] [t_is] limp and unresponsive; there are no signs of life and [t_his] soul has departed." - else - . += "[t_He] [t_is] limp and unresponsive; there are no signs of life." + if(getorgan(/obj/item/organ/brain)) + if(ai_controller?.ai_status == AI_STATUS_ON) + . += "[t_He] do[t_es]n't appear to be [t_him]self.\n" + else if(!key && !get_ghost(FALSE, TRUE)) + . += "[t_He] [t_is] limp and unresponsive; there are no signs of life and [t_his] soul has departed." + else + . += "[t_He] [t_is] limp and unresponsive; there are no signs of life." if(get_bodypart(BODY_ZONE_HEAD) && !getorgan(/obj/item/organ/brain)) . += "It appears that [t_his] brain is missing." @@ -287,6 +291,8 @@ if(InCritical()) msg += "[t_He] [t_is] barely conscious.\n" if(getorgan(/obj/item/organ/brain)) + if(ai_controller?.ai_status == AI_STATUS_ON) + msg += "[t_He] do[t_es]n't appear to be [t_him]self.\n" if(!key) msg += "[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.\n" else if(!client) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 430a8ca967b..4789b4bf10a 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1194,6 +1194,7 @@ visible_message("[src] lands elegantly on [p_their()] feet!", "You fall [levels] level[levels > 1 ? "s" : ""] into [T], perfecting the landing!") +<<<<<<< HEAD /mob/living/carbon/human/washed(var/atom/washer) . = ..() if(wear_suit) @@ -1211,6 +1212,10 @@ if(gloves && !(HIDEGLOVES in check_obscured_slots()) && gloves.washed(washer)) SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD) +======= +/mob/living/carbon/human/monkeybrain + ai_controller = /datum/ai_controller/monkey +>>>>>>> 23504ea087 ([PORT] Datumized AI + implemented for monkeys from TG (#4670)) /mob/living/carbon/human/species var/race = null diff --git a/code/modules/mob/living/carbon/monkey/life.dm b/code/modules/mob/living/carbon/monkey/life.dm index 689b18985c0..8f397963843 100644 --- a/code/modules/mob/living/carbon/monkey/life.dm +++ b/code/modules/mob/living/carbon/monkey/life.dm @@ -1,7 +1,6 @@ - - /mob/living/carbon/monkey +<<<<<<< HEAD /mob/living/carbon/monkey/Life() set invisibility = 0 @@ -28,6 +27,8 @@ else walk_to(src,0) +======= +>>>>>>> 23504ea087 ([PORT] Datumized AI + implemented for monkeys from TG (#4670)) /mob/living/carbon/monkey/handle_mutations_and_radiation() if(radiation) if(radiation > RAD_MOB_KNOCKDOWN && prob(RAD_MOB_KNOCKDOWN_PROB)) diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm index 6f3110240dc..cb5c6482d9e 100644 --- a/code/modules/mob/living/carbon/monkey/monkey.dm +++ b/code/modules/mob/living/carbon/monkey/monkey.dm @@ -18,6 +18,8 @@ /obj/item/bodypart/r_arm/monkey, /obj/item/bodypart/r_leg/monkey, /obj/item/bodypart/l_leg/monkey) hud_type = /datum/hud/monkey mobchatspan = "monkeyhive" + ai_controller = /datum/ai_controller/monkey + faction = list("neutral", "monkey") /mob/living/carbon/monkey/Initialize(mapload, cubespawned=FALSE, mob/spawner) add_verb(/mob/living/proc/mob_sleep) @@ -171,10 +173,10 @@ return TRUE /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) @@ -204,9 +206,9 @@ icon_state = null butcher_results = list(/obj/effect/spawner/lootdrop/teratoma/minor = 5, /obj/effect/spawner/lootdrop/teratoma/major = 1) type_of_meat = /obj/effect/spawner/lootdrop/teratoma/minor - aggressive = TRUE bodyparts = list(/obj/item/bodypart/chest/monkey/teratoma, /obj/item/bodypart/head/monkey/teratoma, /obj/item/bodypart/l_arm/monkey/teratoma, /obj/item/bodypart/r_arm/monkey/teratoma, /obj/item/bodypart/r_leg/monkey/teratoma, /obj/item/bodypart/l_leg/monkey/teratoma) + ai_controller = null /mob/living/carbon/monkey/tumor/Initialize() . = ..() diff --git a/code/modules/mob/living/carbon/monkey/punpun.dm b/code/modules/mob/living/carbon/monkey/punpun.dm index 1e3f0f2db77..5fa3091fb6d 100644 --- a/code/modules/mob/living/carbon/monkey/punpun.dm +++ b/code/modules/mob/living/carbon/monkey/punpun.dm @@ -16,7 +16,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 diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 5c4373a4d2a..62bac68b4ec 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -279,6 +279,9 @@ pulling = AM AM.pulledby = src + + SEND_SIGNAL(src, COMSIG_LIVING_START_PULL, AM, state, force) + if(!supress_message) var/sound_to_play = 'sound/weapons/thudswoosh.ogg' if(ishuman(src)) diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index 18dd11f1bb9..0d0623a19b0 100644 --- a/code/modules/mob/logout.dm +++ b/code/modules/mob/logout.dm @@ -1,4 +1,5 @@ /mob/Logout() + SEND_SIGNAL(src, COMSIG_MOB_LOGOUT) log_message("[key_name(src)] is no longer owning mob [src]([src.type])", LOG_OWNERSHIP) SStgui.on_logout(src) unset_machine() diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 50299c93c95..f56fa3b50d2 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -1,5 +1,12 @@ +<<<<<<< HEAD /mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG)) if (notransform) +======= +#define TRANSFORMATION_DURATION 22 + +/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG | TR_KEEPAI)) + if (notransform || transformation_timer) +>>>>>>> 23504ea087 ([PORT] Datumized AI + implemented for monkeys from TG (#4670)) return //Handle items on mob @@ -133,6 +140,13 @@ changeling.regain_powers() + //if we have an AI, transfer it; if we don't, make sure the new thing doesn't either + if(tr_flags & TR_KEEPAI) + if(ai_controller) + ai_controller.PossessPawn(O) + else if(O.ai_controller) + QDEL_NULL(O.ai_controller) + if (tr_flags & TR_DEFAULTMSG) to_chat(O, "You are now a monkey.") @@ -300,8 +314,13 @@ ////////////////////////// Humanize ////////////////////////////// //Could probably be merged with monkeyize but other transformations got their own procs, too +<<<<<<< HEAD /mob/living/carbon/proc/humanize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG)) if (notransform) +======= +/mob/living/carbon/proc/humanize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG | TR_KEEPAI)) + if (notransform || transformation_timer) +>>>>>>> 23504ea087 ([PORT] Datumized AI + implemented for monkeys from TG (#4670)) return //Handle items on mob @@ -445,6 +464,14 @@ changeling.purchasedpowers -= HF changeling.regain_powers() + //if we have an AI, transfer it; if we don't, make sure the new thing doesn't either + if(tr_flags & TR_KEEPAI) + if(ai_controller) + ai_controller.PossessPawn(O) + else if(O.ai_controller) + QDEL_NULL(O.ai_controller) + + O.a_intent = INTENT_HELP if (tr_flags & TR_DEFAULTMSG) to_chat(O, "You are now a human.") diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm index 3553c839f6e..2b391e16ed7 100644 --- a/code/modules/reagents/reagent_containers/syringes.dm +++ b/code/modules/reagents/reagent_containers/syringes.dm @@ -78,11 +78,8 @@ else if(!L.can_inject(user, TRUE)) 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) diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm index d276c339317..1ffe58025c4 100644 --- a/code/modules/recycling/disposal/bin.dm +++ b/code/modules/recycling/disposal/bin.dm @@ -126,6 +126,7 @@ add_fingerprint(user) if(user == target) user.visible_message("[user] starts climbing into [src].", "You start climbing into [src]...") + . = TRUE else target.visible_message("[user] starts putting [target] into [src].", "[user] starts putting you into [src]!") if(do_mob(user, target, 20)) @@ -134,10 +135,16 @@ target.forceMove(src) if(user == target) user.visible_message("[user] climbs into [src].", "You climb into [src].") + . = TRUE else target.visible_message("[user] has placed [target] in [src].", "[user] has placed you in [src].") log_combat(user, target, "stuffed", addition="into [src]") +<<<<<<< HEAD target.LAssailant = user +======= + target.LAssailant = WEAKREF(user) + . = TRUE +>>>>>>> 23504ea087 ([PORT] Datumized AI + implemented for monkeys from TG (#4670)) update_icon() /obj/machinery/disposal/relaymove(mob/user) diff --git a/sound/creatures/monkey/monkey_screech_1.ogg b/sound/creatures/monkey/monkey_screech_1.ogg new file mode 100644 index 00000000000..45f8db6ac6b Binary files /dev/null and b/sound/creatures/monkey/monkey_screech_1.ogg differ diff --git a/sound/creatures/monkey/monkey_screech_2.ogg b/sound/creatures/monkey/monkey_screech_2.ogg new file mode 100644 index 00000000000..5a5015916d4 Binary files /dev/null and b/sound/creatures/monkey/monkey_screech_2.ogg differ diff --git a/sound/creatures/monkey/monkey_screech_3.ogg b/sound/creatures/monkey/monkey_screech_3.ogg new file mode 100644 index 00000000000..4ba7b74d05f Binary files /dev/null and b/sound/creatures/monkey/monkey_screech_3.ogg differ diff --git a/sound/creatures/monkey/monkey_screech_4.ogg b/sound/creatures/monkey/monkey_screech_4.ogg new file mode 100644 index 00000000000..87e7e157e98 Binary files /dev/null and b/sound/creatures/monkey/monkey_screech_4.ogg differ diff --git a/sound/creatures/monkey/monkey_screech_5.ogg b/sound/creatures/monkey/monkey_screech_5.ogg new file mode 100644 index 00000000000..fd8ccef0f69 Binary files /dev/null and b/sound/creatures/monkey/monkey_screech_5.ogg differ diff --git a/sound/creatures/monkey/monkey_screech_6.ogg b/sound/creatures/monkey/monkey_screech_6.ogg new file mode 100644 index 00000000000..cd9eddf198a Binary files /dev/null and b/sound/creatures/monkey/monkey_screech_6.ogg differ diff --git a/sound/creatures/monkey/monkey_screech_7.ogg b/sound/creatures/monkey/monkey_screech_7.ogg new file mode 100644 index 00000000000..9cd09b17e45 Binary files /dev/null and b/sound/creatures/monkey/monkey_screech_7.ogg differ