Skip to content

Commit

Permalink
Create ai_leadership.dm
Browse files Browse the repository at this point in the history
Update lobotomy-corp13.dme

Update ai_leadership.dm

Update indigo.dm
  • Loading branch information
InsightfulParasite committed Oct 14, 2023
1 parent 482fd7d commit 2e8a37b
Show file tree
Hide file tree
Showing 4 changed files with 332 additions and 133 deletions.
291 changes: 291 additions & 0 deletions code/datums/components/ai_leadership.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
/* Component for AI leadership. Ai will leader
other mobs of the listed type. */

/datum/component/ai_leadership
dupe_mode = COMPONENT_DUPE_UNIQUE
//Cooldowns for recruitment
var/recruit_cooldown = 0
var/recruit_delay = 2 SECONDS
//Cooldowns for headcount
var/headcount_cooldown = 0
var/headcount_delay = 25 SECONDS
//Amount of allowed followers
var/unit_amount
//If the team should only have a certain amount of type.
var/unqiue_team
//If followers will return to a base when disbanded
var/return_to_fob
//Datum that tracks all of the components
var/static/datum/collective_management/follower_tracker
//Allowed types of followers
var/list/possible_followers = list()
//This components current followers.
var/list/followers = list()

/* This could use some improvement.
Or a full on replacement. */
/datum/component/ai_leadership/Initialize(allowed_types, amount = 6, unique = FALSE, forwardbase = FALSE)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE

if(!follower_tracker)
follower_tracker = new /datum/collective_management()
var/atom/L = parent

return_to_fob = forwardbase
unqiue_team = unique
unit_amount = amount
possible_followers += allowed_types

follower_tracker.AddLeader(L)
ScanLocation()
START_PROCESSING(SSdcs, src)

/datum/component/ai_leadership/RegisterWithParent()
if(isliving(parent))
RegisterSignal(parent, COMSIG_LIVING_DEATH, .proc/RemoveLeader)
if(ishostile(parent))
RegisterSignal(parent, COMSIG_PATROL_START, .proc/HeadCount)

/datum/component/ai_leadership/UnregisterFromParent()
if(isliving(parent))
UnregisterSignal(parent, COMSIG_LIVING_DEATH)
if(ishostile(parent))
UnregisterSignal(parent, COMSIG_PATROL_START)

//On death remove this component and disband their followers.
/datum/component/ai_leadership/proc/RemoveLeader()
SIGNAL_HANDLER

follower_tracker.RemoveLeader(parent, followers)
STOP_PROCESSING(SSdcs, src)
qdel(src)

/datum/component/ai_leadership/process()
//If not a hostile then it would not get a signal.
if(!ishostile(parent) && world.time > headcount_cooldown && followers.len)
HeadCount(parent)
if(world.time > recruit_cooldown && followers.len < unit_amount)
ScanLocation()
recruit_cooldown = world.time + recruit_delay

/*-----------------------
|~ Main Function Procs ~|
-----------------------*/

/*Scan proc for leader. Originally this was a proc
in the hostile found since their targeting
system scans everything all the time. */
/datum/component/ai_leadership/proc/ScanLocation()
for(var/mob/living/L in view(7, parent))
if(followers.len >= unit_amount)
break
if(Recruitable(L))
Recruit(L)

/*Checks the group of creatures to see if anyone is missing.
Anyone missing will be removed from the group so that
another team can recruit them.*/
/datum/component/ai_leadership/proc/HeadCount(atom/U)
if(world.time < headcount_cooldown || !followers.len)
return
var/turf/fob
var/list/whosehere = list()
followers = uniqueList(followers)

for(var/mob/living/L in followers)
if(L.stat != DEAD && !L.client && L.z == U.z && get_dist(U, L) < 10)
whosehere += L

var/list/absent_troops = difflist(followers, whosehere ,1)
if(absent_troops.len)
if(return_to_fob)
//Only run this once.
fob = get_turf(FindForwardBase())
for(var/mob/living/S in absent_troops)
Disband(S)
if(ishostile(S))
var/mob/living/simple_animal/hostile/R = S
if(fob && R.stat != DEAD && !R.target)
walk(R, 0)
R.patrol_to(fob)
headcount_cooldown = world.time + headcount_delay
Regroup()

/*---------------------
|~ Follower Commands ~|
---------------------*/

//Command follower to follow leader.
/datum/component/ai_leadership/proc/FollowLeader(mob/living/L)
if(ishostile(parent))
var/mob/living/simple_animal/hostile/H = parent
walk_to(L, parent, 2, H.move_to_delay - 1.5)
if(iscarbon(parent))
var/mob/living/carbon/H = parent
walk_to(L, parent, 2, H.cached_multiplicative_slowdown - 1.5)
else if(ishostile(L))
var/mob/living/simple_animal/hostile/R = L
if(!R.target)
walk_to(R, parent, 2, R.move_to_delay)
else
return FALSE
return TRUE

