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:
Rob Nelson
2017-02-25 15:04:06 -08:00
committed by Probe1
parent 3d70cc1a86
commit fe929e5849
24 changed files with 810 additions and 3 deletions

View 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"

View File

@@ -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)

View File

@@ -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)

View 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)

View 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))

View 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

View 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"])

View 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

View 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

View 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)

View 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

View File

@@ -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

View 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

View 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)

View 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)
..()

View 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)

View 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

View 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()

View File

@@ -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

View 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

View 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)

View 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 .

View File

@@ -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)

View File

@@ -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"