diff --git a/code/datums/skills/uscm.dm b/code/datums/skills/uscm.dm
index d3f4823286..871278ff23 100644
--- a/code/datums/skills/uscm.dm
+++ b/code/datums/skills/uscm.dm
@@ -75,6 +75,7 @@ United States Colonial Marines
SKILL_ENDURANCE = SKILL_ENDURANCE_TRAINED,
SKILL_JTAC = SKILL_JTAC_TRAINED,
SKILL_INTEL = SKILL_INTEL_TRAINED,
+ SKILL_PILOT = SKILL_PILOT_EXPERT,
)
/datum/skills/intel
@@ -290,6 +291,7 @@ COMMAND STAFF
SKILL_JTAC = SKILL_JTAC_EXPERT,
SKILL_INTEL = SKILL_INTEL_TRAINED,
SKILL_SURGERY = SKILL_SURGERY_NOVICE,
+ SKILL_PILOT = SKILL_PILOT_EXPERT,
)
/datum/skills/SEA
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 114964464a..3f9dc09ae6 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -524,6 +524,27 @@
qdel(signal)
..()
+/obj/effect/landmark/rappel
+ name = "Rappel Point"
+ var/datum/cas_signal/signal
+ invisibility_value = SEE_INVISIBLE_OBSERVER
+ icon_state = "o_green"
+
+/obj/effect/landmark/rappel/New()
+ . = ..()
+ signal = new(src)
+ signal.target_id = ++cas_tracking_id_increment
+ name = "Rappel Point #[signal.target_id]"
+ signal.name = name
+ cas_groups[FACTION_MARINE].add_signal(signal)
+
+/obj/effect/landmark/rappel/Destroy()
+ if(signal)
+ cas_groups[FACTION_MARINE].remove_signal(signal)
+ QDEL_NULL(signal)
+ return ..()
+
+
/// Signal flares deployed by a flare gun
/obj/item/device/flashlight/flare/signal/gun
activate_message = FALSE
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 3f4e721d59..3a752b43f6 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -330,6 +330,7 @@ var/list/roundstart_mod_verbs = list(
add_verb(src, /client/proc/toggle_join_xeno)
add_verb(src, /client/proc/game_master_rename_platoon)
add_verb(src, /client/proc/toggle_vehicle_blockers)
+ add_verb(src, /client/proc/toggle_rappel_menu)
if(CLIENT_HAS_RIGHTS(src, R_SERVER))
add_verb(src, admin_verbs_server)
if(CLIENT_HAS_RIGHTS(src, R_DEBUG))
@@ -363,6 +364,7 @@ var/list/roundstart_mod_verbs = list(
/client/proc/toggle_join_xeno,
/client/proc/game_master_rename_platoon,
/client/proc/toggle_vehicle_blockers,
+ /client/proc/toggle_rappel_menu,
admin_verbs_admin,
admin_verbs_ban,
admin_verbs_minor_event,
diff --git a/code/modules/admin/game_master/extra_buttons/rappel_menu.dm b/code/modules/admin/game_master/extra_buttons/rappel_menu.dm
new file mode 100644
index 0000000000..f35cbae24e
--- /dev/null
+++ b/code/modules/admin/game_master/extra_buttons/rappel_menu.dm
@@ -0,0 +1,108 @@
+GLOBAL_LIST_EMPTY(game_master_rappels)
+GLOBAL_DATUM_INIT(rappel_panel, /datum/rappel_menu, new)
+#define RAPPEL_CLICK_INTERCEPT_ACTION "rappel_click_intercept_action"
+
+/client/proc/toggle_rappel_menu()
+ set name = "Rappel Menu"
+ set category = "Game Master.Extras"
+ if(!check_rights(R_ADMIN))
+ return
+
+ GLOB.rappel_panel.tgui_interact(mob)
+
+/datum/rappel_menu
+ var/rappel_click_intercept = FALSE
+
+/datum/rappel_menu/ui_data(mob/user)
+ . = ..()
+
+ var/list/data = list()
+
+ data["game_master_rappels"] = length(GLOB.game_master_rappels) ? GLOB.game_master_rappels : ""
+ data["rappel_click_intercept"] = rappel_click_intercept
+ return data
+
+/datum/rappel_menu/proc/InterceptClickOn(mob/user, params, atom/object)
+ var/list/modifiers = params2list(params)
+ if(rappel_click_intercept)
+ var/turf/object_turf = get_turf(object)
+ if(LAZYACCESS(modifiers, MIDDLE_CLICK))
+ for(var/obj/effect/landmark/rappel/R in object_turf)
+ GLOB.game_master_rappels -= R
+ QDEL_NULL(R)
+ return TRUE
+
+ var/obj/effect/landmark/rappel/rappel = new(object_turf)
+ var/rappel_ref = REF(rappel)
+ GLOB.game_master_rappels += list(list(
+ "rappel" = rappel,
+ "rappel_name" = rappel.name,
+ "rappel_ref" = rappel_ref,
+ ))
+ return TRUE
+
+/datum/rappel_menu/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "GameMasterRappelMenu", "Rappel Menu")
+ ui.open()
+ user.client?.click_intercept = src
+/datum/rappel_menu/ui_status(mob/user, datum/ui_state/state)
+ return UI_INTERACTIVE
+
+
+/datum/rappel_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+
+ switch(action)
+ if("remove_rappel")
+ if(!params["val"])
+ return
+
+ var/list/rappel = params["val"]
+
+ var/atom/rappel_atom = locate(rappel["rappel_ref"])
+
+ if(!rappel_atom)
+ return TRUE
+
+ if(tgui_alert(ui.user, "Do you want to remove [rappel_atom] ?", "Confirmation", list("Yes", "No")) != "Yes")
+ return TRUE
+
+ remove_rappel(rappel_atom)
+
+ if("jump_to_rappel")
+ if(!params["val"])
+ return
+
+ var/list/rappel = params["val"]
+
+ var/atom/rappel_atom = locate(rappel["rappel_ref"])
+
+ var/turf/rappel_turf = get_turf(rappel_atom)
+
+ if(!rappel_turf)
+ return TRUE
+
+ var/client/jumping_client = ui.user.client
+ jumping_client.jump_to_turf(rappel_turf)
+ return TRUE
+
+ if("toggle_click_rappel")
+ rappel_click_intercept = !rappel_click_intercept
+ return
+
+/datum/rappel_menu/ui_close(mob/user)
+ var/client/user_client = user.client
+ if(user_client?.click_intercept == src)
+ user_client.click_intercept = null
+
+ rappel_click_intercept = FALSE
+
+/datum/rappel_menu/proc/remove_rappel(obj/removing_datum)
+ SIGNAL_HANDLER
+
+ for(var/list/cycled_rappel in GLOB.game_master_rappels)
+ if(cycled_rappel["rappel"] == removing_datum)
+ GLOB.game_master_rappels.Remove(list(cycled_rappel))
+ QDEL_NULL(removing_datum)
diff --git a/colonialmarines.dme b/colonialmarines.dme
index d1942e6e66..2bf0329801 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -1382,6 +1382,7 @@
#include "code\modules\admin\ToRban.dm"
#include "code\modules\admin\game_master\game_master.dm"
#include "code\modules\admin\game_master\game_master_submenu.dm"
+#include "code\modules\admin\game_master\extra_buttons\rappel_menu.dm"
#include "code\modules\admin\game_master\extra_buttons\rename_platoon.dm"
#include "code\modules\admin\game_master\extra_buttons\toggle_join_xeno.dm"
#include "code\modules\admin\game_master\extra_buttons\toggle_vehicle_blockers.dm"
diff --git a/tgui/packages/tgui/interfaces/GameMasterRappelMenu.js b/tgui/packages/tgui/interfaces/GameMasterRappelMenu.js
new file mode 100644
index 0000000000..f1e3286db4
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/GameMasterRappelMenu.js
@@ -0,0 +1,75 @@
+import { useBackend } from '../backend';
+import { Button, Section, Collapsible, Stack, Divider } from '../components';
+import { Window } from '../layouts';
+
+export const GameMasterRappelMenu = (props, context) => {
+ const { data, act } = useBackend(context);
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export const GameMasterRappelPanel = (props, context) => {
+ const { data, act } = useBackend(context);
+
+ return (
+
+
+
+
+ {data.game_master_rappels && (
+
+
+
+ {data.game_master_rappels.map((val) => {
+ if (val) {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+ })}
+
+
+
+
+ )}
+
+
+ );
+};