//Orders all troops to follow the leader.
/datum/component/ai_leadership/proc/Regroup()
for(var/i in followers)
FollowLeader(i)

/*-----------------
|~ System Checks ~|
-----------------*/

/*Determines if the creature is recruitable
based on their current status and faction.*/
/datum/component/ai_leadership/proc/Recruitable(mob/living/L)
//Are we at maximum followers
if(followers.len >= unit_amount)
return FALSE
//Are they dead?
if(L.stat == DEAD)
return FALSE
//Player controlled, they do not recognize our authroity.
if(L.client)
return FALSE
//If we are a hostile do these checks
if(ishostile(parent))
var/mob/living/simple_animal/hostile/H = parent
//Are they the same faction as us?
if(!H.faction_check_mob(L))
return FALSE
//If they currently have a target let them finish up.
if(H.target)
return FALSE
var/recruit_type = CheckUnitType(possible_followers, L)
//Are they a type in our possible follower types?
if(!recruit_type)
return FALSE
//If we only have the command capacity to command more than one of them.
if(unqiue_team)
if(!TeamCheck(L))
return FALSE
//Are they already a follower of another leader?
if(FollowerLedger(L))
return FALSE
return TRUE

//Register recruit with the system
/datum/component/ai_leadership/proc/Recruit(mob/living/L)
followers += L
follower_tracker.RecruitFollower(L)
FollowLeader(L)

//Releases the recruit from our command
/datum/component/ai_leadership/proc/Disband(mob/living/L)
followers -= L
follower_tracker.DismissFollower(L)

//Is the recruit already recruited in the system?
/datum/component/ai_leadership/proc/FollowerLedger(mob/living/recruit)
if(follower_tracker.FindRecruit(recruit))
return TRUE
return FALSE

//Check the type system
/datum/component/ai_leadership/proc/CheckUnitType(list/type_list, mob/living/unit_type)
for(var/i in type_list)
if(unit_type.type == i)
return i
return null

//Checks if theres any room for this unit in the team.
/datum/component/ai_leadership/proc/TeamCheck(mob/living/M)
var/many_we_have = CountByTroopType(M)
var/our_capacity = 0
for(var/i in possible_followers)
if(M.type == i)
our_capacity = possible_followers[i]
break
if(our_capacity <= many_we_have)
return FALSE
return TRUE

/*Used to be count by type until i realized count by
type includes subtypes when i need a strict type.*/
/datum/component/ai_leadership/proc/CountByTroopType(mob/living/M)
if(!M)
return 0
var/counting_dudes = 0
for(var/mob/living/dude in followers)
if(dude.type == M.type)
counting_dudes++
return counting_dudes

//Calculate a base to return to, usually is a department.
/datum/component/ai_leadership/proc/FindForwardBase()
var/mob/living/L = parent
var/turf/second_choice
for(var/turf/T in GLOB.department_centers)
if(T.z != L.z)
continue
second_choice = T
if(istype(get_area(T), /area/department_main/command))
return T
return second_choice

/*-----------------------\
| Group Mind Datum |
\-----------------------*/

/datum/collective_management
var/list/leaders = list()
var/list/follower_list = list()

//Called by component recruit
/datum/collective_management/proc/RecruitFollower(follower)
if(!follower)
return FALSE
LAZYADD(follower_list, follower)
return TRUE

//Called when a follower is removed from component and list.
/datum/collective_management/proc/DismissFollower(follower)
if(!follower)
return FALSE
CheckLedger()
LAZYREMOVE(follower_list, follower)
return TRUE

//Registers leader into the system
/datum/collective_management/proc/AddLeader(leader)
if(LAZYFIND(leaders, leader))
return FALSE
CheckLedger()
LAZYADD(leaders, leader)
return TRUE

//Removes leader from the ledger
/datum/collective_management/proc/RemoveLeader(leader, followers)
CheckLedger()
LAZYREMOVE(leaders, leader)
for(var/minion in followers)
DismissFollower(minion)

//Checks to find if recruit is already in the system
/datum/collective_management/proc/FindRecruit(follower)
if(LAZYFIND(follower_list, follower))
return TRUE
return FALSE

