mirror of
https://github.com/vgstation-coders/vgstation13.git
synced 2025-12-10 18:32:03 +00:00
Component Model + PoC Mob (#13866)
* Initial work on component mobs. * Revert simple_animal.dm * Fix movement. * Component signals are now #defines. * Fix magic number in atmos.dm * Added basic melee attacks. * Get rid of test spam
This commit is contained in:
72
code/__DEFINES/component_signals.dm
Normal file
72
code/__DEFINES/component_signals.dm
Normal file
@@ -0,0 +1,72 @@
|
||||
// Component Signal names.
|
||||
// Avoids any mishaps caused by typos.
|
||||
|
||||
/** Sent when a mob AI component wants to set new machine state.
|
||||
* @param state mixed: The new machine state (HOSTILE_STANCE_IDLE, etc)
|
||||
*/
|
||||
#define COMSIG_STATE "state"
|
||||
|
||||
/** Sent when we've been bumped.
|
||||
* @param movable /atom/movable: The bumping entity.
|
||||
*/
|
||||
#define COMSIG_BUMPED "bumped"
|
||||
|
||||
/** Sent when we've bumped someone else.
|
||||
* @param movable /atom/movable: The bumped entity.
|
||||
*/
|
||||
#define COMSIG_BUMP "bump"
|
||||
|
||||
/** Sent by mob Life() tick. No arguments.
|
||||
*/
|
||||
#define COMSIG_LIFE "life"
|
||||
|
||||
/** Sent when a mob AI component has identified a new target.
|
||||
* @param target /atom: The targetted entity.
|
||||
*/
|
||||
#define COMSIG_TARGET "target"
|
||||
|
||||
/** Sent when a mob wants to move or stop.
|
||||
* @param dir integer: 0 to stop, NORTH/SOUTH/WEST/EAST/etc to move in that direction.
|
||||
* @param loc /turf: Specify to move in the direction of that turf.
|
||||
*/
|
||||
#define COMSIG_MOVE "move"
|
||||
|
||||
|
||||
/** BLURB
|
||||
* @param temp decimal: Adds value to body temperature
|
||||
*/
|
||||
#define COMSIG_ADJUST_BODYTEMP "add body temp" // DONE, NEEDS IMPL
|
||||
|
||||
|
||||
/** BLURB
|
||||
* @param amount decimal: Adjust bruteloss by the given amount.
|
||||
*/
|
||||
#define COMSIG_ADJUST_BRUTE "adjust brute loss" // DONE, NEEDS IMPL
|
||||
|
||||
|
||||
/** BLURB
|
||||
* @param target /atom: The target being attacked.
|
||||
*/
|
||||
#define COMSIG_ATTACKING "attacking target" // DONE
|
||||
|
||||
|
||||
/** BLURB
|
||||
* @param state boolean: Busy if true.
|
||||
*/
|
||||
#define COMSIG_BUSY "busy" // DONE, NEEDS IMPL
|
||||
|
||||
|
||||
/** BLURB
|
||||
* @param temp decimal: Sets body temperature to provided value, in kelvin.
|
||||
*/
|
||||
#define COMSIG_SET_BODYTEMP "body temp" // DONE, NEEDS IMPL
|
||||
|
||||
/** Sent when a component is added to the container.
|
||||
* @param component /datum/component: Component being added.
|
||||
*/
|
||||
#define COMSIG_COMPONENT_ADDED "component added"
|
||||
|
||||
/** Sent when a component is being removed from the container.
|
||||
* @param component /datum/component: Component being removed.
|
||||
*/
|
||||
#define COMSIG_COMPONENT_REMOVING "component removing"
|
||||
@@ -39,3 +39,10 @@ var/cmp_field = "name"
|
||||
|
||||
/proc/cmp_subsystem_priority(datum/subsystem/a, datum/subsystem/b)
|
||||
return a.priority - b.priority
|
||||
|
||||
var/atom/cmp_dist_origin=null
|
||||
/proc/cmp_dist_asc(var/atom/a, var/atom/b)
|
||||
return get_dist_squared(cmp_dist_origin, a) - get_dist_squared(cmp_dist_origin, b)
|
||||
|
||||
/proc/cmp_dist_desc(var/atom/a, var/atom/b)
|
||||
return get_dist_squared(cmp_dist_origin, b) - get_dist_squared(cmp_dist_origin, a)
|
||||
|
||||
@@ -226,6 +226,16 @@ proc/arctan(x)
|
||||
#undef k1
|
||||
#undef k2
|
||||
|
||||
/**
|
||||
* Get Distance, Squared
|
||||
*
|
||||
* Because sqrt is slow, this returns the distance squared, which skips the sqrt step.
|
||||
*
|
||||
* Use to compare distances. Used in component mobs.
|
||||
*/
|
||||
/proc/get_dist_squared(var/atom/a, var/atom/b)
|
||||
return ((b.x-a.x)**2) + ((b.y-a.y)**2)
|
||||
|
||||
//Checks if something's a power of 2, to check bitflags.
|
||||
//Thanks to wwjnc for this.
|
||||
/proc/test_bitflag(var/bitflag)
|
||||
|
||||
13
code/modules/components/ai/ai_component.dm
Normal file
13
code/modules/components/ai/ai_component.dm
Normal file
@@ -0,0 +1,13 @@
|
||||
/datum/component/ai
|
||||
var/datum/component/controller/controller
|
||||
|
||||
var/state=0 // AI_STATE_* of the AI.
|
||||
|
||||
/datum/component/ai/RecieveSignal(var/message_type, var/list/args)
|
||||
switch(message_type)
|
||||
if(COMSIG_STATE) // list("name"="statename")
|
||||
state = args["name"]
|
||||
|
||||
/datum/component/ai/New(var/datum/component_container/CC)
|
||||
..(CC)
|
||||
controller=GetComponent(/datum/component/controller)
|
||||
97
code/modules/components/ai/atmos.dm
Normal file
97
code/modules/components/ai/atmos.dm
Normal file
@@ -0,0 +1,97 @@
|
||||
/datum/component/ai/atmos_checker
|
||||
//Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage
|
||||
var/min_oxy = 5
|
||||
var/max_oxy = 0 //Leaving something at 0 means it's off - has no maximum
|
||||
var/min_tox = 0
|
||||
var/max_tox = 1
|
||||
var/min_co2 = 0
|
||||
var/max_co2 = 5
|
||||
var/min_n2 = 0
|
||||
var/max_n2 = 0
|
||||
var/unsuitable_damage = 2 //This damage is taken when atmos doesn't fit all the requirements above
|
||||
|
||||
var/minbodytemp = 250
|
||||
var/maxbodytemp = 350
|
||||
var/heat_damage_per_tick = 3 //amount of damage applied if animal's body temperature is higher than maxbodytemp
|
||||
var/cold_damage_per_tick = 2 //same as heat_damage_per_tick, only if the bodytemperature it's lower than minbodytemp
|
||||
var/fire_alert = 0
|
||||
var/oxygen_alert = 0
|
||||
var/toxins_alert = 0
|
||||
|
||||
var/min_overheat_temp=40
|
||||
|
||||
/datum/component/ai/atmos_checker/RecieveSignal(var/message_type, var/list/args)
|
||||
switch(message_type)
|
||||
if("life")
|
||||
OnLife()
|
||||
else
|
||||
..(message_type, args)
|
||||
|
||||
/datum/component/ai/atmos_checker/proc/OnLife()
|
||||
if(!isliving(container.holder))
|
||||
return 1
|
||||
if(container.holder & INVULNERABLE)
|
||||
return 1
|
||||
|
||||
var/atmos_suitable = 1
|
||||
|
||||
var/atom/A = container.holder.loc
|
||||
|
||||
if(isturf(A))
|
||||
var/turf/T = A
|
||||
var/datum/gas_mixture/Environment = T.return_air()
|
||||
|
||||
if(Environment)
|
||||
if(abs(Environment.temperature - controller.getBodyTemperature()) > min_overheat_temp)
|
||||
SendSignal(COMSIG_ADJUST_BODYTEMP, list("temp"=((Environment.temperature - controller.getBodyTemperature()) / 5)))
|
||||
|
||||
if(min_oxy)
|
||||
if(Environment.oxygen < min_oxy)
|
||||
atmos_suitable = 0
|
||||
oxygen_alert = 1
|
||||
else
|
||||
oxygen_alert = 0
|
||||
|
||||
if(max_oxy)
|
||||
if(Environment.oxygen > max_oxy)
|
||||
atmos_suitable = 0
|
||||
|
||||
if(min_tox)
|
||||
if(Environment.toxins < min_tox)
|
||||
atmos_suitable = 0
|
||||
|
||||
if(max_tox)
|
||||
if(Environment.toxins > max_tox)
|
||||
atmos_suitable = 0
|
||||
toxins_alert = 1
|
||||
else
|
||||
toxins_alert = 0
|
||||
|
||||
if(min_n2)
|
||||
if(Environment.nitrogen < min_n2)
|
||||
atmos_suitable = 0
|
||||
|
||||
if(max_n2)
|
||||
if(Environment.nitrogen > max_n2)
|
||||
atmos_suitable = 0
|
||||
|
||||
if(min_co2)
|
||||
if(Environment.carbon_dioxide < min_co2)
|
||||
atmos_suitable = 0
|
||||
|
||||
if(max_co2)
|
||||
if(Environment.carbon_dioxide > max_co2)
|
||||
atmos_suitable = 0
|
||||
|
||||
//Atmos effect
|
||||
if(controller.getBodyTemperature() < minbodytemp)
|
||||
fire_alert = 2
|
||||
SendSignal(COMSIG_ADJUST_BRUTE, list("amount"=cold_damage_per_tick))
|
||||
else if(controller.getBodyTemperature() > maxbodytemp)
|
||||
fire_alert = 1
|
||||
SendSignal(COMSIG_ADJUST_BRUTE, list("amount"=heat_damage_per_tick))
|
||||
else
|
||||
fire_alert = 0
|
||||
|
||||
if(!atmos_suitable)
|
||||
SendSignal(COMSIG_ADJUST_BRUTE, list("amount"=unsuitable_damage))
|
||||
64
code/modules/components/ai/controller.dm
Normal file
64
code/modules/components/ai/controller.dm
Normal file
@@ -0,0 +1,64 @@
|
||||
/datum/component/controller
|
||||
var/atom/holder
|
||||
|
||||
var/_busy=FALSE
|
||||
var/atom/_target=null
|
||||
|
||||
var/_state=HOSTILE_STANCE_IDLE
|
||||
|
||||
/datum/component/controller/New(var/datum/component_container/container, var/atom/_holder)
|
||||
..(container)
|
||||
holder=_holder
|
||||
|
||||
// Called when we are bumped by another movable atom.
|
||||
/datum/component/controller/proc/OnBumped(var/atom/A)
|
||||
if(istype(A, /atom/movable))
|
||||
var/atom/movable/AM = A
|
||||
SendSignal(COMSIG_BUMPED, list("movable"=AM))
|
||||
|
||||
// Called when we bump another movable atom.
|
||||
/datum/component/controller/proc/OnBump(var/atom/A)
|
||||
if(istype(A, /atom/movable))
|
||||
var/atom/movable/AM = A
|
||||
SendSignal(COMSIG_BUMP, list("movable"=AM))
|
||||
|
||||
// Called when we receive the Life() tick from the MC/scheduler/whatever
|
||||
/datum/component/controller/proc/Life()
|
||||
SendSignal(COMSIG_LIFE, list())
|
||||
|
||||
//* Mob calls these to send signals to components. */
|
||||
/datum/component/controller/proc/AttackTarget(var/atom/A)
|
||||
container.SendSignalToFirst(/datum/component/ai, COMSIG_ATTACKING, list("target"=A))
|
||||
|
||||
/datum/component/controller/proc/setBusy(var/yes)
|
||||
_busy = yes
|
||||
SendSignal(COMSIG_BUSY, list("state"=_busy))
|
||||
|
||||
/datum/component/controller/proc/getBusy()
|
||||
return _busy
|
||||
|
||||
/datum/component/controller/proc/setTarget(var/atom/A)
|
||||
_target = A
|
||||
SendSignal(COMSIG_TARGET, list("target"=_target))
|
||||
|
||||
/datum/component/controller/proc/getTarget()
|
||||
return _target
|
||||
|
||||
/datum/component/controller/proc/setState(var/new_state)
|
||||
_state = new_state
|
||||
SendSignal(COMSIG_STATE, list("state"=_state))
|
||||
|
||||
/datum/component/controller/proc/getState()
|
||||
return _state
|
||||
|
||||
/datum/component/controller/proc/setBodyTemperature(var/temp)
|
||||
SendSignal(COMSIG_SET_BODYTEMP, list("temp"=temp,"from"=src))
|
||||
|
||||
/datum/component/controller/proc/getBodyTemperature()
|
||||
return -1
|
||||
|
||||
/datum/component/controller/proc/canAttack(var/atom/A)
|
||||
if(istype(container.holder, /mob/living/simple_animal))
|
||||
var/mob/living/simple_animal/SA = container.holder
|
||||
return SA.CanAttack(A)
|
||||
return FALSE
|
||||
26
code/modules/components/ai/controllers/mob_controller.dm
Normal file
26
code/modules/components/ai/controllers/mob_controller.dm
Normal file
@@ -0,0 +1,26 @@
|
||||
/datum/component/controller/mob
|
||||
var/walk_delay=4
|
||||
|
||||
/datum/component/controller/mob/RecieveSignal(var/message_type, var/list/args)
|
||||
if(isliving(container.holder))
|
||||
var/mob/living/M=container.holder
|
||||
//testing("Got command: \[[message_type]\]: [json_encode(args)]")
|
||||
switch(message_type)
|
||||
if(COMSIG_MOVE) // list("loc"=turf)
|
||||
// list("dir"=NORTH)
|
||||
if("loc" in args)
|
||||
//walk_to(src, target, minimum_distance, delay)
|
||||
//testing("Walking towards [args["loc"]] with walk_delay=[walk_delay]")
|
||||
walk_to(M, args["loc"], 1, walk_delay)
|
||||
if("dir" in args)
|
||||
// walk(M, get_dir(src,M), MISSILE_SPEED)
|
||||
walk(M, args["dir"], walk_delay)
|
||||
|
||||
if(COMSIG_ADJUST_BODYTEMP) // list("temp"=TEMP_IN_KELVIN)
|
||||
M.bodytemperature += args["temp"]
|
||||
|
||||
if(COMSIG_SET_BODYTEMP) // list("temp"=TEMP_IN_KELVIN)
|
||||
M.bodytemperature = args["temp"]
|
||||
|
||||
if(COMSIG_STATE) // list("state"=HOSTILE_STANCE_ATTACK)
|
||||
setState(args["state"])
|
||||
8
code/modules/components/ai/controllers/simple_animal.dm
Normal file
8
code/modules/components/ai/controllers/simple_animal.dm
Normal file
@@ -0,0 +1,8 @@
|
||||
/datum/component/controller/simple_animal
|
||||
var/disable_automove_on_busy=1
|
||||
|
||||
/datum/component/controller/simple_animal/setBusy(var/yes)
|
||||
..(yes)
|
||||
if(disable_automove_on_busy)
|
||||
var/mob/living/simple_animal/SA = holder
|
||||
SA.stop_automated_movement = yes
|
||||
78
code/modules/components/ai/door_opener.dm
Normal file
78
code/modules/components/ai/door_opener.dm
Normal file
@@ -0,0 +1,78 @@
|
||||
/datum/component/ai/door_opener
|
||||
var/pressure_check=TRUE
|
||||
var/max_pressure_diff=-1
|
||||
|
||||
/datum/component/ai/door_opener/RecieveSignal(var/message_type, var/list/args)
|
||||
switch(message_type)
|
||||
if(COMSIG_ATTACKING) // list("target"=A)
|
||||
OnAttackingTarget(args["target"])
|
||||
else
|
||||
..(message_type, args)
|
||||
|
||||
/datum/component/ai/door_opener/proc/OnAttackingTarget(var/atom/target)
|
||||
if(istype(target,/obj/machinery/door))
|
||||
var/obj/machinery/door/D = target
|
||||
if(CanOpenDoor(D))
|
||||
if(get_dist(src, target) > 1)
|
||||
return // keep movin'.
|
||||
controller.setBusy(TRUE)
|
||||
SendSignal(COMSIG_MOVE, "dir"=0) // Stop movement?
|
||||
D.visible_message("<span class='warning'>\The [D]'s motors whine as four arachnid claws begin trying to force it open!</span>")
|
||||
spawn(50)
|
||||
if(CanOpenDoor(D) && prob(25))
|
||||
D.open(1)
|
||||
D.visible_message("<span class='warning'>\The [src] forces \the [D] open!</span>")
|
||||
|
||||
// Open firedoors, too.
|
||||
for(var/obj/machinery/door/firedoor/FD in D.loc)
|
||||
if(FD && FD.density)
|
||||
FD.open(1)
|
||||
|
||||
// Reset targetting
|
||||
controller.setBusy(FALSE)
|
||||
controller.setTarget(null)
|
||||
return
|
||||
controller.setBusy(FALSE)
|
||||
return
|
||||
|
||||
/datum/component/ai/door_opener/proc/performPressureCheck(var/turf/loc)
|
||||
var/turf/simulated/lT=loc
|
||||
if(!istype(lT) || !lT.zone)
|
||||
return 0
|
||||
var/datum/gas_mixture/myenv=lT.return_air()
|
||||
var/pressure=myenv.return_pressure()
|
||||
|
||||
for(var/dir in cardinal)
|
||||
var/turf/simulated/T=get_turf(get_step(loc,dir))
|
||||
if(T && istype(T) && T.zone)
|
||||
var/datum/gas_mixture/environment = T.return_air()
|
||||
var/pdiff = abs(pressure - environment.return_pressure())
|
||||
if(pdiff > max_pressure_diff)
|
||||
return pdiff
|
||||
return 0
|
||||
|
||||
/datum/component/ai/door_opener/proc/CanOpenDoor(var/obj/machinery/door/D)
|
||||
if(istype(D,/obj/machinery/door/poddoor) || istype(D, /obj/machinery/door/airlock/multi_tile/glass))
|
||||
return 0
|
||||
|
||||
// Don't fuck with doors that are doing something
|
||||
if(D.operating>0)
|
||||
return 0
|
||||
|
||||
// Don't open opened doors.
|
||||
if(!D.density)
|
||||
return 0
|
||||
|
||||
// Can't open bolted/welded doors
|
||||
if(istype(D,/obj/machinery/door/airlock))
|
||||
var/obj/machinery/door/airlock/A=D
|
||||
if(A.locked || A.welded || A.jammed)
|
||||
return 0
|
||||
|
||||
var/turf/T = get_turf(D)
|
||||
|
||||
// Don't kill ourselves
|
||||
if(max_pressure_diff > -1 && !performPressureCheck(T))
|
||||
return 0
|
||||
|
||||
return 1
|
||||
51
code/modules/components/ai/hostile/escape_confinement.dm
Normal file
51
code/modules/components/ai/hostile/escape_confinement.dm
Normal file
@@ -0,0 +1,51 @@
|
||||
/datum/component/ai/escape_confinement
|
||||
var/life_tick=0
|
||||
|
||||
/datum/component/ai/escape_confinement/RecieveSignal(var/message_type, var/list/args)
|
||||
switch(message_type)
|
||||
if(COMSIG_LIFE)
|
||||
OnLife()
|
||||
|
||||
/datum/component/ai/escape_confinement/proc/OnLife()
|
||||
life_tick++
|
||||
var/mob/M = container.holder
|
||||
if(!controller)
|
||||
controller = GetComponent(/datum/component/controller)
|
||||
if(controller.getBusy())
|
||||
return
|
||||
switch(controller.getState())
|
||||
if(HOSTILE_STANCE_IDLE)
|
||||
EscapeConfinement()
|
||||
if(HOSTILE_STANCE_ATTACK)
|
||||
if(!(M.flags & INVULNERABLE))
|
||||
DestroySurroundings()
|
||||
if(HOSTILE_STANCE_ATTACKING)
|
||||
if(!(M.flags & INVULNERABLE))
|
||||
DestroySurroundings()
|
||||
|
||||
/datum/component/ai/escape_confinement/proc/EscapeConfinement()
|
||||
var/atom/A = container.holder
|
||||
if(istype(A, /mob))
|
||||
var/mob/M = A
|
||||
if(M.locked_to)
|
||||
M.locked_to.attack_animal(A)
|
||||
if(!isturf(A.loc) && A.loc != null)//Did someone put us in something?
|
||||
var/atom/locA = A.loc
|
||||
locA.attack_animal(A)//Bang on it till we get out
|
||||
|
||||
/datum/component/ai/escape_confinement/proc/DestroySurroundings()
|
||||
EscapeConfinement()
|
||||
var/list/smash_dirs = list(0)
|
||||
var/atom/target = controller.getTarget()
|
||||
if(!target || !controller.canAttack(target))
|
||||
smash_dirs |= alldirs //if no target, attack everywhere
|
||||
else
|
||||
var/targdir = get_dir(src, target)
|
||||
smash_dirs |= widen_dir(targdir) //otherwise smash towards the target
|
||||
for(var/dir in smash_dirs)
|
||||
var/turf/T = get_step(src, dir)
|
||||
if(istype(T, /turf/simulated/wall) && container.holder.Adjacent(T))
|
||||
T.attack_animal(src)
|
||||
for(var/atom/A in T)
|
||||
if((istype(A, /obj/structure/window) || istype(A, /obj/structure/closet) || istype(A, /obj/structure/table) || istype(A, /obj/structure/grille) || istype(A, /obj/structure/rack)) && container.holder.Adjacent(A))
|
||||
A.attack_animal(src)
|
||||
53
code/modules/components/ai/hostile/hunt.dm
Normal file
53
code/modules/components/ai/hostile/hunt.dm
Normal file
@@ -0,0 +1,53 @@
|
||||
// Hunting controller from spiders
|
||||
/datum/component/ai/hunt
|
||||
var/last_dir=0 // cardinal direction
|
||||
var/last_was_bumped=0 // Boolean, indicates whether the last movement resulted in a Bump().
|
||||
var/life_tick=0
|
||||
|
||||
var/movement_range=20 // Maximum range of points we move to (20 in spiders)
|
||||
|
||||
var/targetfind_delay=10
|
||||
var/datum/component/ai/target_holder/target_holder = null
|
||||
|
||||
/datum/component/ai/hunt/RecieveSignal(var/message_type, var/list/args)
|
||||
switch(message_type)
|
||||
if(COMSIG_LIFE) // no arguments
|
||||
OnLife()
|
||||
|
||||
if(COMSIG_BUMPED) // list("movable"=AM)
|
||||
OnBumped(args["movable"])
|
||||
|
||||
/datum/component/ai/hunt/proc/OnLife()
|
||||
life_tick++
|
||||
//testing("HUNT LIFE, controller=[!isnull(controller)], busy=[controller && controller.getBusy()], state=[controller && controller.getState()]")
|
||||
if(!target_holder)
|
||||
target_holder = GetComponent(/datum/component/ai/target_holder)
|
||||
if(!controller)
|
||||
controller = GetComponent(/datum/component/controller)
|
||||
if(controller.getBusy())
|
||||
return
|
||||
switch(controller.getState())
|
||||
if(HOSTILE_STANCE_IDLE)
|
||||
var/atom/target = target_holder.GetBestTarget(src, "target_evaluator")
|
||||
//testing(" IDLE STANCE, target=\ref[target]")
|
||||
if(!isnull(target))
|
||||
SendSignal(COMSIG_TARGET, list("target"=target))
|
||||
SendSignal(COMSIG_STATE, list("state"=HOSTILE_STANCE_ATTACK))
|
||||
else
|
||||
SendSignal(COMSIG_MOVE, list("loc" = pick(orange(movement_range, src))))
|
||||
if(HOSTILE_STANCE_ATTACK)
|
||||
var/atom/target = target_holder.GetBestTarget(src, "target_evaluator")
|
||||
//testing(" ATTACK STANCE, target=\ref[target]")
|
||||
if(!isnull(target))
|
||||
var/turf/T = get_turf(target)
|
||||
container.SendSignalToFirst(/datum/component/ai, COMSIG_ATTACKING, list("target"=target)) // We're telling the attack modules that we have attack intention. They then individually decide whether to fire.
|
||||
if(T)
|
||||
SendSignal(COMSIG_MOVE, list("loc" = T))
|
||||
return
|
||||
SendSignal(COMSIG_STATE, list("state"=HOSTILE_STANCE_IDLE)) // Lost target
|
||||
|
||||
/datum/component/ai/hunt/proc/OnBumped(var/atom/movable/AM)
|
||||
// TODO
|
||||
|
||||
/datum/component/ai/hunt/proc/target_evaluator(var/atom/target)
|
||||
return TRUE
|
||||
@@ -0,0 +1,7 @@
|
||||
// This just calls animal_attack() on stuff.
|
||||
/datum/component/ai/melee/attack_animal/OnAttackingTarget(var/atom/target)
|
||||
if(..(target))
|
||||
var/mob/living/L = target
|
||||
L.attack_animal(container.holder)
|
||||
return 1 // Accepted
|
||||
return 0 // Unaccepted
|
||||
19
code/modules/components/ai/hostile/melee/inject_reagent.dm
Normal file
19
code/modules/components/ai/hostile/melee/inject_reagent.dm
Normal file
@@ -0,0 +1,19 @@
|
||||
/datum/component/ai/melee/inject_reagent
|
||||
var/poison_type = "" // STOXIN, etc
|
||||
var/poison_per_bite = 0 // Mols to inject
|
||||
var/inject_prob = 0 // Chance to inject, -1 = ALWAYS
|
||||
var/max_poison = 0 // Maximum mols in target's blood. 0 = INF
|
||||
|
||||
/datum/component/ai/melee/inject_reagent/OnAttackingTarget(var/atom/target)
|
||||
if(..(target))
|
||||
var/mob/living/L = target
|
||||
if(L.reagents)
|
||||
if(inject_prob == -1 || prob(inject_prob))
|
||||
var/curamt = L.reagents.get_reagent_amount(poison_type)
|
||||
var/newamt = max_poison - curamt
|
||||
if(newamt >= 1)
|
||||
// TEXT-FORMATTING FUNCTIONS WHEN BYOND?
|
||||
container.holder.visible_message("<span class='warning'>\The [src] injects something into \the [target]!</span>")
|
||||
L.reagents.add_reagent(poison_type, poison_per_bite)
|
||||
return 1 // Accepted signal
|
||||
return 0 // Did not accept signal
|
||||
14
code/modules/components/ai/hostile/melee/melee.dm
Normal file
14
code/modules/components/ai/hostile/melee/melee.dm
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
/datum/component/ai/melee/RecieveSignal(var/message_type, var/list/args)
|
||||
switch(message_type)
|
||||
if(COMSIG_ATTACKING) // list("target"=A)
|
||||
return OnAttackingTarget(args["target"])
|
||||
else
|
||||
return ..(message_type, args)
|
||||
|
||||
/datum/component/ai/melee/proc/OnAttackingTarget(var/atom/target)
|
||||
if(!isliving(target))
|
||||
return 0
|
||||
var/mob/living/L = target
|
||||
return L.Adjacent(container.holder)
|
||||
25
code/modules/components/ai/mobs/component_mob.dm
Normal file
25
code/modules/components/ai/mobs/component_mob.dm
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Component-driven mob.
|
||||
*
|
||||
* See /datum/component and /datum/component_container.
|
||||
*/
|
||||
/mob/living/component
|
||||
var/datum/component_container/container
|
||||
|
||||
/mob/living/component/New()
|
||||
..()
|
||||
container = new (src)
|
||||
InitializeComponents()
|
||||
|
||||
/mob/living/component/proc/InitializeComponents()
|
||||
// Set up components here
|
||||
//var/datum/component/.../ref = container.AddComponent(/datum/component/...)
|
||||
|
||||
/mob/living/component/Life()
|
||||
..()
|
||||
container.SendSignal(COMSIG_LIFE,list())
|
||||
|
||||
|
||||
/mob/living/component/Destroy()
|
||||
qdel(container)
|
||||
..()
|
||||
22
code/modules/components/ai/mobs/giant_spider.dm
Normal file
22
code/modules/components/ai/mobs/giant_spider.dm
Normal file
@@ -0,0 +1,22 @@
|
||||
/mob/living/component/giant_spider
|
||||
name="component giant spider"
|
||||
|
||||
icon_state = "guard"
|
||||
icon = 'icons/mob/animal.dmi'
|
||||
|
||||
/mob/living/component/giant_spider/InitializeComponents()
|
||||
container.AddComponent(/datum/component/controller/mob)
|
||||
container.AddComponent(/datum/component/ai/escape_confinement)
|
||||
container.AddComponent(/datum/component/ai/hunt)
|
||||
container.AddComponent(/datum/component/ai/melee/attack_animal)
|
||||
var/datum/component/ai/melee/inject_reagent/injector = container.AddComponent(/datum/component/ai/melee/inject_reagent)
|
||||
injector.poison_type = STOXIN
|
||||
injector.poison_per_bite = 5
|
||||
var/datum/component/ai/target_finder/simple_view/sv = container.AddComponent(/datum/component/ai/target_finder/simple_view)
|
||||
sv.range = 5
|
||||
// These two should probably be done on New() based on container.holder.
|
||||
sv.exclude_types += src.type
|
||||
sv.exclude_types += /mob/living/silicon/robot/mommi // Because we wuv dem
|
||||
var/datum/component/ai/target_holder/prioritizing/th = container.AddComponent(/datum/component/ai/target_holder/prioritizing)
|
||||
th.type_priorities[src.type]=0
|
||||
container.AddComponent(/datum/component/ai/door_opener)
|
||||
8
code/modules/components/ai/target_finders/simple_view.dm
Normal file
8
code/modules/components/ai/target_finders/simple_view.dm
Normal file
@@ -0,0 +1,8 @@
|
||||
/datum/component/ai/target_finder/simple_view/GetTargets()
|
||||
ASSERT(container.holder!=null)
|
||||
var/list/o = list()
|
||||
for(var/atom/A in view(range, container.holder))
|
||||
if(is_type_in_list(A, exclude_types))
|
||||
continue
|
||||
o += A
|
||||
return o
|
||||
14
code/modules/components/ai/target_finders/target_finder.dm
Normal file
14
code/modules/components/ai/target_finders/target_finder.dm
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
var/datum/controller/target_finder/TF = GetComponent(/datum/controller/target_finder)
|
||||
var/list/target = TF.GetTargets()
|
||||
*/
|
||||
/datum/component/ai/target_finder
|
||||
var/range=0
|
||||
var/list/exclude_types=list(
|
||||
/obj/effect,
|
||||
/atom/movable/lighting_overlay,
|
||||
/turf
|
||||
)
|
||||
|
||||
/datum/component/ai/target_finder/proc/GetTargets()
|
||||
return list()
|
||||
@@ -0,0 +1,55 @@
|
||||
/datum/component/ai/target_holder/prioritizing
|
||||
var/list/targets= list()
|
||||
var/list/type_priorities = list(
|
||||
/mob/living = 1,
|
||||
// /mob/living/simple_animal/hostile/giant_spider=0,
|
||||
/obj/machinery/door = 2,
|
||||
/obj/machinery/light = 3
|
||||
)
|
||||
var/default_priority=2
|
||||
|
||||
var/max_priority=3
|
||||
|
||||
var/datum/component/ai/target_finder/finder = null
|
||||
|
||||
/datum/component/ai/target_holder/prioritizing/proc/attach()
|
||||
if(!finder)
|
||||
finder = GetComponent(/datum/component/ai/target_finder)
|
||||
|
||||
/datum/component/ai/target_holder/prioritizing/AddTarget(var/atom/A)
|
||||
var/priority=-1
|
||||
for(var/priority_type in type_priorities)
|
||||
if(istype(A, priority_type))
|
||||
priority = type_priorities[priority_type]
|
||||
break
|
||||
if(priority==-1) // Use default
|
||||
priority=default_priority
|
||||
if(priority==0) // Don't add
|
||||
return
|
||||
if(!("[priority]" in targets))
|
||||
targets["[priority]"] = list()
|
||||
if(!(A in targets["[priority]"]))
|
||||
targets["[priority]"] += A
|
||||
|
||||
/datum/component/ai/target_holder/prioritizing/RemoveTarget(var/atom/A)
|
||||
for(var/priority in targets)
|
||||
if(A in targets[priority])
|
||||
targets[priority] -= A
|
||||
|
||||
/datum/component/ai/target_holder/prioritizing/GetBestTarget(var/objRef, var/procName, var/from_finder=1)
|
||||
if(from_finder)
|
||||
attach()
|
||||
targets.Cut() // Clear first
|
||||
//var/n=0
|
||||
for(var/atom/target in finder.GetTargets())
|
||||
AddTarget(target)
|
||||
//n++
|
||||
//testing(" TH: Got [n] targets from TF")
|
||||
for(var/priority=1;priority<=max_priority;priority++)
|
||||
var/list/priority_targets = targets["[priority]"]
|
||||
if(priority_targets == null)
|
||||
continue
|
||||
for(var/atom/target in sortMerge(priority_targets, cmp=/proc/cmp_dist_asc, associative=0, fromIndex=1, toIndex=priority_targets.len))
|
||||
if(call(objRef, procName)(target))
|
||||
return target
|
||||
return null
|
||||
19
code/modules/components/ai/target_holders/target_holder.dm
Normal file
19
code/modules/components/ai/target_holders/target_holder.dm
Normal file
@@ -0,0 +1,19 @@
|
||||
/datum/component/ai/target_holder
|
||||
|
||||
/datum/component/ai/target_holder/proc/AddTarget(var/atom/A)
|
||||
return
|
||||
|
||||
/datum/component/ai/target_holder/proc/RemoveTarget(var/atom/A)
|
||||
return
|
||||
|
||||
|
||||
/**
|
||||
* Get the best target
|
||||
*
|
||||
* @param objRef Direct reference to the holding object containing the target validation callback
|
||||
* @param procName Name of the callback proc
|
||||
* @param from_finder Use /datum/component/ai/target_finder.GetTargets()
|
||||
* @return null if not target found, /atom if a target is found.
|
||||
*/
|
||||
/datum/component/ai/target_holder/proc/GetBestTarget(var/objRef, var/procName, var/from_finder=1)
|
||||
return
|
||||
24
code/modules/components/component.dm
Normal file
24
code/modules/components/component.dm
Normal file
@@ -0,0 +1,24 @@
|
||||
/datum/component
|
||||
var/datum/component_container/container
|
||||
|
||||
// Enables or disables the components
|
||||
var/enabled=1
|
||||
|
||||
/datum/component/New(var/datum/component_container/CC)
|
||||
container=CC
|
||||
|
||||
// Override to receive signals.
|
||||
/datum/component/proc/RecieveSignal(var/sigtype, var/list/args)
|
||||
return
|
||||
|
||||
// Send a signal to all other components in the container.
|
||||
/datum/component/proc/SendSignal(var/sigtype, var/list/args)
|
||||
container.SendSignal(sigtype, args)
|
||||
|
||||
// Return first /datum/component that is subtype of c_type.
|
||||
/datum/component/proc/GetComponent(var/c_type)
|
||||
return container.GetComponent(c_type)
|
||||
|
||||
// Returns ALL /datum/components in parent container that are subtypes of c_type.
|
||||
/datum/component/proc/GetComponents(var/c_type)
|
||||
return container.GetComponents(c_type)
|
||||
99
code/modules/components/component_container.dm
Normal file
99
code/modules/components/component_container.dm
Normal file
@@ -0,0 +1,99 @@
|
||||
// Basic multipurpose component container.
|
||||
/datum/component_container
|
||||
|
||||
// Where the components themselves are stored after initialization.
|
||||
var/list/components=list()
|
||||
|
||||
// The types to initialize the holder with. Later, holds unique list of all types in container.
|
||||
var/list/types=list()
|
||||
|
||||
// The entity that holds this datum.
|
||||
var/atom/holder
|
||||
|
||||
// Used for dependency management.
|
||||
|
||||
/datum/component_container/New(var/atom/holder_atom)
|
||||
holder=holder_atom
|
||||
|
||||
/datum/component_container/proc/AddComponentsByType(var/list/new_types)
|
||||
var/list/newtypes=list()
|
||||
for(var/new_type in new_types)
|
||||
AddComponent(new_type,TRUE)
|
||||
if(!(new_type in newtypes))
|
||||
newtypes.Add(new_type)
|
||||
types=newtypes
|
||||
|
||||
/**
|
||||
* Add component to the container.
|
||||
*
|
||||
* @param new_type The type we wish to instantiate in the component_container.
|
||||
* @param initializing Do not use, only used in container New() for internal purposes.
|
||||
*/
|
||||
/datum/component_container/proc/AddComponent(var/new_type, var/initializing=FALSE)
|
||||
if(!initializing && !(new_type in types))
|
||||
types.Add(new_type)
|
||||
var/datum/component/C = new new_type(src)
|
||||
components.Add(C)
|
||||
SendSignal(COMSIG_COMPONENT_ADDED,list("component"=C))
|
||||
return C
|
||||
|
||||
/**
|
||||
* Removes a component from the container.
|
||||
*
|
||||
* @param C The component to remove
|
||||
*/
|
||||
/datum/component_container/proc/RemoveComponent(var/datum/component/C)
|
||||
SendSignal(COMSIG_COMPONENT_REMOVING,list("component"=C))
|
||||
components.Remove(C)
|
||||
types.Cut()
|
||||
for(var/datum/component/CC in components)
|
||||
if(!(CC.type in types))
|
||||
types.Add(CC.type)
|
||||
//qdel(C) Will most likely get GC'd anyway.
|
||||
|
||||
/**
|
||||
* Send a signal to all components in the container.
|
||||
*
|
||||
* @param message_type Name of the signal.
|
||||
* @param args List of arguments to send with the signal.
|
||||
*/
|
||||
/datum/component_container/proc/SendSignal(var/message_type, var/list/args)
|
||||
for(var/datum/component/C in components)
|
||||
if(C.enabled)
|
||||
C.RecieveSignal(message_type, args)
|
||||
|
||||
/**
|
||||
* Send a signal to the first component of type accepting a signal.
|
||||
*
|
||||
* @param component_type
|
||||
* @param message_type Name of the signal.
|
||||
* @param args List of arguments to send with the signal.
|
||||
*/
|
||||
/datum/component_container/proc/SendSignalToFirst(var/desired_type, var/message_type, var/list/args, var/shuffle=TRUE)
|
||||
var/list/shuffled=list(components) // Copy list so we don't disorder the container.
|
||||
if(shuffle)
|
||||
shuffled=shuffle(shuffled)
|
||||
for(var/datum/component/C in components)
|
||||
if(C.enabled && istype(C, desired_type))
|
||||
if(C.RecieveSignal(message_type, args)) // return 1 to accept signal.
|
||||
return
|
||||
|
||||
|
||||
/**
|
||||
* Get the first component matching the specified type.
|
||||
*/
|
||||
/datum/component_container/proc/GetComponent(var/desired_type)
|
||||
for(var/datum/component/C in components)
|
||||
if(istype(C, desired_type))
|
||||
return C
|
||||
return null
|
||||
|
||||
/**
|
||||
* Get the all components matching the specified type.
|
||||
*/
|
||||
/datum/component_container/proc/GetComponents(var/desired_type)
|
||||
. = list()
|
||||
for(var/datum/component/C in components)
|
||||
if(istype(C, desired_type))
|
||||
. += C
|
||||
return .
|
||||
@@ -76,9 +76,10 @@
|
||||
if (flags & INVULNERABLE)
|
||||
bodytemperature = initial(bodytemperature)
|
||||
if (monkeyizing)
|
||||
return
|
||||
return 0
|
||||
if(!loc)
|
||||
return // Fixing a null error that occurs when the mob isn't found in the world -- TLE
|
||||
return 0 // Fixing a null error that occurs when the mob isn't found in the world -- TLE
|
||||
// Why the fuck is this handled here?
|
||||
if(reagents && reagents.has_reagent(BUSTANUT))
|
||||
if(!(M_HARDCORE in mutations))
|
||||
mutations.Add(M_HARDCORE)
|
||||
@@ -99,7 +100,7 @@
|
||||
if(mind)
|
||||
if(mind in ticker.mode.implanted)
|
||||
if(implanting)
|
||||
return
|
||||
return 0
|
||||
// to_chat(world, "[src.name]")
|
||||
var/datum/mind/head = ticker.mode.implanted[mind]
|
||||
//var/list/removal
|
||||
@@ -115,6 +116,7 @@
|
||||
special_role = null
|
||||
to_chat(current, "<span class='danger'><FONT size = 3>The fog clouding your mind clears. You remember nothing from the moment you were implanted until now..(You don't remember who enslaved you)</FONT></span>")
|
||||
*/
|
||||
return 1
|
||||
|
||||
// Apply connect damage
|
||||
/mob/living/beam_connect(var/obj/effect/beam/B)
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "code\hub.dm"
|
||||
#include "code\names.dm"
|
||||
#include "code\world.dm"
|
||||
#include "code\__DEFINES\component_signals.dm"
|
||||
#include "code\__DEFINES\disease2.dm"
|
||||
#include "code\__DEFINES\hydro.dm"
|
||||
#include "code\__DEFINES\pai_software.dm"
|
||||
@@ -1132,6 +1133,25 @@
|
||||
#include "code\modules\clothing\under\jobs\engineering.dm"
|
||||
#include "code\modules\clothing\under\jobs\medsci.dm"
|
||||
#include "code\modules\clothing\under\jobs\security.dm"
|
||||
#include "code\modules\components\component.dm"
|
||||
#include "code\modules\components\component_container.dm"
|
||||
#include "code\modules\components\ai\ai_component.dm"
|
||||
#include "code\modules\components\ai\atmos.dm"
|
||||
#include "code\modules\components\ai\controller.dm"
|
||||
#include "code\modules\components\ai\door_opener.dm"
|
||||
#include "code\modules\components\ai\controllers\mob_controller.dm"
|
||||
#include "code\modules\components\ai\controllers\simple_animal.dm"
|
||||
#include "code\modules\components\ai\hostile\escape_confinement.dm"
|
||||
#include "code\modules\components\ai\hostile\hunt.dm"
|
||||
#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\mobs\component_mob.dm"
|
||||
#include "code\modules\components\ai\mobs\giant_spider.dm"
|
||||
#include "code\modules\components\ai\target_finders\simple_view.dm"
|
||||
#include "code\modules\components\ai\target_finders\target_finder.dm"
|
||||
#include "code\modules\components\ai\target_holders\prioritizing_holder.dm"
|
||||
#include "code\modules\components\ai\target_holders\target_holder.dm"
|
||||
#include "code\modules\context_click\context_click.dm"
|
||||
#include "code\modules\context_click\remote.dm"
|
||||
#include "code\modules\customitems\item_spawning.dm"
|
||||
|
||||
Reference in New Issue
Block a user