mirror of
https://github.com/vgstation-coders/vgstation13.git
synced 2025-12-10 10:21:11 +00:00
Basic Human AI (#15172)
* Adds a basic NPC AI for humans, capable of feeding itself when hungry and clothing itself. * Does more stuff. * Adds some more stuff. * Forgot this.
This commit is contained in:
29
code/__DEFINES/component_desires.dm
Normal file
29
code/__DEFINES/component_desires.dm
Normal file
@@ -0,0 +1,29 @@
|
||||
//Defines for the personal desires that can be held by human AI
|
||||
#define DESIRE_FOOD "desire_food"
|
||||
#define DESIRE_UNDERCLOTHING "desire_underclothing"
|
||||
#define DESIRE_SHOES "desire_shoes"
|
||||
#define DESIRE_BACK "desire_back"
|
||||
#define DESIRE_GLOVES "desire_gloves"
|
||||
#define DESIRE_HAT "desire_hat"
|
||||
#define DESIRE_BELT "desire_belt"
|
||||
#define DESIRE_EXOSUIT "desire_exosuit"
|
||||
#define DESIRE_GLASSES "desire_glasses"
|
||||
#define DESIRE_MASK "desire_mask"
|
||||
#define DESIRE_ID "desire_id"
|
||||
#define DESIRE_HAVE_WEAPON "desire_have_weapon"
|
||||
#define DESIRE_CONFLICT "desire_conflict"
|
||||
|
||||
var/list/desire_ranks = list(
|
||||
DESIRE_HAVE_WEAPON,
|
||||
DESIRE_CONFLICT,
|
||||
DESIRE_FOOD,
|
||||
DESIRE_UNDERCLOTHING,
|
||||
DESIRE_SHOES,
|
||||
DESIRE_BACK,
|
||||
DESIRE_GLOVES,
|
||||
DESIRE_HAT,
|
||||
DESIRE_BELT,
|
||||
DESIRE_EXOSUIT,
|
||||
DESIRE_GLASSES,
|
||||
DESIRE_MASK,
|
||||
DESIRE_ID)
|
||||
@@ -31,6 +31,11 @@
|
||||
*/
|
||||
#define COMSIG_MOVE "move"
|
||||
|
||||
/** Sent when a mob wants to take a single step.
|
||||
* @param dir integer: NORTH/SOUTH/WEST/EAST/etc to move in that direction.
|
||||
*/
|
||||
#define COMSIG_STEP "step"
|
||||
|
||||
|
||||
/** BLURB
|
||||
* @param temp decimal: Adds value to body temperature
|
||||
@@ -70,3 +75,35 @@
|
||||
* @param component /datum/component: Component being removed.
|
||||
*/
|
||||
#define COMSIG_COMPONENT_REMOVING "component removing"
|
||||
|
||||
/** Sent when a mob wants to drop the item in its active hand. No arguments.
|
||||
*/
|
||||
#define COMSIG_DROP "drop"
|
||||
|
||||
/** Sent when a mob wants to click on something.
|
||||
* @param target /atom: The thing to be clicked on.
|
||||
*/
|
||||
#define COMSIG_CLICKON "clickon"
|
||||
|
||||
/** Sent when a mob wants to activate a hand which is holding a specific item.
|
||||
* @param target /atom: The item in question.
|
||||
*/
|
||||
#define COMSIG_ACTVHANDBYITEM "actvhandbyitem"
|
||||
|
||||
/** Sent when a mob wants to activate an empty hand. No arguments.
|
||||
*/
|
||||
#define COMSIG_ACTVEMPTYHAND "actvemptyhand"
|
||||
|
||||
/** Sent when a mob wants to throw the item in its active hand at something.
|
||||
* @param target /atom: The atom at which to throw.
|
||||
*/
|
||||
#define COMSIG_THROWAT "throwat"
|
||||
|
||||
/** Sent when a mob wants to call attack_self() on the item in its active hand. No arguments.
|
||||
*/
|
||||
#define COMSIG_ITMATKSELF "itmatkself"
|
||||
|
||||
/** Sent when a mob wants to quick-equip the item in its active hand. No arguments.
|
||||
*/
|
||||
#define COMSIG_EQUIPACTVHAND "equipactvhand"
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
return
|
||||
click_delayer.setDelay(1)
|
||||
|
||||
if(client.buildmode)
|
||||
if(client && client.buildmode)
|
||||
build_click(src, client.buildmode, params, A)
|
||||
return
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
var/mob/living/M=container.holder
|
||||
//testing("Got command: \[[message_type]\]: [json_encode(args)]")
|
||||
switch(message_type)
|
||||
if(COMSIG_CLICKON)
|
||||
var/atom/A = args["target"]
|
||||
M.ClickOn(A)
|
||||
|
||||
if(COMSIG_MOVE) // list("loc"=turf)
|
||||
// list("dir"=NORTH)
|
||||
if("loc" in args)
|
||||
@@ -16,6 +20,9 @@
|
||||
// walk(M, get_dir(src,M), MISSILE_SPEED)
|
||||
walk(M, args["dir"], walk_delay)
|
||||
|
||||
if(COMSIG_STEP)
|
||||
step(M, args["dir"], walk_delay)
|
||||
|
||||
if(COMSIG_ADJUST_BODYTEMP) // list("temp"=TEMP_IN_KELVIN)
|
||||
M.bodytemperature += args["temp"]
|
||||
|
||||
|
||||
210
code/modules/components/ai/human/brain.dm
Normal file
210
code/modules/components/ai/human/brain.dm
Normal file
@@ -0,0 +1,210 @@
|
||||
//Basic thought processes
|
||||
/datum/component/ai/human_brain
|
||||
var/life_tick=0
|
||||
var/wander = TRUE //Whether the mob will walk around searching for goals, or wait for them to become visible
|
||||
var/lastdir = null
|
||||
|
||||
var/list/desired_items = list() //Specific items
|
||||
var/list/personal_desires = list() //General desires for events/situations/types of items
|
||||
|
||||
var/list/friendly_factions = list()
|
||||
var/list/enemy_factions = list()
|
||||
var/list/enemy_types = list()
|
||||
var/list/enemy_species = list("Tajaran")
|
||||
var/list/friends = list()
|
||||
var/list/enemies = list()
|
||||
|
||||
var/datum/component/ai/target_holder/target_holder = null
|
||||
var/atom/current_target
|
||||
|
||||
/datum/component/ai/human_brain/RecieveSignal(var/message_type, var/list/args)
|
||||
switch(message_type)
|
||||
if(COMSIG_LIFE) // no arguments
|
||||
OnLife()
|
||||
|
||||
/datum/component/ai/human_brain/proc/OnLife()
|
||||
life_tick++
|
||||
if(!target_holder)
|
||||
target_holder = GetComponent(/datum/component/ai/target_holder)
|
||||
if(!controller)
|
||||
controller = GetComponent(/datum/component/controller)
|
||||
if(controller.getBusy())
|
||||
return
|
||||
if(!ishuman(container.holder))
|
||||
return
|
||||
var/mob/living/carbon/human/H = container.holder
|
||||
|
||||
if(H.stat != CONSCIOUS || !H.canmove || !isturf(H.loc))
|
||||
SendSignal(COMSIG_MOVE, list("dir" = 0))
|
||||
|
||||
current_target = target_holder.GetBestTarget(src, "target_evaluator")
|
||||
if(!isnull(current_target))
|
||||
personal_desires.Add(DESIRE_CONFLICT)
|
||||
SendSignal(COMSIG_TARGET, list("target"=current_target))
|
||||
if(IsBetterWeapon(H))
|
||||
personal_desires.Add(DESIRE_HAVE_WEAPON)
|
||||
if(IsBetterWeapon(H, H.contents))
|
||||
if(WieldBestWeapon(H))
|
||||
personal_desires.Remove(DESIRE_HAVE_WEAPON)
|
||||
|
||||
AssessNeeds(H)
|
||||
var/obj/item/I = AttainExternalItemGoal(H)
|
||||
if(I)
|
||||
if(H.Adjacent(I))
|
||||
AcquireItem(H, I)
|
||||
SendSignal(COMSIG_MOVE, list("dir" = 0))
|
||||
else
|
||||
if(H.stat == CONSCIOUS && H.canmove && isturf(H.loc))
|
||||
SendSignal(COMSIG_MOVE, list("loc" = get_turf(I)))
|
||||
return
|
||||
|
||||
if(!isnull(current_target))
|
||||
container.SendSignalToFirst(/datum/component/ai, COMSIG_ATTACKING, list("target"=current_target))
|
||||
var/turf/T = get_turf(current_target)
|
||||
if(T)
|
||||
if(H.stat == CONSCIOUS && H.canmove && isturf(H.loc))
|
||||
SendSignal(COMSIG_MOVE, list("loc" = T))
|
||||
return
|
||||
else
|
||||
personal_desires.Remove(DESIRE_CONFLICT)
|
||||
|
||||
if(wander && prob(70))
|
||||
var/dir = pick(NORTH,SOUTH,EAST,WEST)
|
||||
if(lastdir)
|
||||
var/roll = rand(1,100)
|
||||
if(roll <= 50)
|
||||
dir = lastdir
|
||||
else if(roll <= 90)
|
||||
dir = pick(turn(lastdir,90),turn(lastdir,270))
|
||||
else
|
||||
dir = turn(lastdir, 180)
|
||||
if(H.stat == CONSCIOUS && H.canmove && isturf(H.loc))
|
||||
SendSignal(COMSIG_STEP, list("dir" = dir))
|
||||
lastdir = dir
|
||||
|
||||
/datum/component/ai/human_brain/proc/AssessNeeds(mob/living/carbon/human/H)
|
||||
personal_desires = list()
|
||||
if(H.nutrition < 250)
|
||||
personal_desires.Add(DESIRE_FOOD)
|
||||
if(!H.get_item_by_slot(slot_w_uniform))
|
||||
personal_desires.Add(DESIRE_UNDERCLOTHING)
|
||||
if(!H.get_item_by_slot(slot_shoes))
|
||||
personal_desires.Add(DESIRE_SHOES)
|
||||
if(!H.get_item_by_slot(slot_back))
|
||||
personal_desires.Add(DESIRE_BACK)
|
||||
if(!H.get_item_by_slot(slot_gloves))
|
||||
personal_desires.Add(DESIRE_GLOVES)
|
||||
if(!H.get_item_by_slot(slot_head))
|
||||
personal_desires.Add(DESIRE_HAT)
|
||||
if(!H.get_item_by_slot(slot_belt))
|
||||
personal_desires.Add(DESIRE_BELT)
|
||||
if(!H.get_item_by_slot(slot_wear_suit))
|
||||
personal_desires.Add(DESIRE_EXOSUIT)
|
||||
if(!H.get_item_by_slot(slot_glasses))
|
||||
personal_desires.Add(DESIRE_GLASSES)
|
||||
if(!H.get_item_by_slot(slot_wear_mask))
|
||||
personal_desires.Add(DESIRE_MASK)
|
||||
if(!H.get_item_by_slot(slot_wear_id))
|
||||
personal_desires.Add(DESIRE_ID)
|
||||
|
||||
/datum/component/ai/human_brain/proc/AttainExternalItemGoal(mob/living/carbon/human/H)
|
||||
var/obj/item/goal = null
|
||||
processing_desires:
|
||||
for(var/D in desire_ranks)
|
||||
if(D in personal_desires)
|
||||
for(var/obj/item/I in view(H))
|
||||
if(I in H.contents)
|
||||
continue
|
||||
switch(D)
|
||||
if(DESIRE_HAVE_WEAPON)
|
||||
if((!goal && I.force > 2) || (goal && (I.force > goal.force || (I.force == goal.force && I.sharpness > goal.sharpness))))
|
||||
goal = I
|
||||
if(DESIRE_CONFLICT)
|
||||
break processing_desires
|
||||
if(DESIRE_FOOD)
|
||||
if(istype(I, /obj/item/weapon/reagent_containers/food/snacks))
|
||||
goal = I
|
||||
if(DESIRE_UNDERCLOTHING)
|
||||
if((I.slot_flags & SLOT_ICLOTHING) && I.mob_can_equip(H, slot_w_uniform))
|
||||
goal = I
|
||||
if(DESIRE_SHOES)
|
||||
if((I.slot_flags & SLOT_FEET) && I.mob_can_equip(H, slot_shoes))
|
||||
goal = I
|
||||
if(DESIRE_BACK)
|
||||
if((I.slot_flags & SLOT_BACK) && I.mob_can_equip(H, slot_back))
|
||||
goal = I
|
||||
if(DESIRE_GLOVES)
|
||||
if((I.slot_flags & SLOT_GLOVES) && I.mob_can_equip(H, slot_gloves))
|
||||
goal = I
|
||||
if(DESIRE_HAT)
|
||||
if((I.slot_flags & SLOT_HEAD) && I.mob_can_equip(H, slot_head))
|
||||
goal = I
|
||||
if(DESIRE_BELT)
|
||||
if((I.slot_flags & SLOT_BELT) && I.mob_can_equip(H, slot_belt))
|
||||
goal = I
|
||||
if(DESIRE_EXOSUIT)
|
||||
if((I.slot_flags & SLOT_OCLOTHING) && I.mob_can_equip(H, slot_wear_suit))
|
||||
goal = I
|
||||
if(DESIRE_GLASSES)
|
||||
if((I.slot_flags & SLOT_EYES) && I.mob_can_equip(H, slot_glasses))
|
||||
goal = I
|
||||
if(DESIRE_MASK)
|
||||
if((I.slot_flags & SLOT_MASK) && I.mob_can_equip(H, slot_wear_mask))
|
||||
goal = I
|
||||
if(DESIRE_ID)
|
||||
if((I.slot_flags & SLOT_ID) && I.mob_can_equip(H, slot_wear_id))
|
||||
goal = I
|
||||
if(goal)
|
||||
break
|
||||
return goal
|
||||
|
||||
/datum/component/ai/human_brain/proc/AcquireItem(mob/living/carbon/human/H, obj/item/I)
|
||||
SendSignal(COMSIG_ACTVEMPTYHAND, list())
|
||||
SendSignal(COMSIG_CLICKON, list("target" = I))
|
||||
if(istype(I, /obj/item/weapon/reagent_containers/food/snacks))
|
||||
ConsumeFood(H, I)
|
||||
else
|
||||
SendSignal(COMSIG_EQUIPACTVHAND, list())
|
||||
|
||||
/datum/component/ai/human_brain/proc/ConsumeFood(mob/living/carbon/human/H, obj/item/I)
|
||||
while(H.nutrition < 250 && !I.gcDestroyed)
|
||||
SendSignal(COMSIG_ITMATKSELF, list())
|
||||
sleep(1)
|
||||
SendSignal(COMSIG_DROP, list())
|
||||
|
||||
/datum/component/ai/human_brain/proc/target_evaluator(var/atom/target)
|
||||
return TRUE
|
||||
|
||||
/datum/component/ai/human_brain/proc/WieldBestWeapon(mob/living/carbon/human/H, var/list/excluded)
|
||||
SendSignal(COMSIG_ACTVEMPTYHAND, list())
|
||||
if(H.get_active_hand())
|
||||
SendSignal(COMSIG_DROP, list())
|
||||
if(H.get_active_hand())
|
||||
return 1
|
||||
if(!excluded)
|
||||
excluded = list()
|
||||
var/obj/item/current_candidate = null
|
||||
for(var/obj/item/I in H.contents)
|
||||
if(!current_candidate)
|
||||
if(I.force > 2)
|
||||
current_candidate = I
|
||||
else
|
||||
if(I.force > current_candidate.force || (I.force == current_candidate.force && I.sharpness > current_candidate.sharpness))
|
||||
current_candidate = I
|
||||
if(current_candidate)
|
||||
SendSignal(COMSIG_CLICKON, list("target" = current_candidate))
|
||||
if(current_candidate != H.get_active_hand())
|
||||
excluded.Add(current_candidate)
|
||||
.(H, excluded)
|
||||
else
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
|
||||
/datum/component/ai/human_brain/proc/IsBetterWeapon(mob/living/carbon/human/H, var/list/search_location)
|
||||
if(!search_location)
|
||||
search_location = view(H)
|
||||
var/obj/item/O = H.get_active_hand()
|
||||
for(var/obj/item/I in search_location)
|
||||
if((!O && I.force > 2) || (O && (I.force > O.force || (I.force == O.force && I.sharpness > O.sharpness))))
|
||||
return 1
|
||||
32
code/modules/components/ai/human/hand_usage.dm
Normal file
32
code/modules/components/ai/human/hand_usage.dm
Normal file
@@ -0,0 +1,32 @@
|
||||
/datum/component/ai/hand_control/RecieveSignal(var/message_type, var/list/args)
|
||||
if(iscarbon(container.holder))
|
||||
var/mob/living/carbon/M = container.holder
|
||||
//testing("Got command: \[[message_type]\]: [json_encode(args)]")
|
||||
switch(message_type)
|
||||
if(COMSIG_DROP) // list("pickup" = item)
|
||||
if(M.get_active_hand())
|
||||
M.drop_item()
|
||||
if(COMSIG_ACTVHANDBYITEM) // list("target" = item)
|
||||
var/obj/item/I = args["target"]
|
||||
for(var/j = 1 to M.held_items.len)
|
||||
if(M.held_items[j] == I)
|
||||
M.active_hand = j
|
||||
break
|
||||
if(COMSIG_ACTVEMPTYHAND)
|
||||
for(var/j = 1 to M.held_items.len)
|
||||
if(M.held_items[j] == null)
|
||||
M.active_hand = j
|
||||
break
|
||||
if(COMSIG_THROWAT) // list("target" = atom)
|
||||
var/atom/A = args["target"]
|
||||
M.throw_mode_on()
|
||||
M.ClickOn(A)
|
||||
M.throw_mode_off()
|
||||
if(COMSIG_ITMATKSELF)
|
||||
var/obj/item/I = M.get_active_hand()
|
||||
if(I)
|
||||
I.attack_self(M)
|
||||
if(COMSIG_EQUIPACTVHAND)
|
||||
var/obj/item/I = M.get_active_hand()
|
||||
if(I)
|
||||
M.equip_to_appropriate_slot(I)
|
||||
10
code/modules/components/ai/human/human_attack.dm
Normal file
10
code/modules/components/ai/human/human_attack.dm
Normal file
@@ -0,0 +1,10 @@
|
||||
/datum/component/ai/melee/attack_human
|
||||
// var/datum/component/ai/human_brain/B
|
||||
|
||||
/datum/component/ai/melee/attack_human/OnAttackingTarget(var/atom/target)
|
||||
if(..(target))
|
||||
var/mob/living/carbon/human/H = container.holder
|
||||
H.a_intent = I_HURT
|
||||
SendSignal(COMSIG_CLICKON, list("target" = target))
|
||||
return 1 // Accepted
|
||||
return 0 // Unaccepted
|
||||
21
code/modules/components/ai/human/human_target_finder.dm
Normal file
21
code/modules/components/ai/human/human_target_finder.dm
Normal file
@@ -0,0 +1,21 @@
|
||||
/datum/component/ai/target_finder/human
|
||||
range = 7
|
||||
var/datum/component/ai/human_brain/B
|
||||
|
||||
/datum/component/ai/target_finder/human/GetTargets()
|
||||
ASSERT(container.holder!=null)
|
||||
if(!B)
|
||||
B = GetComponent(/datum/component/ai/human_brain)
|
||||
var/list/o = list()
|
||||
for(var/mob/M in view(range, container.holder))
|
||||
if(is_type_in_list(M, exclude_types))
|
||||
continue
|
||||
if(M.stat)
|
||||
continue
|
||||
if((M in B.enemies) || (M.faction && M.faction in B.enemy_factions) || (M.type in B.enemy_types))
|
||||
o += M
|
||||
else if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
if(H.species && H.species.name in B.enemy_species)
|
||||
o += M
|
||||
return o
|
||||
@@ -59,6 +59,10 @@
|
||||
qdel(vessel)
|
||||
vessel = null
|
||||
|
||||
if(NPC_brain)
|
||||
qdel(NPC_brain)
|
||||
NPC_brain = null
|
||||
|
||||
..()
|
||||
|
||||
for(var/obj/abstract/Overlays/O in obj_overlays)
|
||||
|
||||
@@ -75,6 +75,10 @@
|
||||
h_style = "Bald"
|
||||
..(new_loc, "Slime")
|
||||
|
||||
/mob/living/carbon/human/NPC/New(var/new_loc, delay_ready_dna = 0)
|
||||
..(new_loc)
|
||||
initialize_basic_NPC_components()
|
||||
|
||||
/mob/living/carbon/human/frankenstein/New(var/new_loc, delay_ready_dna = 0) //Just fuck my shit up: the mob
|
||||
f_style = pick(facial_hair_styles_list)
|
||||
h_style = pick(hair_styles_list)
|
||||
@@ -1817,3 +1821,15 @@ mob/living/carbon/human/remove_internal_organ(var/mob/living/user, var/datum/org
|
||||
if(M_HULK in mutations)
|
||||
return image(icon = 'icons/mob/attackanims.dmi', icon_state = "hulk")
|
||||
else return image(icon = 'icons/mob/attackanims.dmi', icon_state = "default")
|
||||
|
||||
/mob/living/carbon/human/proc/initialize_barebones_NPC_components() //doesn't actually do anything, but contains tools needed for other types to do things
|
||||
NPC_brain = new (src)
|
||||
NPC_brain.AddComponent(/datum/component/controller/mob)
|
||||
NPC_brain.AddComponent(/datum/component/ai/hand_control)
|
||||
|
||||
/mob/living/carbon/human/proc/initialize_basic_NPC_components() //will wander around
|
||||
initialize_barebones_NPC_components()
|
||||
NPC_brain.AddComponent(/datum/component/ai/human_brain)
|
||||
NPC_brain.AddComponent(/datum/component/ai/target_finder/human)
|
||||
NPC_brain.AddComponent(/datum/component/ai/target_holder/prioritizing)
|
||||
NPC_brain.AddComponent(/datum/component/ai/melee/attack_human)
|
||||
|
||||
@@ -83,6 +83,8 @@
|
||||
|
||||
var/obj/item/weapon/organ/head/decapitated = null //to keep track of a decapitated head, for debug and soulstone purposes
|
||||
|
||||
var/datum/component_container/NPC_brain
|
||||
|
||||
fire_dmi = 'icons/mob/OnFire.dmi'
|
||||
fire_sprite = "Standing"
|
||||
plane = HUMAN_PLANE
|
||||
@@ -187,6 +187,8 @@ var/global/list/organ_damage_overlays = list(
|
||||
if(update_overlays)
|
||||
update_overlays = 0
|
||||
UpdateDamageIcon()
|
||||
if(NPC_brain)
|
||||
NPC_brain.SendSignal(COMSIG_LIFE,list())
|
||||
cycle++
|
||||
..()
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "code\hub.dm"
|
||||
#include "code\names.dm"
|
||||
#include "code\world.dm"
|
||||
#include "code\__DEFINES\component_desires.dm"
|
||||
#include "code\__DEFINES\component_signals.dm"
|
||||
#include "code\__DEFINES\disease2.dm"
|
||||
#include "code\__DEFINES\hydro.dm"
|
||||
@@ -1164,6 +1165,10 @@
|
||||
#include "code\modules\components\ai\hostile\melee\attack_animal.dm"
|
||||
#include "code\modules\components\ai\hostile\melee\inject_reagent.dm"
|
||||
#include "code\modules\components\ai\hostile\melee\melee.dm"
|
||||
#include "code\modules\components\ai\human\brain.dm"
|
||||
#include "code\modules\components\ai\human\hand_usage.dm"
|
||||
#include "code\modules\components\ai\human\human_attack.dm"
|
||||
#include "code\modules\components\ai\human\human_target_finder.dm"
|
||||
#include "code\modules\components\ai\mobs\component_mob.dm"
|
||||
#include "code\modules\components\ai\mobs\giant_spider.dm"
|
||||
#include "code\modules\components\ai\target_finders\simple_view.dm"
|
||||
|
||||
Reference in New Issue
Block a user