diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index 6facc15a3eb3..d37fc83dc6e9 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -410,11 +410,15 @@
var/list/all_teams = list()
var/list/all_antagonists = list()
+ for(var/datum/team/A in GLOB.antagonist_teams)
+ if(!A.members)
+ continue
+ all_teams |= A
+
for(var/datum/antagonist/A in GLOB.antagonists)
if(!A.owner)
continue
- all_teams |= A.get_team()
- all_antagonists += A
+ all_antagonists |= A
for(var/datum/team/T in all_teams)
result += T.roundend_report()
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index b4ed849cf950..8533d3f16b7b 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -431,40 +431,15 @@
else
target_antag = target
-
+ if(!GLOB.admin_objective_list)
+ generate_admin_objective_list()
- var/static/list/choices
- if(!choices)
- choices = list()
-
- var/list/allowed_types = list(
- /datum/objective/assassinate,
- /datum/objective/maroon,
- /datum/objective/debrain,
- /datum/objective/protect,
- /datum/objective/destroy,
- /datum/objective/hijack,
- /datum/objective/escape,
- /datum/objective/survive,
- /datum/objective/martyr,
- /datum/objective/steal,
- /datum/objective/download,
- /datum/objective/nuclear,
- /datum/objective/capture,
- /datum/objective/absorb,
- /datum/objective/custom
- )
-
- for(var/T in allowed_types)
- var/datum/objective/X = T
- choices[initial(X.name)] = T
-
if(old_objective)
- if(old_objective.name in choices)
+ if(old_objective.name in GLOB.admin_objective_list)
def_value = old_objective.name
- var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in choices
- selected_type = choices[selected_type]
+ var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in GLOB.admin_objective_list
+ selected_type = GLOB.admin_objective_list[selected_type]
if (!selected_type)
return
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 88dbc6bfb9b0..6d77197b6e03 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -1,3 +1,5 @@
+GLOBAL_LIST(admin_objective_list) //Prefilled admin assignable objective list
+
/datum/objective
var/datum/mind/owner //The primary owner of the objective. !!SOMEWHAT DEPRECATED!! Prefer using 'team' for new code.
var/datum/team/team //An alternative to 'owner': a team. Use this when writing new code.
@@ -985,5 +987,28 @@ GLOBAL_LIST_EMPTY(possible_items_special)
explanation_text = "Have X or more heads of staff escape on the shuttle disguised as heads, while the real heads are dead"
command_staff_only = TRUE
+//Ideally this would be all of them but laziness and unusual subtypes
+/proc/generate_admin_objective_list()
+ GLOB.admin_objective_list = list()
-
+ var/list/allowed_types = list(
+ /datum/objective/assassinate,
+ /datum/objective/maroon,
+ /datum/objective/debrain,
+ /datum/objective/protect,
+ /datum/objective/destroy,
+ /datum/objective/hijack,
+ /datum/objective/escape,
+ /datum/objective/survive,
+ /datum/objective/martyr,
+ /datum/objective/steal,
+ /datum/objective/download,
+ /datum/objective/nuclear,
+ /datum/objective/capture,
+ /datum/objective/absorb,
+ /datum/objective/custom
+ )
+
+ for(var/T in allowed_types)
+ var/datum/objective/X = T
+ GLOB.admin_objective_list[initial(X.name)] = T
\ No newline at end of file
diff --git a/code/modules/admin/check_antagonists.dm b/code/modules/admin/check_antagonists.dm
index 8292bea1bbed..95490b9b30e9 100644
--- a/code/modules/admin/check_antagonists.dm
+++ b/code/modules/admin/check_antagonists.dm
@@ -164,7 +164,8 @@
dat += "Living crew limit: [CONFIG_GET(number/midround_antag_life_check) * 100]% of crew alive
"
dat += "If limits past: [SSticker.mode.round_ends_with_antag_death ? "End The Round" : "Continue As Extended"]
"
dat += "End Round Now
"
- dat += "[SSticker.delay_end ? "End Round Normally" : "Delay Round End"]"
+ dat += "[SSticker.delay_end ? "End Round Normally" : "Delay Round End"]
"
+ dat += "Check Teams"
var/connected_players = GLOB.clients.len
var/lobby_players = 0
var/observers = 0
diff --git a/code/modules/admin/team_panel.dm b/code/modules/admin/team_panel.dm
new file mode 100644
index 000000000000..2b16a66e14ac
--- /dev/null
+++ b/code/modules/admin/team_panel.dm
@@ -0,0 +1,183 @@
+//Split into Team List -> Team Details ?
+/datum/admins/proc/team_listing()
+ var/list/content = list()
+ for(var/datum/team/T in GLOB.antagonist_teams)
+ content += "
[T.name] - [T.type]
"
+ content += "Rename"
+ content += "Delete"
+ content += "Communicate"
+ for(var/command in T.get_admin_commands())
+ content += "[command]"
+ content += "
"
+ content += "Objectives:
"
+ for(var/datum/objective/O in T.objectives)
+ content += "- [O.explanation_text] - Remove
"
+ content += "
Add Objective
"
+ content += "Members:
"
+ for(var/datum/mind/M in T.members)
+ content += "- [M.name] - Remove Member
"
+ content += "
Add Member"
+ content += "
"
+ content += "Create Team
"
+ return content.Join()
+
+
+/datum/admins/proc/check_teams()
+ if(!SSticker.HasRoundStarted())
+ alert("The game hasn't started yet!")
+ return
+
+ var/datum/browser/popup = new(usr, "teams", "Team Listing", 500, 500)
+ popup.set_content(team_listing())
+ popup.open()
+
+/datum/admins/proc/admin_create_team(mob/user)
+ var/team_name = stripped_input(user,"Team name ?")
+ if(!team_name)
+ return
+ var/datum/team/custom/T = new()
+ T.name = team_name
+
+ message_admins("[key_name_admin(usr)] created new [name] antagonist team.")
+ log_admin("[key_name(usr)] created new [name] antagonist team.")
+
+/datum/team/proc/admin_rename(mob/user)
+ var/old_name = name
+ var/team_name = stripped_input(user,"new team name ?","Team rename",old_name)
+ if(!team_name)
+ return
+ name = team_name
+ message_admins("[key_name_admin(usr)] renamed [old_name] team to [name]")
+ log_admin("[key_name(usr)] renamed [old_name] team to [name]")
+
+/datum/team/proc/admin_communicate(mob/user)
+ var/message = input(user,"Message for the team ?","Team Message") as text|null
+ if(!message)
+ return
+ for(var/datum/mind/M in members)
+ to_chat(M.current,message)
+
+ message_admins("[key_name_admin(usr)] messaged [name] team with : [message]")
+ log_admin("Team Message: [key_name(usr)] -> [name] team : [message]")
+
+/datum/team/proc/admin_add_objective(mob/user)
+ //any antag with get_team == src => add objective to that antag
+ //otherwise create new custom antag
+ if(!GLOB.admin_objective_list)
+ generate_admin_objective_list()
+
+ var/selected_type = input("Select objective type:", "Objective type") as null|anything in GLOB.admin_objective_list
+ selected_type = GLOB.admin_objective_list[selected_type]
+ if (!selected_type)
+ return
+
+ var/datum/objective/O = new selected_type
+ O.team = src
+ O.admin_edit(user)
+ objectives |= O
+
+ var/custom_antag_name
+
+ for(var/datum/mind/M in members)
+ var/datum/antagonist/team_antag
+ for(var/datum/antagonist/A in M.antag_datums)
+ if(A.get_team() == src)
+ team_antag = A
+ if(!team_antag)
+ team_antag = new /datum/antagonist/custom
+ if(!custom_antag_name)
+ custom_antag_name = stripped_input(user, "Custom team antagonist name:", "Custom antag", "Antagonist")
+ if(!custom_antag_name)
+ custom_antag_name = "Team Member"
+ team_antag.name = custom_antag_name
+ M.add_antag_datum(team_antag,src)
+ team_antag.objectives |= O
+
+ message_admins("[key_name_admin(usr)] added objective \"[O.explanation_text]\" to [name]")
+ log_admin("[key_name(usr)] added objective \"[O.explanation_text]\" to [name]")
+
+/datum/team/proc/admin_remove_objective(mob/user,datum/objective/O)
+ for(var/datum/mind/M in members)
+ for(var/datum/antagonist/A in M.antag_datums)
+ A.objectives -= O
+ objectives -= O
+
+ message_admins("[key_name_admin(usr)] removed objective \"[O.explanation_text]\" from [name]")
+ log_admin("[key_name(usr)] removed objective \"[O.explanation_text]\" from [name]")
+ //qdel maybe
+
+/datum/team/proc/admin_add_member(mob/user)
+ var/list/minds = list()
+ for(var/mob/M in GLOB.mob_list)
+ if(M.mind)
+ minds |= M.mind
+ var/datum/mind/value = input("Select new member:", "New team member", null) as null|anything in minds
+ if (!value)
+ return
+
+ message_admins("[key_name_admin(usr)] added [value.name] as a member of [name] team")
+ log_admin("[key_name(usr)] added [value.name] as a member of [name] team")
+
+ add_member(value)
+
+/datum/team/proc/admin_remove_member(mob/user,datum/mind/M)
+ message_admins("[key_name_admin(usr)] removed [M.name] from [name] team")
+ log_admin("[key_name(usr)] removed [M.name] from [name] team")
+ remove_member(M)
+
+//After a bit of consideration i block team deletion if there's any members left until unified objective handling is in.
+/datum/team/proc/admin_delete(mob/user)
+ if(members.len > 0)
+ to_chat(user,"Team has members left, remove them first and make sure you know what you're doing.")
+ return
+ qdel(src)
+
+/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
+
+/datum/team/proc/get_admin_commands()
+ return list()
+
+//Custom team subtype created by the panel, allow forcing hud for the team for now
+/datum/team/custom
+ var/datum/atom_hud/antag/custom_hud
+ var/custom_hud_state = "traitor"
+
+/datum/team/custom/add_member(datum/mind/new_member)
+ . = ..()
+ if(custom_hud)
+ custom_hud.join_hud(new_member.current)
+ set_antag_hud(new_member.current,custom_hud_state)
+
+/datum/team/custom/remove_member(datum/mind/member)
+ . = ..()
+ if(custom_hud)
+ custom_hud.leave_hud(member.current)
+
+/datum/team/custom/get_admin_commands()
+ . = ..()
+ .["Force HUD"] = CALLBACK(src,.proc/admin_force_hud)
+
+//This is here if you want admin created teams to tell each other apart easily.
+/datum/team/custom/proc/admin_force_hud(mob/user)
+ var/list/possible_icons = icon_states('icons/mob/hud.dmi')
+ var/new_hud_state = input(user,"Choose hud icon state","Custom HUD","traitor") as null|anything in possible_icons
+ if(!new_hud_state)
+ return
+ //suppose could ask for color too
+ custom_hud_state = new_hud_state
+ custom_hud = new
+ custom_hud.self_visible = TRUE
+ GLOB.huds += custom_hud //Make it show in admin hud
+
+ for(var/datum/mind/M in members)
+ custom_hud.join_hud(M.current)
+ set_antag_hud(M.current,custom_hud_state)
\ No newline at end of file
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 1f1594bb8071..7dd24f5f3c42 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -2556,6 +2556,54 @@
else if(answer == "no")
log_query_debug("[usr.key] | Reported no server hang")
+ 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
+ switch(href_list["team_command"])
+ if("create_team")
+ admin_create_team(usr)
+ if("rename_team")
+ var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams
+ if(T)
+ T.admin_rename(usr)
+ if("communicate")
+ var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams
+ if(T)
+ T.admin_communicate(usr)
+ if("delete_team")
+ var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams
+ if(T)
+ T.admin_delete(usr)
+ if("add_objective")
+ var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams
+ if(T)
+ T.admin_add_objective(usr)
+ if("remove_objective")
+ var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams
+ if(!T)
+ return
+ var/datum/objective/O = locate(href_list["tobjective"]) in T.objectives
+ if(O)
+ T.admin_remove_objective(usr,O)
+ if("add_member")
+ var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams
+ if(T)
+ T.admin_add_member(usr)
+ if("remove_member")
+ var/datum/team/T = locate(href_list["team"]) in GLOB.antagonist_teams
+ if(!T)
+ return
+ var/datum/mind/M = locate(href_list["tmember"]) in T.members
+ if(M)
+ T.admin_remove_member(usr,M)
+ check_teams()
+
+
/datum/admins/proc/HandleCMode()
if(!check_rights(R_ADMIN))
return
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index 3bb47139e0c2..2c7a698b5911 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -219,6 +219,13 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/custom
antagpanel_category = "Custom"
show_name_in_check_antagonists = TRUE //They're all different
+ var/datum/team/custom_team
+
+datum/antagonist/custom/create_team(datum/team/team)
+ custom_team = team
+
+/datum/antagonist/custom/get_team()
+ return custom_team
/datum/antagonist/custom/admin_add(datum/mind/new_owner,mob/admin)
var/custom_name = stripped_input(admin, "Custom antagonist name:", "Custom antag", "Antagonist")
diff --git a/code/modules/antagonists/_common/antag_team.dm b/code/modules/antagonists/_common/antag_team.dm
index 486b5b04147e..d1771ce3d9e8 100644
--- a/code/modules/antagonists/_common/antag_team.dm
+++ b/code/modules/antagonists/_common/antag_team.dm
@@ -1,3 +1,5 @@
+GLOBAL_LIST_EMPTY(antagonist_teams)
+
//A barebones antagonist team.
/datum/team
var/list/datum/mind/members = list()
@@ -7,6 +9,7 @@
/datum/team/New(starting_members)
. = ..()
+ GLOB.antagonist_teams += src
if(starting_members)
if(islist(starting_members))
for(var/datum/mind/M in starting_members)
@@ -14,6 +17,10 @@
else
add_member(starting_members)
+/datum/team/Destroy(force, ...)
+ GLOB.antagonist_teams -= src
+ . = ..()
+
/datum/team/proc/is_solo()
return members.len == 1
diff --git a/tgstation.dme b/tgstation.dme
index 568faad3fc94..a10b48fae368 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1082,6 +1082,7 @@
#include "code\modules\admin\sound_emitter.dm"
#include "code\modules\admin\sql_message_system.dm"
#include "code\modules\admin\stickyban.dm"
+#include "code\modules\admin\team_panel.dm"
#include "code\modules\admin\topic.dm"
#include "code\modules\admin\whitelist.dm"
#include "code\modules\admin\DB_ban\functions.dm"