//Does technical fixes such as remove nulls and duplicates
/datum/collective_management/proc/CheckLedger()
//Remove duplicates.
var/fixed_leader_list = uniqueList(leaders)
var/fixed_follower_list = uniqueList(follower_list)
//Replace list with a sorted fixed version.
leaders = sortNames(fixed_leader_list)
follower_list = sortNames(fixed_follower_list)
//Remove nulls.
listclearnulls(leaders)
listclearnulls(follower_list)
53 changes: 9 additions & 44 deletions code/modules/mob/living/simple_animal/hostile/ordeal/indigo.dm
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
attack_sound = 'sound/effects/ordeals/indigo/stab_1.ogg'
damage_coeff = list(BRUTE = 1, RED_DAMAGE = 1, WHITE_DAMAGE = 1.5, BLACK_DAMAGE = 0.5, PALE_DAMAGE = 0.8)
blood_volume = BLOOD_VOLUME_NORMAL
var/leader //used by indigo dusk to recruit sweepers

/mob/living/simple_animal/hostile/ordeal/indigo_noon/Initialize()
. = ..()
Expand All @@ -79,8 +78,7 @@

/mob/living/simple_animal/hostile/ordeal/indigo_noon/LoseAggro()
. = ..()
if(leader)
a_intent_change(INTENT_HELP)
a_intent_change(INTENT_HELP)

/mob/living/simple_animal/hostile/ordeal/indigo_noon/AttackingTarget()
. = ..()
Expand Down Expand Up @@ -199,28 +197,20 @@
armortype = PALE_DAMAGE
damage_coeff = list(BRUTE = 1, RED_DAMAGE = 1.5, WHITE_DAMAGE = 0.7, BLACK_DAMAGE = 0.7, PALE_DAMAGE = 0.5)


/mob/living/simple_animal/hostile/ordeal/indigo_dusk/Found(atom/A) //every time she finds a sweeper that sweeper is compelled to follow her as family
if(istype(A, /mob/living/simple_animal/hostile/ordeal/indigo_noon) && troops.len < 6)
var/mob/living/simple_animal/hostile/ordeal/indigo_noon/S = A
if(S.stat != DEAD && !S.leader && !S.target && !S.client) //are you dead? do you have a leader? are you currently fighting? Are you a player?
S.Goto(src,S.move_to_delay,1)
S.leader = src
troops += S
/mob/living/simple_animal/hostile/ordeal/indigo_dusk/Initialize(mapload)
..()
var/units_to_add = list(
/mob/living/simple_animal/hostile/ordeal/indigo_noon = 1,
)
AddComponent(/datum/component/ai_leadership, units_to_add)

/mob/living/simple_animal/hostile/ordeal/indigo_dusk/Aggro()
. = ..()
a_intent_change(INTENT_HARM)
..()
if(order_cooldown < world.time && troops.len)
order_cooldown = world.time + (10 SECONDS)
var/mob/living/simple_animal/hostile/ordeal/overachiver = locate(/mob/living/simple_animal/hostile/ordeal/indigo_noon) in troops
if(overachiver)
overachiver.TemporarySpeedChange(amount = -2, time = 5 SECONDS)

/mob/living/simple_animal/hostile/ordeal/indigo_dusk/LoseAggro()
. = ..()
if(troops.len)
a_intent_change(INTENT_HELP) //so that they dont get body blocked by their kin outside of combat
a_intent_change(INTENT_HELP) //so that they dont get body blocked by their kin outside of combat

/mob/living/simple_animal/hostile/ordeal/indigo_dusk/AttackingTarget()
. = ..()
Expand All @@ -232,31 +222,6 @@
else
devour(L)

/mob/living/simple_animal/hostile/ordeal/indigo_dusk/patrol_select()
if(troops.len)
headcount()
for(var/mob/living/simple_animal/hostile/ordeal/indigo_noon/family in troops)
if(family.stat == DEAD || family.client) //if you are dead or are a player your no longer active in the family.
troops -= family
Goto(src , 2, 1) //had to change it to 2 because the 3 "move to delay" leader would keep outrunning the 4 "move to delay" followers
..()

/mob/living/simple_animal/hostile/ordeal/indigo_dusk/death()
for(var/mob/living/simple_animal/hostile/ordeal/indigo_noon/S in troops) //The leader can no longer lead their troops into battle.
if(S)
S.leader = null
return ..()

/mob/living/simple_animal/hostile/ordeal/indigo_dusk/proc/headcount()
var/list/whosehere = list()
for(var/mob/living/simple_animal/hostile/ordeal/indigo_noon/soldier in oview(src, 10))
whosehere += soldier
var/list/absent_troops = difflist(troops, whosehere ,1)
if(absent_troops.len)
for(var/mob/living/simple_animal/hostile/ordeal/indigo_noon/s in absent_troops)
s.leader = null
troops -= s

/mob/living/simple_animal/hostile/ordeal/indigo_dusk/proc/devour(mob/living/L)
if(!L)
return FALSE
Expand Down
Loading

0 comments on commit 2e8a37b

Please sign in to comment.