Refactors antag team code. Adds an admin UI to interact with teams (#19870)

* adds antag team framework code

* Apply suggestions from code review

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>

* sirryan review

* adds the ability for admins to add members

* oops

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
This commit is contained in:
SteelSlayer
2022-12-26 13:32:55 -06:00
committed by GitHub
parent 9caf885ae1
commit 80d2959d19
8 changed files with 306 additions and 40 deletions
+2
View File
@@ -363,6 +363,8 @@
dat += "ETA: <a href='?_src_=holder;edit_shuttle_time=1'>[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]</a><BR>"
dat += "<a href='?src=[UID()];delay_round_end=1'>[SSticker.delay_end ? "End Round Normally" : "Delay Round End"]</a><br>"
dat += "<br><b>Antagonist Teams</b><br>"
dat += "<a href='?src=[UID()];check_teams=1'>View Teams</a><br>"
if(SSticker.mode.syndicates.len)
dat += "<br><table cellspacing=5><tr><td><B>Syndicates</B></td><td></td></tr>"
for(var/datum/mind/N in SSticker.mode.syndicates)
+43
View File
@@ -1609,6 +1609,49 @@
else if(href_list["check_antagonist"])
check_antagonists()
else if(href_list["check_teams"])
if(!check_rights(R_ADMIN))
return
check_teams()
else if(href_list["team_command"])
if(!check_rights(R_ADMIN))
return
var/datum/team/team
var/datum/mind/member
if(href_list["team"])
team = locateUID(href_list["team"])
if(QDELETED(team))
to_chat(usr, "<span class='warning'>This team doesn't exist anymore!</span>")
return
if(href_list["member"])
member = locateUID(href_list["member"])
if(QDELETED(member))
to_chat(usr, "<span class='warning'>This team member doesn't exist anymore!</span>")
return
switch(href_list["team_command"])
if("communicate")
team.admin_communicate(usr)
if("delete_team")
message_admins("[key_name_admin(usr)] has deleted the '[team.name]' team.")
log_admin("[key_name_admin(usr)] has deleted the '[team.name]' team.")
qdel(team)
if("rename_team")
team.admin_rename_team(usr)
if("admin_add_member")
team.admin_add_member(usr)
if("remove_member")
team.admin_remove_member(usr, member)
if("view_member")
show_player_panel(member.current)
if("add_objective")
team.admin_add_objective(usr)
if("remove_objective")
var/datum/objective/O = locateUID(href_list["objective"])
if(O)
team.admin_remove_objective(usr, O)
check_teams()
else if(href_list["take_question"])
var/index = text2num(href_list["take_question"])
@@ -44,7 +44,10 @@ GLOBAL_LIST_EMPTY(antagonists)
assigned_targets = list()
/datum/antagonist/Destroy(force, ...)
QDEL_LIST(objectives)
for(var/datum/objective/O as anything in objectives)
objectives -= O
if(!O.team)
qdel(O)
remove_owner_from_gamemode()
GLOB.antagonists -= src
if(!silent)
@@ -6,14 +6,3 @@
continue
if(!antag_type || !specific && istype(A, antag_type) || specific && A.type == antag_type)
. += A.owner
//Get all teams [of type team_type]
/proc/get_all_teams(team_type)
. = list()
for(var/V in GLOB.antagonists)
var/datum/antagonist/A = V
if(!A.owner)
continue
var/datum/team/T = A.get_team()
if(!team_type || istype(T, team_type))
. |= T
+208 -15
View File
@@ -1,24 +1,217 @@
//A barebones antagonist team.
GLOBAL_LIST_EMPTY(antagonist_teams)
/**
* # Antagonist Team
*
* Datum used by team antagonists to track it's members and objectives the team needs to complete.
*/
/datum/team
var/list/datum/mind/members = list()
var/name = "team"
var/member_name = "member"
var/list/objectives = list() //common objectives, these won't be added or removed automatically, subtypes handle this, this is here for bookkeeping purposes.
/// The name of the team.
var/name = "Generic Team Name"
/// A list of [minds][/datum/mind] who belong to this team.
var/list/members
/// A list of objectives which all team members share.
var/list/objectives
/// Type of antag datum members of this team have. Also given to new members added by admins.
var/antag_datum_type
/datum/team/New(starting_members)
. = ..()
if(starting_members)
if(islist(starting_members))
for(var/datum/mind/M in starting_members)
add_member(M)
else
add_member(starting_members)
/datum/team/New(list/starting_members)
..()
members = list()
objectives = list()
if(starting_members && !islist(starting_members))
starting_members = list(starting_members)
for(var/datum/mind/M as anything in starting_members)
add_member(M)
GLOB.antagonist_teams += src
/datum/team/proc/is_solo()
return members.len == 1
/datum/team/Destroy(force = FALSE, ...)
for(var/datum/mind/member as anything in members)
remove_member(member)
QDEL_LIST(objectives)
members.Cut()
GLOB.antagonist_teams -= src
return ..()
/**
* Adds `new_member` to this team.
*
* Generally this should ONLY be called by `add_antag_datum()` to ensure proper order of operations.
*/
/datum/team/proc/add_member(datum/mind/new_member)
SHOULD_CALL_PARENT(TRUE)
var/datum/antagonist/team_antag = get_antag_datum_from_member(new_member)
members |= new_member
team_antag.objectives |= objectives
/**
* Removes `member` from this team.
*/
/datum/team/proc/remove_member(datum/mind/member)
SHOULD_CALL_PARENT(TRUE)
var/datum/antagonist/A = get_antag_datum_from_member(member)
members -= member
A.objectives -= objectives
/**
* Adds a new member to this team from a list of players in the round.
*/
/datum/team/proc/admin_add_member(mob/user)
var/list/valid_minds = list()
for(var/mob/living/carbon/human/H in GLOB.player_list)
if(!H.mind || (H.mind in members))
continue
valid_minds[H.real_name] = H.mind
var/name = input(user, "Choose a player to add to this team", "Add Team Member") as null|anything in valid_minds
if(!name)
to_chat(user, "<span class='warning'>No suitable humanoid targets found!</span>")
return
var/datum/mind/new_member = valid_minds[name]
add_member(new_member)
/**
* Adds a team objective to each member's matching antag datum.
*/
/datum/team/proc/add_objective_to_members(datum/objective/O)
for(var/datum/mind/M as anything in members)
var/datum/antagonist/A = get_antag_datum_from_member(M)
A.objectives |= O
/**
* Remove a team objective from each member's matching antag datum.
*/
/datum/team/proc/remove_objective_from_members(datum/objective/O)
for(var/datum/mind/M as anything in members)
var/datum/antagonist/A = get_antag_datum_from_member(M)
A.objectives -= O
objectives -= O
qdel(O)
/**
* Return an antag datum from a member which is linked with this team.
*/
/datum/team/proc/get_antag_datum_from_member(datum/mind/member)
for(var/datum/antagonist/A as anything in member.antag_datums)
if(A.get_team() != src)
continue
return A
// If no matching antag datum was found, give them one.
if(antag_datum_type)
member.add_antag_datum(antag_datum_type, src)
/**
* Allows admins to send a message to all members of this team.
*/
/datum/team/proc/admin_communicate(mob/user)
var/message = input(user, "Enter a message to send to the team:", "Team Message") as text|null
if(!message)
return
for(var/datum/mind/M as anything in members)
to_chat(M.current, "<font color='#d6000b'><span class='bold'>Admin Team Message ([user.key]): </span><span class='notice'>[message]</span>")
message_admins("Team Message: [key_name(user)] -> '[name]' team. Message: [message]")
log_admin("Team Message: [key_name(user)] -> '[name]' team. Message: [message]")
/**
* Allows admins to add a team objective.
*/
/datum/team/proc/admin_add_objective(mob/user)
var/selected = input("Select an objective type:", "Objective Type") as null|anything in GLOB.admin_objective_list
if(!selected)
return
var/objective_type = GLOB.admin_objective_list[selected]
var/datum/objective/O = new objective_type(team_to_join = src)
O.find_target(members) // Blacklist any team members from being the target.
objectives |= O
add_objective_to_members(O)
message_admins("[key_name_admin(user)] added objective [O.type] to the team '[name]'.")
log_admin("[key_name(user)] added objective [O.type] to the team '[name]'.")
/**
* Allows admins to remove a team objective.
*/
/datum/team/proc/admin_remove_objective(mob/user, datum/objective/O)
remove_objective_from_members(O)
message_admins("[key_name_admin(user)] removed objective [O.type] from the team '[name]'.")
log_admin("[key_name(user)] removed objective [O.type] from the team '[name]'.")
/**
* Allows admins to rename the team.
*/
/datum/team/proc/admin_rename_team(mob/user)
var/team_name = stripped_input(user, "Enter a new name for the team:", "Rename Team")
if(!team_name)
return
message_admins("[key_name_admin(user)] renamed the '[name]' team to '[team_name]'.")
log_admin("[key_name(user)] renamed the '[name]' team to '[team_name]'.")
name = team_name
/**
* Allows admins to remove a team member.
*/
/datum/team/proc/admin_remove_member(mob/user, datum/mind/M)
message_admins("[key_name_admin(user)] removed [key_name_admin(M)] from the team '[name]'.")
log_admin("[key_name(user)] removed [key_name(M)] from the team '[name]'.")
remove_member(M)
// Used for running team specific admin commands.
/datum/team/Topic(href, href_list)
if(!check_rights(R_ADMIN))
return
var/commands = get_admin_commands()
for(var/admin_command in commands)
if(href_list["command"] == admin_command)
var/datum/callback/C = commands[admin_command]
C.Invoke(usr)
return
/**
* A list of team-specific admin commands for this team. Should be in the form of `"command" = CALLBACK(x, PROC_REF(some_proc))`.
*/
/datum/team/proc/get_admin_commands()
return list()
/**
* Opens a window which lists the teams for the round.
*/
/datum/admins/proc/check_teams()
if(!SSticker.HasRoundStarted())
to_chat(usr, "<span class='warning'>The game hasn't started yet!</span>")
return
var/datum/browser/popup = new(usr, "teams", "Team List", 500, 500)
popup.set_content(list_teams())
popup.open()
/**
* Returns HTML content for the "check teams" window.
*/
/datum/admins/proc/list_teams()
var/list/content = list()
if(!length(GLOB.antagonist_teams))
content += "There are currently no antag teams."
for(var/datum/team/T as anything in GLOB.antagonist_teams)
content += "<h3>[T.name] - [T.type]</h3>"
content += "<a href='?_src_=holder;team_command=rename_team;team=[T.UID()]'>Rename Team</a>"
content += "<a href='?_src_=holder;team_command=delete_team;team=[T.UID()]'>Delete Team</a>"
content += "<a href='?_src_=holder;team_command=communicate;team=[T.UID()]'>Message Team</a>"
for(var/command in T.get_admin_commands())
// _src_ is T.UID() so it points to `/datum/team/Topic` instead of `/datum/admins/Topic`.
content += "<a href='?_src_=[T.UID()];command=[command]'>[command]</a>"
content += "<br><br>Objectives:<br><ol>"
for(var/datum/objective/O as anything in T.objectives)
content += "<li>[O.explanation_text] - <a href='?_src_=holder;team_command=remove_objective;team=[T.UID()];objective=[O.UID()]'>Remove</a></li>"
content += "</ol><a href='?_src_=holder;team_command=add_objective;team=[T.UID()]'>Add Objective</a><br><br>"
content += "Members: <br><ol>"
for(var/datum/mind/M as anything in T.members)
content += "<li>[M.name] - <a href='?_src_=holder;team_command=view_member;team=[T.UID()];member=[M.UID()]'>Show Player Panel</a>"
content += "<a href='?_src_=holder;team_command=remove_member;team=[T.UID()];member=[M.UID()]'>Remove Member</a></li>"
content += "</ol><a href='?_src_=holder;team_command=admin_add_member;team=[T.UID()]'>Add Member</a><hr>"
return content.Join()