diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 5e9116c8696..d8357fd0208 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -185,7 +185,7 @@
#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24"
#define ui_ghost_teleport "SOUTH:6,CENTER:24"
#define ui_ghost_pai "SOUTH: 6, CENTER+1:24"
-#define ui_ghost_mafia "SOUTH: 6, CENTER+2:24"
+#define ui_ghost_minigames "SOUTH: 6, CENTER+2:24"
#define ui_ghost_language_menu "SOUTH: 22, CENTER+3:8"
//Blobbernauts
diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm
index 7efab81cc42..eeb8dccb933 100644
--- a/code/_onclick/hud/ghost.dm
+++ b/code/_onclick/hud/ghost.dm
@@ -45,13 +45,13 @@
var/mob/dead/observer/G = usr
G.register_pai()
-/atom/movable/screen/ghost/mafia
- name = "Mafia Signup"
- icon_state = "mafia"
-
-/atom/movable/screen/ghost/mafia/Click()
- var/mob/dead/observer/G = usr
- G.mafia_signup()
+/atom/movable/screen/ghost/minigames_menu
+ name ="Minigames"
+ icon_state = "minigames"
+
+/atom/movable/screen/ghost/minigames_menu/Click()
+ var/mob/dead/observer/observer = usr
+ observer.open_minigames_menu()
/datum/hud/ghost/New(mob/owner)
..()
@@ -82,8 +82,8 @@
using.hud = src
static_inventory += using
- using = new /atom/movable/screen/ghost/mafia()
- using.screen_loc = ui_ghost_mafia
+ using = new /atom/movable/screen/ghost/minigames_menu()
+ using.screen_loc = ui_ghost_minigames
using.hud = src
static_inventory += using
diff --git a/code/datums/minigames_menu.dm b/code/datums/minigames_menu.dm
new file mode 100644
index 00000000000..9a058024b23
--- /dev/null
+++ b/code/datums/minigames_menu.dm
@@ -0,0 +1,47 @@
+/datum/minigames_menu
+ var/mob/dead/observer/owner
+
+/datum/minigames_menu/New(mob/dead/observer/new_owner)
+ if(!istype(new_owner))
+ qdel(src)
+ owner = new_owner
+
+/datum/minigames_menu/Destroy()
+ owner = null
+ return ..()
+
+/datum/minigames_menu/ui_state(mob/user)
+ return GLOB.observer_state
+
+/datum/minigames_menu/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "MinigamesMenu")
+ ui.open()
+
+/datum/minigames_menu/ui_act(action, params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("mafia")
+ ui.close()
+ mafia()
+ return TRUE
+ if("ctf")
+ ui.close()
+ ctf()
+ return TRUE
+
+/datum/minigames_menu/proc/mafia()
+ var/datum/mafia_controller/game = GLOB.mafia_game //this needs to change if you want multiple mafia games up at once.
+ if(!game)
+ game = create_mafia_game("mafia")
+ game.ui_interact(usr)
+
+/datum/minigames_menu/proc/ctf()
+ var/datum/ctf_panel/ctf_panel
+ if(!ctf_panel)
+ ctf_panel = new(src)
+ ctf_panel.ui_interact(usr)
diff --git a/code/modules/capture_the_flag/_defines.dm b/code/modules/capture_the_flag/_defines.dm
new file mode 100644
index 00000000000..96079739950
--- /dev/null
+++ b/code/modules/capture_the_flag/_defines.dm
@@ -0,0 +1 @@
+#define CTF_REQUIRED_PLAYERS 4
diff --git a/code/modules/capture_the_flag/ctf_game.dm b/code/modules/capture_the_flag/ctf_game.dm
index fe8fac882e4..5b2e31cef0a 100644
--- a/code/modules/capture_the_flag/ctf_game.dm
+++ b/code/modules/capture_the_flag/ctf_game.dm
@@ -6,7 +6,6 @@
#define FLAG_RETURN_TIME 200 // 20 seconds
#define INSTAGIB_RESPAWN 50 //5 seconds
#define DEFAULT_RESPAWN 150 //15 seconds
-#define CTF_REQUIRED_PLAYERS 4
/obj/item/ctf
name = "banner"
@@ -638,4 +637,3 @@
#undef FLAG_RETURN_TIME
#undef INSTAGIB_RESPAWN
#undef DEFAULT_RESPAWN
-#undef CTF_REQUIRED_PLAYERS
diff --git a/code/modules/capture_the_flag/ctf_map_loading.dm b/code/modules/capture_the_flag/ctf_map_loading.dm
index 8efaaa751d3..60212cd8b44 100644
--- a/code/modules/capture_the_flag/ctf_map_loading.dm
+++ b/code/modules/capture_the_flag/ctf_map_loading.dm
@@ -54,7 +54,7 @@ GLOBAL_DATUM(ctf_spawner, /obj/effect/landmark/ctf)
description = "The original CTF map."
mappath = "_maps/map_files/CTF/classic.dmm"
-/datum/map_template/ctf/fourSide
+/datum/map_template/ctf/four_side
name = "Four Side"
description = "A CTF map created to demonstrate 4 team CTF, features a single centred flag rather than one per team."
mappath = "_maps/map_files/CTF/fourSide.dmm"
diff --git a/code/modules/capture_the_flag/ctf_panel.dm b/code/modules/capture_the_flag/ctf_panel.dm
new file mode 100644
index 00000000000..a12eeaa08dc
--- /dev/null
+++ b/code/modules/capture_the_flag/ctf_panel.dm
@@ -0,0 +1,52 @@
+GLOBAL_DATUM_INIT(ctf_panel, /datum/ctf_panel, new())
+
+/datum/ctf_panel
+
+/datum/ctf_panel/ui_state(mob/user)
+ return GLOB.observer_state
+
+/datum/ctf_panel/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "CTFPanel")
+ ui.open()
+
+/datum/ctf_panel/ui_data(mob/user)
+ var/list/data = list()
+ data["teams"] = list()
+ data["enabled"] = ""
+ for(var/obj/machinery/capture_the_flag/team in GLOB.machines)
+ var/list/this = list()
+ this["name"] = team
+ this["color"] = team.team
+ this["score"] = team.points + team.control_points
+ this["team_size"] = team.team_members.len
+ this["refs"] += "[REF(team)]"
+ data["teams"] += list(this)
+ if(!data["enabled"])
+ if(team.ctf_enabled)
+ data["enabled"] = "CTF is currently running!"
+ else
+ data["enabled"] = "CTF needs [CTF_REQUIRED_PLAYERS] players to start, currently [team.people_who_want_to_play.len]/[CTF_REQUIRED_PLAYERS] have signed up!"
+ return data
+
+
+/datum/ctf_panel/ui_act(action, params, datum/tgui/ui)
+ .= ..()
+ if(.)
+ return
+ var/mob/user = ui.user
+
+ switch(action)
+ if("jump")
+ var/obj/machinery/capture_the_flag/ctf_spawner = locate(params["refs"])
+ if(istype(ctf_spawner))
+ user.forceMove(get_turf(ctf_spawner))
+ return TRUE
+ if("join")
+ var/obj/machinery/capture_the_flag/ctf_spawner = locate(params["refs"])
+ if(istype(ctf_spawner))
+ if(ctf_spawner.ctf_enabled)
+ user.forceMove(get_turf(ctf_spawner))
+ ctf_spawner.attack_ghost(user)
+ return TRUE
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index bd4ea297b93..65f5eee14a1 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -60,6 +60,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
// of the mob
var/deadchat_name
var/datum/spawners_menu/spawners_menu
+ var/datum/minigames_menu/minigames_menu
/mob/dead/observer/Initialize(mapload)
set_invisibility(GLOB.observer_default_invisibility)
@@ -67,7 +68,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
add_verb(src, list(
/mob/dead/observer/proc/dead_tele,
/mob/dead/observer/proc/open_spawners_menu,
- /mob/dead/observer/proc/tray_view))
+ /mob/dead/observer/proc/tray_view,
+ /mob/dead/observer/proc/open_minigames_menu))
if(icon_state in GLOB.ghost_forms_with_directions_list)
ghostimage_default = image(src.icon,src,src.icon_state + "_nodir")
@@ -176,6 +178,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
updateallghostimages()
QDEL_NULL(spawners_menu)
+ QDEL_NULL(minigames_menu)
return ..()
/*
@@ -1000,6 +1003,20 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
spawners_menu.ui_interact(src)
+/mob/dead/observer/proc/open_minigames_menu()
+ set name = "Minigames Menu"
+ set desc = "See all currently available minigames"
+ set category = "Ghost"
+ if(!client)
+ return
+ if(!isobserver(src))
+ to_chat(usr, span_warning("You must be a ghost to play minigames!"))
+ return
+ if(!minigames_menu)
+ minigames_menu = new(src)
+
+ minigames_menu.ui_interact(src)
+
/mob/dead/observer/proc/tray_view()
set category = "Ghost"
set name = "T-ray view"
diff --git a/icons/hud/screen_ghost.dmi b/icons/hud/screen_ghost.dmi
index c7f5d7d5d11..532fee175a8 100644
Binary files a/icons/hud/screen_ghost.dmi and b/icons/hud/screen_ghost.dmi differ
diff --git a/strings/tips.txt b/strings/tips.txt
index 3a4c045fdfe..7eeb9be0646 100644
--- a/strings/tips.txt
+++ b/strings/tips.txt
@@ -237,7 +237,7 @@ As a Morph, you can talk while disguised, but your words have a chance of being
As a Drone, you can ping other drones to alert them of areas in the station in need of repair.
As a Ghost, you can see the inside of a container on the ground by clicking on it.
As a Ghost, you can double click on just about anything to follow it. Or just warp around!
-As a Ghost, you can both start and join capture the flag games by clicking on one of the team spawners, which can be found under the "Misc" section of the orbit menu.
+As a Ghost, you can both start and join capture the flag games through the minigames menu, or by clicking on one of the team spawners, which can be found under the "Misc" section of the orbit menu.
As a Security Officer, remember that correlation does not equal causation. Someone may have just been at the wrong place at the wrong time!
As a Security Officer, remember that you can attach a sec-lite to your disabler or your helmet!
You can swap floor tiles by holding a crowbar in one hand and a stack of tiles in the other.
diff --git a/tgstation.dme b/tgstation.dme
index 26ab4f4456e..3914d8b48c4 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -492,6 +492,7 @@
#include "code\datums\hud.dm"
#include "code\datums\map_config.dm"
#include "code\datums\mind.dm"
+#include "code\datums\minigames_menu.dm"
#include "code\datums\movement_detector.dm"
#include "code\datums\mutable_appearance.dm"
#include "code\datums\numbered_display.dm"
@@ -2101,10 +2102,12 @@
#include "code\modules\buildmode\submodes\smite.dm"
#include "code\modules\buildmode\submodes\throwing.dm"
#include "code\modules\buildmode\submodes\variable_edit.dm"
+#include "code\modules\capture_the_flag\_defines.dm"
#include "code\modules\capture_the_flag\ctf_classes.dm"
#include "code\modules\capture_the_flag\ctf_equipment.dm"
#include "code\modules\capture_the_flag\ctf_game.dm"
#include "code\modules\capture_the_flag\ctf_map_loading.dm"
+#include "code\modules\capture_the_flag\ctf_panel.dm"
#include "code\modules\capture_the_flag\medieval_sim\medisim_classes.dm"
#include "code\modules\capture_the_flag\medieval_sim\medisim_game.dm"
#include "code\modules\cargo\bounty.dm"
diff --git a/tgui/packages/tgui/interfaces/CTFPanel.js b/tgui/packages/tgui/interfaces/CTFPanel.js
new file mode 100644
index 00000000000..d9c9bdf25d3
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CTFPanel.js
@@ -0,0 +1,68 @@
+import { useBackend } from '../backend';
+import { Box, Button, Section, Flex, Stack, Divider } from '../components';
+import { Window } from '../layouts';
+
+export const CTFPanel = (props, context) => {
+ const { act, data } = useBackend(context);
+ const teams = data.teams || [];
+ const enabled = data.enabled || [];
+ return (
+
+
+
+ {enabled}
+
+
+
+
+
+ {teams.map(team => (
+
+
+
+
+
+ {team.team_size} member
+ {team.team_size === 1 ? "" : "s"}
+
+
+
+
+
+ {team.score} point
+ {team.score === 1 ? "" : "s"}
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/MinigamesMenu.js b/tgui/packages/tgui/interfaces/MinigamesMenu.js
new file mode 100644
index 00000000000..4c8bb9ebd7e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MinigamesMenu.js
@@ -0,0 +1,40 @@
+import { useBackend } from '../backend';
+import { Button, Section, Stack } from '../components';
+import { Window } from '../layouts';
+
+export const MinigamesMenu = (props, context) => {
+ const { act } = useBackend(context);
+ return (
+
+
+
+
+
+ act('ctf')}
+ />
+
+
+ act('mafia')}
+ />
+
+
+
+
+
+ );
+};