diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index b5cac72b7803..b60d02c79d2c 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -26,3 +26,8 @@ _build_dependencies.sh @AffectedArc07
dreamchecker.exe @AffectedArc07
rust_g.dll @AffectedArc07
librust_g.so @AffectedArc07
+
+### S34NW
+
+# TGUI stuff
+/tgui/bin @S34NW
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index f22c1134b10f..ae777746daf3 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -157,7 +157,7 @@ if(thing == TRUE)
return "bleh"
var/other_thing = pick(TRUE, FALSE)
if(other_thing == FALSE)
- return "meh"
+ return "meh"
// Good
var/thing = pick(TRUE, FALSE)
@@ -165,7 +165,7 @@ if(thing)
return "bleh"
var/other_thing = pick(TRUE, FALSE)
if(!other_thing)
- return "meh"
+ return "meh"
```
### Use `pick(x, y, z)`, not `pick(list(x, y, z))`
@@ -452,6 +452,32 @@ Look for code examples on how to properly use it.
addtimer(CALLBACK(target, PROC_REF(dothing), arg1, arg2, arg3), 5 SECONDS)
```
+### Signals
+
+Signals are a slightly more advanced topic, but are often useful for attaching external behavior to objects that should be triggered when a specific event occurs.
+
+When defining procs that should be called by signals, you must include `SIGNAL_HANDLER` after the proc header. This ensures that no sleeping code can be called from within a signal handler, as that can cause problems with the signal system.
+
+Since callbacks can be connected to many signals with `RegisterSignal`, it can be difficult to pin down the source that a callback is invoked from. Any new `SIGNAL_HANDLER` should be followed by a comment listing the signals that the proc is expected to be invoked for. If there are multiple signals to be handled, separate them with a `+`.
+
+```dm
+/atom/movable/proc/when_moved(atom/movable/A)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
+ do_something()
+
+/datum/component/foo/proc/on_enter(datum/source, atom/enterer)
+ SIGNAL_HANDLER // COMSIG_ATOM_ENTERED + COMSIG_ATOM_INITIALIZED_ON
+ do_something_else()
+```
+
+If your proc does have something that needs to sleep (such as a `do_after()`), do not simply omit the `SIGNAL_HANDLER`. Instead, call the sleeping code with `INVOKE_ASYNC` from within the signal handling function.
+
+```dm
+/atom/movable/proc/when_moved(atom/movable/A)
+ SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
+ INVOKE_ASYNC(src, PROC_REF(thing_that_sleeps), arg1)
+```
+
### Operators
#### Spacing of operators
diff --git a/SQL/updates/53-54.sql b/SQL/updates/53-54.sql
index 303de0b32be3..c2fb1988a299 100644
--- a/SQL/updates/53-54.sql
+++ b/SQL/updates/53-54.sql
@@ -2,4 +2,4 @@
#Add a choice for what type of brain borgs want to have
ALTER TABLE `characters`
- ADD COLUMN `cyborg_brain_type` VARCHAR(11) NOT NULL DEFAULT 'MMI' AFTER `height`;
+ ADD COLUMN `cyborg_brain_type` ENUM('MMI', 'Robobrain', 'Positronic') NOT NULL DEFAULT 'MMI' AFTER `height`;
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/intactemptyship.dmm b/_maps/map_files/RandomRuins/SpaceRuins/intactemptyship.dmm
index a00c71b1e94d..89383c9e77cd 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/intactemptyship.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/intactemptyship.dmm
@@ -126,6 +126,11 @@
/obj/structure/bed,
/turf/simulated/floor/mineral/titanium/purple,
/area/ruin/space/powered)
+"Q" = (
+/obj/structure/table/wood,
+/obj/item/blank_tarot_card,
+/turf/simulated/floor/mineral/titanium/purple,
+/area/ruin/space/powered)
(1,1,1) = {"
a
@@ -277,7 +282,7 @@ b
j
r
w
-n
+Q
j
b
a
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm b/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm
index cd0277ea77cc..ce511619893d 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/listeningpost.dmm
@@ -551,7 +551,6 @@
info = "Nothing of interest to report.";
name = "november report"
},
-/obj/item/pen,
/obj/item/tape,
/obj/item/radio/intercom{
freerange = 1;
@@ -559,7 +558,7 @@
name = "intercom"
},
/obj/item/paper_bin,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/obj/effect/decal/cleanable/dirt,
/turf/simulated/floor/plasteel/dark,
/area/ruin/space/syndicate_listening_station)
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm b/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm
index 52cb532a602d..b38b995bef2f 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/mechtransport.dmm
@@ -523,7 +523,7 @@
/obj/item/paper_bin{
pixel_x = -6
},
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/mineral/plastitanium,
/area/ruin/space/mech_transport)
"MA" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/moonoutpost19.dmm b/_maps/map_files/RandomRuins/SpaceRuins/moonoutpost19.dmm
index 7fcf54e36ba7..349e4181c28b 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/moonoutpost19.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/moonoutpost19.dmm
@@ -5200,6 +5200,7 @@
},
/obj/item/coin/antagtoken/syndicate,
/obj/structure/table,
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/plating/asteroid/ancient,
/area/ruin/space/moonbase19)
"rk" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndicatedruglab.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndicatedruglab.dmm
index 83a335cc9b13..643d69228c37 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/syndicatedruglab.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/syndicatedruglab.dmm
@@ -98,11 +98,11 @@
pixel_x = 4
},
/obj/item/paper/syndicate_druglab,
-/obj/item/pen,
/obj/item/flashlight/lamp/green/off{
pixel_y = 12;
pixel_x = -6
},
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/carpet/black,
/area/ruin/space/syndicate_druglab)
"qa" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm
index 9c2fcf54cdc0..51db92f2ef44 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm
@@ -2985,7 +2985,7 @@
dir = 8
},
/obj/item/hand_labeler,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/plasteel{
dir = 9;
icon_state = "darkgreen"
@@ -3423,7 +3423,7 @@
"th" = (
/obj/structure/table,
/obj/item/paper_bin,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/obj/structure/extinguisher_cabinet{
name = "north bump";
pixel_y = 30
@@ -3760,7 +3760,7 @@
/area/ruin/unpowered/syndicate_space_base/service)
"vd" = (
/obj/structure/table,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/obj/item/paper_bin,
/obj/item/stamp/syndicate,
/obj/machinery/light_switch{
@@ -8462,7 +8462,7 @@
dir = 8
},
/obj/item/hand_labeler,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/plasteel{
dir = 10;
icon_state = "darkgreen"
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm
index eebd64133f93..c8c725c60a69 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm
@@ -498,6 +498,7 @@
/obj/machinery/light/small{
dir = 4
},
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/engine,
/area/ruin/space/unpowered)
"tX" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndiedepot.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndiedepot.dmm
index fc3e95325ab7..5164e88c3292 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/syndiedepot.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/syndiedepot.dmm
@@ -665,7 +665,7 @@
"bV" = (
/obj/structure/table,
/obj/item/folder/syndicate/yellow,
-/obj/item/pen,
+/obj/item/pen/multi/syndicate,
/turf/simulated/floor/plasteel{
icon_state = "dark"
},
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm b/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm
index d1f0175e1f43..8434558c253c 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/wizardcrash.dmm
@@ -167,6 +167,7 @@
/area/ruin/space/unpowered)
"aF" = (
/obj/structure/table/wood,
+/obj/item/blank_tarot_card,
/turf/simulated/floor/wood{
icon_state = "wood-broken6"
},
diff --git a/code/__DEFINES/dcs/dcs_helpers.dm b/code/__DEFINES/dcs/dcs_helpers.dm
index ba2b9a704a32..c5c7e3c42dd9 100644
--- a/code/__DEFINES/dcs/dcs_helpers.dm
+++ b/code/__DEFINES/dcs/dcs_helpers.dm
@@ -10,10 +10,6 @@
/// Every proc you pass to RegisterSignal must have this.
#define SIGNAL_HANDLER SHOULD_NOT_SLEEP(TRUE)
-/// Signifies that this proc is used to handle signals, but also sleeps.
-/// Do not use this for new work.
-#define SIGNAL_HANDLER_DOES_SLEEP
-
/// A wrapper for _AddElement that allows us to pretend we're using normal named arguments
#define AddElement(arguments...) _AddElement(list(##arguments))
/// A wrapper for _RemoveElement that allows us to pretend we're using normal named arguments
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index f10dc5d3334a..1d4d33ac2644 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -1061,3 +1061,6 @@
/// Used by admin-tooling to remove radiation
#define COMSIG_ADMIN_DECONTAMINATE "admin_decontaminate"
+
+/// Sent when bodies transfer between shades/shards and constructs
+#define COMSIG_SHADE_TO_CONSTRUCT_TRANSFER "shade_to_construct_transfer"
diff --git a/code/__DEFINES/preferences_defines.dm b/code/__DEFINES/preferences_defines.dm
index 61d0ef1652df..162df1bb9737 100644
--- a/code/__DEFINES/preferences_defines.dm
+++ b/code/__DEFINES/preferences_defines.dm
@@ -17,7 +17,7 @@
#define PREFTOGGLE_CHAT_DEAD (1<<1)
#define PREFTOGGLE_CHAT_GHOSTEARS (1<<2)
#define PREFTOGGLE_CHAT_GHOSTSIGHT (1<<3)
-#define PREFTOGGLE_CHAT_PRAYER (1<<4)
+#define PREFTOGGLE_CHAT_PRAYER (1<<4) // Defunct
#define PREFTOGGLE_CHAT_RADIO (1<<5)
// #define PREFTOGGLE_AZERTY (1<<6) // obsolete
#define PREFTOGGLE_CHAT_DEBUGLOGS (1<<7)
@@ -81,6 +81,21 @@
#error toggles_2 bitflag over 16777215. Please make an issue report and postpone the feature you are working on.
#endif
+// This is a list index. Required to start at 1 instead of 0 so it's properly placed in the list
+#define PREFTOGGLE_CATEGORY_GENERAL 1
+#define PREFTOGGLE_CATEGORY_LIVING 2
+#define PREFTOGGLE_CATEGORY_GHOST 3
+#define PREFTOGGLE_CATEGORY_ADMIN 4
+
+// Preftoggle type defines
+/// Special toggles, stuff that just overrides set_toggles entirely
+#define PREFTOGGLE_SPECIAL 0
+/// Interacts with the sound bitflag
+#define PREFTOGGLE_SOUND 1
+/// Interacts with the toggles bitflag
+#define PREFTOGGLE_TOGGLE1 2
+/// Interacts with the toggles2 bitflag
+#define PREFTOGGLE_TOGGLE2 3
// Admin attack logs filter system, see /proc/add_attack_logs and /proc/msg_admin_attack
@@ -133,6 +148,7 @@
#define TAB_ANTAG 2
#define TAB_GEAR 3
#define TAB_KEYS 4
+#define TAB_TOGGLES 5
// Colourblind modes
#define COLOURBLIND_MODE_NONE "None"
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 4f185e0f70a4..d34fb4868293 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -18,6 +18,8 @@
#define STATUS_EFFECT_SHADOW_MEND /datum/status_effect/shadow_mend //Quick, powerful heal that deals damage afterwards. Heals 15 brute/burn every second for 3 seconds.
#define STATUS_EFFECT_VOID_PRICE /datum/status_effect/void_price //The price of healing yourself with void energy. Deals 3 brute damage every 3 seconds for 30 seconds.
+#define STATUS_EFFECT_SHADOW_MEND_DEVIL /datum/status_effect/shadow_mend/devil //Tarot version, hurts others over self
+
#define STATUS_EFFECT_HIPPOCRATIC_OATH /datum/status_effect/hippocraticOath //Gives you an aura of healing as well as regrowing the Rod of Asclepius if lost
#define STATUS_EFFECT_REGENERATIVE_CORE /datum/status_effect/regenerative_core
@@ -37,6 +39,8 @@
#define STATUS_EFFECT_BLOODDRUNK /datum/status_effect/blooddrunk //Stun immunity and greatly reduced damage taken
+#define STATUS_EFFECT_BLOODDRUNK_CHARIOT /datum/status_effect/blooddrunk/chariot //adds pacifism
+
#define STATUS_EFFECT_DASH /datum/status_effect/dash // Grants the ability to dash, expiring after a few secodns
/// Rapid burn/brute/oxy/blood healing from the cling ability
@@ -62,6 +66,12 @@
#define STATUS_EFFECT_BEARSERKER_RAGE /datum/status_effect/bearserker_rage
+#define STATUS_EFFECT_XRAY /datum/status_effect/xray // Xray vision for 2 minutes
+
+#define STATUS_EFFECT_BADASS /datum/status_effect/badass // Badass trait for 2 minutes.
+
+#define STATUS_EFFECT_REVERSED_SUN /datum/status_effect/reversed_sun // Weaker eternal darkness, nightvision, but nearsight
+
/////////////
// DEBUFFS //
/////////////
@@ -114,6 +124,8 @@
#define STATUS_EFFECT_PEPPERSPRAYED /datum/status_effect/pepper_spray
+#define STATUS_EFFECT_REVERSED_HIGH_PRIESTESS /datum/status_effect/reversed_high_priestess //Bubblegum will chase the person hit by the effect, grabbing people at random. This can and WILL include the caster.
+
//#define STATUS_EFFECT_NECROPOLIS_CURSE /datum/status_effect/necropolis_curse
//#define CURSE_BLINDING 1 //makes the edges of the target's screen obscured
//#define CURSE_SPAWNING 2 //spawns creatures that attack the target only
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index 97c9fe44971c..174ecddda53c 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -139,6 +139,11 @@
if(initial(D.name))
GLOB.keybindings += new path()
+ for(var/path in subtypesof(/datum/preference_toggle))
+ var/datum/preference_toggle/pref_toggle = path
+ if(initial(pref_toggle.name))
+ GLOB.preference_toggles += new path()
+
for(var/path in subtypesof(/datum/objective))
var/datum/objective/O = path
if(isnull(initial(O.name)))
diff --git a/code/_globalvars/lists/preference_toggle_lists.dm b/code/_globalvars/lists/preference_toggle_lists.dm
new file mode 100644
index 000000000000..bc7081595017
--- /dev/null
+++ b/code/_globalvars/lists/preference_toggle_lists.dm
@@ -0,0 +1,8 @@
+GLOBAL_LIST_EMPTY(preference_toggles)
+
+GLOBAL_LIST_INIT(preference_toggle_groups, list(
+ "General Preferences" = PREFTOGGLE_CATEGORY_GENERAL,
+ "In-Round Preferences" = PREFTOGGLE_CATEGORY_LIVING,
+ "Ghost Preferences" = PREFTOGGLE_CATEGORY_GHOST,
+ "Admin Preferences" = PREFTOGGLE_CATEGORY_ADMIN,
+))
diff --git a/code/_onclick/hud/human_hud.dm b/code/_onclick/hud/human_hud.dm
index 4a036cf6e85c..43ab2c14b69b 100644
--- a/code/_onclick/hud/human_hud.dm
+++ b/code/_onclick/hud/human_hud.dm
@@ -483,16 +483,3 @@
H.r_hand.screen_loc = null
if(H.l_hand)
H.l_hand.screen_loc = null
-
-
-/mob/living/carbon/human/verb/toggle_hotkey_verbs()
- set category = "OOC"
- set name = "Toggle Hotkey Buttons"
- set desc = "This disables or enables the user interface buttons which can be used with hotkeys."
-
- if(hud_used.hotkey_ui_hidden)
- client.screen += hud_used.hotkeybuttons
- hud_used.hotkey_ui_hidden = FALSE
- else
- client.screen -= hud_used.hotkeybuttons
- hud_used.hotkey_ui_hidden = TRUE
diff --git a/code/controllers/configuration/sections/general_configuration.dm b/code/controllers/configuration/sections/general_configuration.dm
index 35477712f338..40b1729b7603 100644
--- a/code/controllers/configuration/sections/general_configuration.dm
+++ b/code/controllers/configuration/sections/general_configuration.dm
@@ -18,7 +18,7 @@
var/allow_antag_hud = TRUE
/// Forbid players from rejoining if they use AntagHUD?
var/restrict_antag_hud_rejoin = TRUE
- /// Enable respanws by default?
+ /// Enable respawns by default?
var/respawn_enabled = FALSE
/// Enable CID randomiser buster?
var/enabled_cid_randomiser_buster = FALSE
@@ -93,8 +93,8 @@
CONFIG_LOAD_BOOL(guest_ban, data["guest_ban"])
CONFIG_LOAD_BOOL(allow_antag_hud, data["allow_antag_hud"])
CONFIG_LOAD_BOOL(restrict_antag_hud_rejoin, data["restrict_antag_hud_rejoin"])
- CONFIG_LOAD_BOOL(respawn_enabled, data["respawn_enabled"])
CONFIG_LOAD_BOOL(enabled_cid_randomiser_buster, data["enable_cid_randomiser_buster"])
+ CONFIG_LOAD_BOOL(respawn_enabled, data["respawn_enabled"])
CONFIG_LOAD_BOOL(forbid_singulo_possession, data["prevent_admin_singlo_possession"])
CONFIG_LOAD_BOOL(popup_admin_pm, data["popup_admin_pm"])
CONFIG_LOAD_BOOL(allow_holidays, data["allow_holidays"])
diff --git a/code/controllers/subsystem/non_firing/SSchangelog.dm b/code/controllers/subsystem/non_firing/SSchangelog.dm
index acfe7484f9de..53830ebce022 100644
--- a/code/controllers/subsystem/non_firing/SSchangelog.dm
+++ b/code/controllers/subsystem/non_firing/SSchangelog.dm
@@ -97,7 +97,7 @@ SUBSYSTEM_DEF(changelog)
/client/verb/changes()
set name = "Changelog"
set desc = "View the changelog."
- set category = "OOC"
+ set category = null
// Just invoke the actual CL thing
SSchangelog.OpenChangelog(src)
diff --git a/code/datums/action.dm b/code/datums/action.dm
index 511865a1c61a..eb6a1a19005f 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -699,7 +699,7 @@
// Make a holder for the charge text
var/image/count_down_holder = image('icons/effects/effects.dmi', icon_state = "nothing")
count_down_holder.plane = FLOAT_PLANE + 1.1
- var/text = S.cooldown_handler.statpanel_info()
+ var/text = S.cooldown_handler.cooldown_info()
count_down_holder.maptext = "
[text]
"
button.add_overlay(count_down_holder)
diff --git a/code/datums/components/cult_held_body.dm b/code/datums/components/cult_held_body.dm
new file mode 100644
index 000000000000..24deac16f3b4
--- /dev/null
+++ b/code/datums/components/cult_held_body.dm
@@ -0,0 +1,76 @@
+/**
+ * A component for tracking and manipulating bodies held inside constructs/shades
+ * Will drop the body/brain when the parent dies or is deleted.
+ */
+/datum/component/construct_held_body
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+ /// A reference to either a mob, or a brain organ that is held inside our parent. Will drop when parent dies/is deleted
+ var/atom/movable/held_body
+
+/datum/component/construct_held_body/Initialize(atom/movable/new_body)
+ . = ..()
+ add_body(new_body)
+
+/datum/component/construct_held_body/Destroy(force, silent)
+ if(held_body)
+ stack_trace("/datum/component/construct_held_body had a held body still despite being destroyed. Body is [held_body] ([held_body.type])")
+ held_body = null
+ return ..()
+
+/datum/component/construct_held_body/PostTransfer()
+ held_body.forceMove(parent) // forcemove them to the new parent
+
+/datum/component/construct_held_body/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_MOB_DEATH, PROC_REF(drop_body))
+ RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(drop_body))
+ RegisterSignal(parent, COMSIG_SHADE_TO_CONSTRUCT_TRANSFER, PROC_REF(transfer_held_body))
+
+/datum/component/construct_held_body/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_MOB_DEATH)
+ UnregisterSignal(parent, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(parent, COMSIG_SHADE_TO_CONSTRUCT_TRANSFER)
+
+/datum/component/construct_held_body/proc/add_body(atom/movable/new_body)
+ held_body = new_body
+ RegisterSignal(new_body, COMSIG_PARENT_QDELETING, PROC_REF(_null_held_body))
+ new_body.forceMove(parent)
+
+/datum/component/construct_held_body/proc/_null_held_body()
+ SIGNAL_HANDLER // COMSIG_PARENT_QDELETING
+ UnregisterSignal(held_body, COMSIG_PARENT_QDELETING)
+ held_body = null
+
+/datum/component/construct_held_body/proc/transfer_held_body(mob/living/current_parent, mob/living/new_body_holder)
+ SIGNAL_HANDLER // COMSIG_SHADE_TO_CONSTRUCT_TRANSFER
+ new_body_holder.TakeComponent(src)
+
+/datum/component/construct_held_body/proc/drop_body()
+ SIGNAL_HANDLER // COMSIG_MOB_DEATH + COMSIG_PARENT_QDELETING
+ INVOKE_ASYNC(src, PROC_REF(_drop_body))
+
+/datum/component/construct_held_body/proc/_drop_body() // call me lazy ig
+ if(!held_body) // Null check for empty bodies
+ return
+ var/mob/living/parent_mob = parent
+ held_body.forceMove(get_turf(parent))
+ SSticker.mode?.cult_team?.add_cult_immunity(held_body)
+
+ var/mob/living/held_mob = held_body
+ if(ismob(held_body)) // Check if the held_body is a mob
+ held_mob.key = parent_mob.key
+ held_mob.cancel_camera()
+ else if(istype(held_body, /obj/item/organ/internal/brain)) // Check if the held_body is a brain
+ var/obj/item/organ/internal/brain/brain = held_body
+ if(brain.brainmob) // Check if the brain has a brainmob
+ brain.brainmob.key = parent_mob.key // Set the key to the brainmob
+ held_mob = brain.brainmob
+
+ if(!istype(held_mob) || QDELETED(held_mob))
+ CRASH("/datum/component/construct_held_body/proc/_drop_body attempted to drop a body despite having no body, or a qdeleted body")
+
+ parent_mob.mind.transfer_to(held_mob) // Transfer the mind to the original mob
+ // goodbye construct antag datums!
+ held_mob.mind.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
+ held_mob.mind.remove_antag_datum(/datum/antagonist/wizard/construct, silent_removal = TRUE)
+ held_body = null
+ qdel(src) // our job here is done
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 9abedd63a0d4..01e94bffe157 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -537,7 +537,10 @@
/client/proc/view_var_Topic(href, href_list, hsrc)
//This should all be moved over to datum/admins/Topic() or something ~Carn
- if(!check_rights(R_ADMIN|R_MOD, FALSE) && !((href_list["datumrefresh"] || href_list["Vars"] || href_list["VarsList"]) && check_rights(R_VIEWRUNTIMES, FALSE)))
+ if(!check_rights(R_ADMIN|R_MOD, FALSE) \
+ && !((href_list["datumrefresh"] || href_list["Vars"] || href_list["VarsList"]) && check_rights(R_VIEWRUNTIMES, FALSE)) \
+ && !((href_list["proc_call"]) && check_rights(R_PROCCALL, FALSE)) \
+ )
return // clients with R_VIEWRUNTIMES can still refresh the window/view references/view lists. they cannot edit anything else however.
if(view_var_Topic_list(href, href_list, hsrc)) // done because you can't use UIDs with lists and I don't want to snowflake into the below check to supress warnings
diff --git a/code/datums/spell.dm b/code/datums/spell.dm
index cc6a40a91ad5..5d5aaba3c1a2 100644
--- a/code/datums/spell.dm
+++ b/code/datums/spell.dm
@@ -65,8 +65,6 @@ GLOBAL_LIST_INIT(spells, typesof(/datum/spell))
/datum/spell
var/name = "Spell" // Only rename this if the spell you're making is not abstract
var/desc = "A wizard spell"
- var/panel = "Spells"//What panel the proc holder needs to go on.
-
var/school = "evocation" //not relevant at now, but may be important later if there are changes to how spells work. the ones I used for now will probably be changed... maybe spell presets? lacking flexibility but with some other benefit?
///recharge time in deciseconds
var/base_cooldown = 10 SECONDS
diff --git a/code/datums/spell_cooldown/spell_charges.dm b/code/datums/spell_cooldown/spell_charges.dm
index 577da557060e..47803609ab25 100644
--- a/code/datums/spell_cooldown/spell_charges.dm
+++ b/code/datums/spell_cooldown/spell_charges.dm
@@ -49,7 +49,7 @@
..()
charge_time = world.time
-/datum/spell_cooldown/charges/statpanel_info()
+/datum/spell_cooldown/charges/cooldown_info()
var/charge_string = charge_duration != 0 ? round(min(1, (charge_duration - (charge_time - world.time)) / charge_duration), 0.01) * 100 : 100 // need this for possible 0 charge duration
var/recharge_string = recharge_duration != 0 ? round(min(1, (recharge_duration - (recharge_time - world.time)) / recharge_duration), 0.01) * 100 : 100
return "[charge_string != 100 ? "[charge_string]%\n" : ""][recharge_string != 100 ? "[recharge_string]%\n" : ""][current_charges]/[max_charges]"
diff --git a/code/datums/spell_cooldown/spell_cooldown.dm b/code/datums/spell_cooldown/spell_cooldown.dm
index 14936e893ac3..9ac6dbf4520f 100644
--- a/code/datums/spell_cooldown/spell_cooldown.dm
+++ b/code/datums/spell_cooldown/spell_cooldown.dm
@@ -64,5 +64,5 @@
/datum/spell_cooldown/proc/revert_cast()
recharge_time = world.time
-/datum/spell_cooldown/proc/statpanel_info()
+/datum/spell_cooldown/proc/cooldown_info()
return "[round(get_availability_percentage(), 0.01) * 100]%"
diff --git a/code/datums/spells/bloodcrawl.dm b/code/datums/spells/bloodcrawl.dm
index b54ca987de37..e5c99fac868a 100644
--- a/code/datums/spells/bloodcrawl.dm
+++ b/code/datums/spells/bloodcrawl.dm
@@ -8,7 +8,6 @@
overlay = null
action_icon_state = "bloodcrawl"
action_background_icon_state = "bg_demon"
- panel = "Demon"
var/allowed_type = /obj/effect/decal/cleanable
var/phased = FALSE
diff --git a/code/datums/spells/mime.dm b/code/datums/spells/mime.dm
index 9fe9431dc8d4..5f483b4edf83 100644
--- a/code/datums/spells/mime.dm
+++ b/code/datums/spells/mime.dm
@@ -2,7 +2,6 @@
name = "Invisible Wall"
desc = "The mime's performance transmutates into physical reality."
school = "mime"
- panel = "Mime"
summon_type = list(/obj/structure/forcefield/mime)
invocation_type = "emote"
invocation_emote_self = "You form a wall in front of yourself."
@@ -32,7 +31,6 @@
name = "Speech"
desc = "Make or break a vow of silence."
school = "mime"
- panel = "Mime"
clothes_req = FALSE
base_cooldown = 5 MINUTES
human_req = TRUE
@@ -66,7 +64,6 @@
name = "Invisible Greater Wall"
desc = "Form an invisible three tile wide blockade."
school = "mime"
- panel = "Mime"
wall_type = /obj/effect/forcefield/mime/advanced
invocation_type = "emote"
invocation_emote_self = "You form a blockade in front of yourself."
@@ -91,7 +88,6 @@
name = "Finger Gun"
desc = "Shoot lethal, silencing bullets out of your fingers! 3 bullets available per cast. Use your fingers to holster them manually."
school = "mime"
- panel = "Mime"
clothes_req = FALSE
base_cooldown = 30 SECONDS
human_req = TRUE
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index 646b40e4d26f..38f94e4ce892 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -62,6 +62,11 @@
id = "shadow_mend"
duration = 3 SECONDS
alert_type = /atom/movable/screen/alert/status_effect/shadow_mend
+ /// If this is true, the status effect will try to apply the debuff to others, rather than the user
+ var/devil = FALSE
+
+/datum/status_effect/shadow_mend/devil
+ devil = TRUE
/atom/movable/screen/alert/status_effect/shadow_mend
name = "Shadow Mend"
@@ -78,10 +83,25 @@
owner.adjustFireLoss(-15)
/datum/status_effect/shadow_mend/on_remove()
- owner.visible_message("The violet light around [owner] glows black!", "The tendrils around you cinch tightly and reap their toll...")
- playsound(owner, 'sound/magic/teleport_diss.ogg', 50, 1)
- owner.apply_status_effect(STATUS_EFFECT_VOID_PRICE)
+ if(!devil)
+ owner.visible_message("The violet light around [owner] glows black!", "The tendrils around you cinch tightly and reap their toll...")
+ playsound(owner, 'sound/magic/teleport_diss.ogg', 50, TRUE)
+ owner.apply_status_effect(STATUS_EFFECT_VOID_PRICE)
+ return
+ var/found_someone = FALSE
+
+ for(var/mob/living/L in oview(9, owner))
+ found_someone = TRUE
+ playsound(owner, 'sound/magic/teleport_diss.ogg', 50, TRUE)
+ L.Beam(owner, "grabber_beam", time = 1 SECONDS, maxdistance = 9)
+ L.apply_status_effect(STATUS_EFFECT_VOID_PRICE)
+ if(found_someone)
+ owner.visible_message("The violet light around [owner] glows black... and shoots off to those around [owner.p_them()]!", "The tendrils around you cinch tightly... but then unwravel and fly at others!")
+ else
+ owner.visible_message("The violet light around [owner] glows black!", "The tendrils around you cinch tightly and reap their toll...")
+ playsound(owner, 'sound/magic/teleport_diss.ogg', 50, TRUE)
+ owner.apply_status_effect(STATUS_EFFECT_VOID_PRICE)
/datum/status_effect/void_price
id = "void_price"
@@ -110,6 +130,13 @@
tick_interval = 0
alert_type = /atom/movable/screen/alert/status_effect/blooddrunk
var/blooddrunk_damage_mod_remove = 4 // Damage is multiplied by this at the end of the status effect. Modify this one, it changes the _add
+ /// If this is the chariot subtype, which grants pacifism while the effect is active.
+ var/chariot = FALSE
+
+/datum/status_effect/blooddrunk/chariot
+ duration = 10 SECONDS
+ chariot = TRUE
+ blooddrunk_damage_mod_remove = 6
/atom/movable/screen/alert/status_effect/blooddrunk
name = "Blood-Drunk"
@@ -120,6 +147,8 @@
. = ..()
if(.)
ADD_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, "blooddrunk")
+ if(chariot)
+ ADD_TRAIT(owner, TRAIT_PACIFISM, "blooddrunk")
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
var/blooddrunk_damage_mod_add = 1 / blooddrunk_damage_mod_remove // Damage is multiplied by this at the start of the status effect. Don't modify this one directly.
@@ -144,6 +173,7 @@
H.physiology.stamina_mod *= blooddrunk_damage_mod_remove
add_attack_logs(owner, owner, "lost blood-drunk stun immunity", ATKLOG_ALL)
REMOVE_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, "blooddrunk")
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, "blooddrunk")
if(islist(owner.stun_absorption) && owner.stun_absorption["blooddrunk"])
owner.remove_stun_absorption("blooddrunk")
@@ -183,7 +213,7 @@
H.physiology.stamina_mod *= 0.5
H.physiology.stun_mod *= 0.5
var/datum/antagonist/vampire/V = owner.mind.has_antag_datum(/datum/antagonist/vampire)
- if(V.get_ability(/datum/vampire_passive/blood_swell_upgrade))
+ if(V?.get_ability(/datum/vampire_passive/blood_swell_upgrade))
bonus_damage_applied = TRUE
H.physiology.melee_bonus += 10
H.dna.species.punchstunthreshold += 8 //higher chance to stun but not 100%
@@ -776,3 +806,70 @@
var/mob/living/carbon/human/H = owner
H.physiology.stamina_mod /= 0.75
add_attack_logs(owner, owner, "lost bearserker rage resistances", ATKLOG_ALL)
+
+/datum/status_effect/xray
+ id = "xray"
+ alert_type = null
+ status_type = STATUS_EFFECT_REFRESH
+ duration = 2 MINUTES
+ tick_interval = 0
+
+/datum/status_effect/xray/on_apply()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_XRAY_VISION, "XRAY_BUFF")
+ ADD_TRAIT(owner, TRAIT_NIGHT_VISION, "XRAY_BUFF")
+ owner.update_sight()
+
+/datum/status_effect/xray/on_remove()
+ . = ..()
+ REMOVE_TRAIT(owner, TRAIT_XRAY_VISION, "XRAY_BUFF")
+ REMOVE_TRAIT(owner, TRAIT_NIGHT_VISION, "XRAY_BUFF")
+ owner.update_sight()
+
+/datum/status_effect/badass
+ id = "badass"
+ alert_type = null
+ status_type = STATUS_EFFECT_REFRESH
+ duration = 2 MINUTES
+ tick_interval = 0
+
+/datum/status_effect/badass/on_apply()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_BADASS, "BADDASS_BUFF")
+
+/datum/status_effect/badass/on_remove()
+ . = ..()
+ REMOVE_TRAIT(owner, TRAIT_BADASS, "BADDASS_BUFF")
+
+/datum/status_effect/reversed_sun
+ id = "reversed_sun"
+ alert_type = null
+ status_type = STATUS_EFFECT_REFRESH
+ duration = 1 MINUTES
+ tick_interval = 0.2 SECONDS
+
+/datum/status_effect/reversed_sun/on_apply()
+ . = ..()
+ owner.become_nearsighted("REVERSED_SUN")
+ ADD_TRAIT(owner, TRAIT_NIGHT_VISION, "REVERSED_SUN")
+ owner.update_sight()
+ owner.set_light(7, -5, "#ddd6cf")
+
+/datum/status_effect/reversed_sun/on_remove()
+ . = ..()
+ owner.remove_light()
+ owner.cure_nearsighted("REVERSED_SUN")
+ REMOVE_TRAIT(owner, TRAIT_NIGHT_VISION, "REVERSED_SUN")
+ owner.update_sight()
+
+/datum/status_effect/reversed_sun/tick()
+ for(var/atom/movable/AM in oview(8, owner))
+ if(isliving(AM))
+ var/mob/living/L = AM
+ if(L.affects_vampire(owner))
+ L.adjust_bodytemperature(-1.5 * TEMPERATURE_DAMAGE_COEFFICIENT)
+ continue
+ if(istype(AM, /obj/item/projectile))
+ var/obj/item/projectile/P = AM
+ if(P.flag == ENERGY || P.flag == LASER)
+ P.damage *= 0.85
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 9edf7eb42ad7..e65da3535cd2 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -1322,3 +1322,29 @@
desc = "Real winners quit before they reach the ultimate prize."
#undef DEFAULT_MAX_CURSE_COUNT
+
+/datum/status_effect/reversed_high_priestess
+ id = "reversed_high_priestess"
+ duration = 1 MINUTES
+ status_type = STATUS_EFFECT_REFRESH
+ tick_interval = 6 SECONDS
+ alert_type = /atom/movable/screen/alert/status_effect/bubblegum_curse
+
+/datum/status_effect/reversed_high_priestess/tick()
+ . = ..()
+ new /obj/effect/bubblegum_warning(get_turf(owner))
+
+/obj/effect/bubblegum_warning
+ name = "bloody rift"
+ desc = "You feel like even being *near* this is a bad idea"
+ icon = 'icons/obj/biomass.dmi'
+ icon_state = "rift"
+ color = "red"
+
+/obj/effect/bubblegum_warning/Initialize()
+ . = ..()
+ addtimer(CALLBACK(src, PROC_REF(slap_someone)), 2.5 SECONDS) //A chance to run away
+
+/obj/effect/bubblegum_warning/proc/slap_someone()
+ new /obj/effect/abstract/bubblegum_rend_helper(get_turf(src), null, 10)
+ qdel(src)
diff --git a/code/datums/status_effects/magic_disguise.dm b/code/datums/status_effects/magic_disguise.dm
index 97a3640de2b3..4a0ed8dceea5 100644
--- a/code/datums/status_effects/magic_disguise.dm
+++ b/code/datums/status_effects/magic_disguise.dm
@@ -4,6 +4,7 @@
tick_interval = -1
alert_type = /atom/movable/screen/alert/status_effect/magic_disguise
status_type = STATUS_EFFECT_REPLACE
+ var/mob/living/disguise_mob
var/datum/icon_snapshot/disguise
/atom/movable/screen/alert/status_effect/magic_disguise
@@ -12,27 +13,26 @@
icon = 'icons/mob/actions/actions.dmi'
icon_state = "chameleon_outfit"
-/datum/status_effect/magic_disguise/on_creation(mob/living/new_owner, mob/living/disguise_mob)
+/datum/status_effect/magic_disguise/on_creation(mob/living/new_owner, mob/living/_disguise_mob)
. = ..()
- if(!ishuman(new_owner))
+ disguise_mob = _disguise_mob
+
+/datum/status_effect/magic_disguise/on_apply()
+ . = ..()
+ if(!ishuman(owner))
return FALSE
if(!disguise_mob)
disguise_mob = select_disguise()
- if(disguise_mob && ishuman(disguise_mob))
+ if(ishuman(disguise_mob))
create_disguise(disguise_mob)
if(disguise)
- apply_disguise(new_owner)
- return TRUE
+ apply_disguise(owner)
else
to_chat(owner, "Your spell fails to find a disguise!")
return FALSE
-/datum/status_effect/magic_disguise/on_apply()
- . = ..()
- if(!ishuman(owner))
- return FALSE
-
RegisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(remove_disguise))
+ return TRUE
/datum/status_effect/magic_disguise/on_remove()
owner.regenerate_icons()
@@ -74,6 +74,7 @@
H.overlays = disguise.overlays
H.update_inv_r_hand()
H.update_inv_l_hand()
+ SEND_SIGNAL(H, COMSIG_CARBON_REGENERATE_ICONS)
to_chat(H, "You disguise yourself as [disguise.name].")
/datum/status_effect/magic_disguise/proc/remove_disguise()
diff --git a/code/datums/uplink_items/uplink_traitor.dm b/code/datums/uplink_items/uplink_traitor.dm
index db79ccb1a6c5..ea331744b392 100644
--- a/code/datums/uplink_items/uplink_traitor.dm
+++ b/code/datums/uplink_items/uplink_traitor.dm
@@ -305,6 +305,17 @@
excludefrom = list(UPLINK_TYPE_NUCLEAR, UPLINK_TYPE_SST)
job = list("Head of Personnel", "Quartermaster", "Cargo Technician", "Librarian", "Coroner", "Psychiatrist", "Virologist")
+// Tarot card generator, librarian and Chaplain.
+
+/datum/uplink_item/jobspecific/tarot_generator
+ name = "Enchanted tarot card deck"
+ desc = "A magic tarot card deck \"borrowed\" from a Wizard federation storage unit. \
+ Capable of producing magic tarot cards of the 22 major arcana, and their reversed versions. Each card has a different effect. \
+ Throw the card at someone to use it on them, or use it in hand to apply it to yourself. Unlimited uses, 25 second cooldown, can have up to 3 cards in the world."
+ reference = "tarot"
+ item = /obj/item/tarot_generator
+ cost = 55 //This can do a lot of stuff, but is quite random. As such, higher price.
+ job = list("Chaplain", "Librarian")
//--------------------------//
// Species Restricted Gear //
diff --git a/code/game/dna/mutations/disabilities.dm b/code/game/dna/mutations/disabilities.dm
index c0140fcb7cac..610a513ab930 100644
--- a/code/game/dna/mutations/disabilities.dm
+++ b/code/game/dna/mutations/disabilities.dm
@@ -490,7 +490,6 @@
/datum/spell/immolate
name = "Incendiary Mitochondria"
desc = "The subject becomes able to convert excess cellular energy into thermal energy."
- panel = "Abilities"
base_cooldown = 600
diff --git a/code/game/dna/mutations/mutation_powers.dm b/code/game/dna/mutations/mutation_powers.dm
index 463b32757867..0c974c59d64b 100644
--- a/code/game/dna/mutations/mutation_powers.dm
+++ b/code/game/dna/mutations/mutation_powers.dm
@@ -277,7 +277,6 @@
/datum/spell/cryokinesis
name = "Cryokinesis"
desc = "Drops the bodytemperature of another person."
- panel = "Abilities"
base_cooldown = 1200
@@ -348,7 +347,6 @@
/datum/spell/eat
name = "Eat"
desc = "Eat just about anything!"
- panel = "Abilities"
base_cooldown = 300
@@ -471,8 +469,6 @@
/datum/spell/leap
name = "Jump"
desc = "Leap great distances!"
- panel = "Abilities"
-
base_cooldown = 60
clothes_req = FALSE
@@ -564,7 +560,6 @@
/datum/spell/polymorph
name = "Polymorph"
desc = "Mimic the appearance of others!"
- panel = "Abilities"
base_cooldown = 1800
clothes_req = FALSE
@@ -726,7 +721,6 @@
/datum/spell/morph
name = "Morph"
desc = "Mimic the appearance of your choice!"
- panel = "Abilities"
base_cooldown = 1800
clothes_req = FALSE
diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm
index 481d2dbfc02c..e298a454a269 100644
--- a/code/game/gamemodes/cult/cult_structures.dm
+++ b/code/game/gamemodes/cult/cult_structures.dm
@@ -318,7 +318,7 @@ GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list(
selection_title = "Archives"
creation_message = "You invoke the dark magic of the tomes creating a %ITEM%!"
choosable_items = list("Shuttle Curse" = /obj/item/shuttle_curse, "Zealot's Blindfold" = /obj/item/clothing/glasses/hud/health/night/cultblind,
- "Veil Shifter" = /obj/item/cult_shift, "Reality sunderer" = /obj/item/portal_amulet) //Add void torch to veil shifter spawn
+ "Veil Shifter" = /obj/item/cult_shift, "Reality sunderer" = /obj/item/portal_amulet, "Blank Tarot Card" = /obj/item/blank_tarot_card)
/obj/structure/cult/functional/archives/Initialize(mapload)
. = ..()
diff --git a/code/game/gamemodes/miniantags/abduction/abduction_gear.dm b/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
index f6181fe104b2..81e111ea154a 100644
--- a/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
+++ b/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
@@ -71,6 +71,7 @@
M.overlays = disguise.overlays
M.update_inv_r_hand()
M.update_inv_l_hand()
+ SEND_SIGNAL(M, COMSIG_CARBON_REGENERATE_ICONS)
/obj/item/clothing/suit/armor/abductor/vest/proc/DeactivateStealth()
if(!stealth_active)
diff --git a/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm
index 5dc9a30ce30f..7edd0bf893ee 100644
--- a/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm
+++ b/code/game/gamemodes/miniantags/demons/shadow_demon/shadow_demon.dm
@@ -187,8 +187,6 @@
action_background_icon_state = "shadow_demon_bg"
action_icon_state = "shadow_grapple"
- panel = "Demon"
-
sound = null
invocation_type = "none"
invocation = null
diff --git a/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter.dm b/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter.dm
index 3812df84a3ab..d2595c8634fc 100644
--- a/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter.dm
+++ b/code/game/gamemodes/miniantags/demons/slaughter_demon/slaughter.dm
@@ -111,7 +111,6 @@
overlay = null
action_icon_state = "bloodcrawl"
action_background_icon_state = "bg_cult"
- panel = "Demon"
/datum/spell/sense_victims/create_new_targeting()
return new /datum/spell_targeting/alive_mob_list
diff --git a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
index 8bb7f2a626c9..b7df938982af 100644
--- a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
+++ b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
@@ -9,7 +9,6 @@
#define PD_UPGRADE_MAX_CHARGE "Capacity"
/datum/spell/pulse_demon
- panel = "Pulse Demon"
school = "pulse demon"
clothes_req = FALSE
action_background_icon_state = "bg_pulsedemon"
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
index 0913180f2ef1..d1962001d880 100644
--- a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
+++ b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
@@ -110,7 +110,6 @@
//Toggle night vision: lets the revenant toggle its night vision
/datum/spell/night_vision/revenant
base_cooldown = 0
- panel = "Revenant Abilities"
message = "You toggle your night vision."
action_icon_state = "r_nightvision"
action_background_icon_state = "bg_revenant"
@@ -119,7 +118,6 @@
/datum/spell/revenant_transmit
name = "Transmit"
desc = "Telepathically transmits a message to the target."
- panel = "Revenant Abilities"
base_cooldown = 0
clothes_req = FALSE
action_icon_state = "r_transmit"
@@ -145,7 +143,6 @@
name = "Spell"
clothes_req = FALSE
action_background_icon_state = "bg_revenant"
- panel = "Revenant Abilities (Locked)"
/// How long it reveals the revenant in deciseconds
var/reveal = 8 SECONDS
/// How long it stuns the revenant in deciseconds
@@ -192,7 +189,6 @@
return FALSE
name = "[initial(name)] ([cast_amount]E)"
to_chat(user, "You have unlocked [initial(name)]!")
- panel = "Revenant Abilities"
locked = FALSE
cooldown_handler.revert_cast()
return FALSE
diff --git a/code/game/gamemodes/wizard/magic_tarot.dm b/code/game/gamemodes/wizard/magic_tarot.dm
new file mode 100644
index 000000000000..e3efcfa81286
--- /dev/null
+++ b/code/game/gamemodes/wizard/magic_tarot.dm
@@ -0,0 +1,885 @@
+/obj/item/tarot_generator
+ name = "Enchanted tarot card deck"
+ desc = "This tarot card box has quite the array of runes and artwork on it."
+ icon = 'icons/obj/playing_cards.dmi'
+ icon_state = "tarot_box"
+ w_class = WEIGHT_CLASS_SMALL
+ /// What is the maximum number of cards the tarot generator can have in the world at a time?
+ var/maximum_cards = 3
+ /// List of cards we have created, to check against maximum, and so we can purge them from the pack.
+ var/list/our_card_list = list()
+ ///How long the cooldown is each time we draw a card before we can draw another?
+ var/our_card_cooldown_time = 25 SECONDS
+ COOLDOWN_DECLARE(card_cooldown)
+
+/obj/item/tarot_generator/wizard
+ maximum_cards = 5
+ our_card_cooldown_time = 12 SECONDS // A minute for a full hand of 5 cards
+
+/obj/item/tarot_generator/attack_self(mob/user)
+ if(!COOLDOWN_FINISHED(src, card_cooldown))
+ to_chat(user, "[src]'s magic is still recovering from the last card, wait [round(COOLDOWN_TIMELEFT(src, card_cooldown) / 10)] more second\s!")
+ return
+ if(length(our_card_list) >= maximum_cards)
+ to_chat(user, "[src]'s magic can only support up to [maximum_cards] in the world at once, use or destroy some!")
+ return
+ var/obj/item/magic_tarot_card/MTC = new /obj/item/magic_tarot_card(get_turf(src), src)
+ our_card_list += MTC
+ user.put_in_hands(MTC)
+ to_chat(user, "You draw [MTC.name]... [MTC.card_desc]") //No period on purpose.
+ COOLDOWN_START(src, card_cooldown, our_card_cooldown_time)
+
+/obj/item/tarot_generator/examine(mob/user)
+ . = ..()
+ . += "Alt-Shift-Click to destroy all cards it has produced."
+ . += "It has [length(our_card_list)] card\s in the world right now."
+ if(!COOLDOWN_FINISHED(src, card_cooldown))
+ . += "You may draw another card again in [round(COOLDOWN_TIMELEFT(src, card_cooldown) / 10)] second\s."
+
+/obj/item/tarot_generator/AltShiftClick(mob/user)
+ for(var/obj/item/magic_tarot_card/MTC in our_card_list)
+ MTC.dust()
+ to_chat(user, "You dispell the cards [src] had created.")
+
+// Booster packs filled with 3, 5, or 7 playing cards! Used by the wizard space ruin, or rarely in lavaland tendril chests.
+/obj/item/tarot_card_pack
+ name = "\improper Enchanted Arcana Pack"
+ desc = "A pack of 3 Enchanted tarot cards. Collect them all!"
+ icon = 'icons/obj/playing_cards.dmi'
+ icon_state = "pack"
+ ///How many cards in a pack. 3 in base, 5 in jumbo, 7 in mega
+ var/cards = 3
+
+/obj/item/tarot_card_pack/attack_self(mob/user)
+ user.visible_message("[user] tears open [src].", \
+ "You tear open [src]!")
+ playsound(loc, 'sound/items/poster_ripped.ogg', 50, TRUE)
+ for(var/i in 1 to cards)
+ new /obj/item/magic_tarot_card(get_turf(src))
+ qdel(src)
+
+/obj/item/tarot_card_pack/jumbo
+ name = "\improper Jumbo Arcana Pack"
+ desc = "A Jumbo card pack from your friend Jimbo!"
+ icon_state = "jumbopack"
+ cards = 5
+
+/obj/item/tarot_card_pack/mega
+ name = "\improper MEGA Arcana Pack"
+ desc = "Sadly, you won't find a Joker for an angel room, or a Soul card in here either."
+ icon_state = "megapack"
+ cards = 7
+
+// Blank tarot cards. Made by the cult, however also good for space ruins potentially, where one feels a card pack would be too much?
+/obj/item/blank_tarot_card
+ name = "blank tarot card"
+ desc = "A blank tarot card."
+ icon = 'icons/obj/playing_cards.dmi'
+ icon_state = "tarot_blank"
+ w_class = WEIGHT_CLASS_TINY
+ throw_speed = 3
+ throw_range = 10
+ throwforce = 0
+ force = 0
+ resistance_flags = FLAMMABLE
+ /// If a person can choose what the card produces. No cost if they can choose.
+ var/let_people_choose = FALSE
+
+/obj/item/blank_tarot_card/examine(mob/user)
+ . = ..()
+ if(!let_people_choose)
+ . += "With a bit of Ink, a work of art could be created. Will you provide your Ink?"
+ else
+ . += "We have the Ink... Could you provide your Vision instead?"
+
+/obj/item/blank_tarot_card/attack_self(mob/user)
+ if(!ishuman(user))
+ return
+ if(!let_people_choose)
+ var/mob/living/carbon/human/H = user
+ if(H.dna && (NO_BLOOD in H.dna.species.species_traits))
+ to_chat(user, "No blood to provide?... Then no Ink for the art...")
+ return
+ if(H.blood_volume <= 100) //Shouldn't happen, they should be dead, but failsafe. Not bleeding as then they could recover the blood with blood rites
+ return
+ H.blood_volume -= 100
+ H.drop_item()
+ var/obj/item/magic_tarot_card/MTC = new /obj/item/magic_tarot_card(get_turf(src))
+ user.put_in_hands(MTC)
+ to_chat(user, "Your blood flows into [src]... And your Ink makes a work of art! [MTC.name]... [MTC.card_desc]") //No period on purpose.
+ qdel(src)
+ return
+ var/tarot_type
+ var/tarot_name
+ var/list/card_by_name = list()
+ for(var/T in subtypesof(/datum/tarot) - /datum/tarot/reversed)
+ var/datum/tarot/temp = T
+ card_by_name[temp.name] = T
+
+ tarot_name = tgui_input_list(user, "Choose the Work of Art to create.", "Art Creation", card_by_name)
+ tarot_type = card_by_name[tarot_name]
+ if(tarot_type)
+ user.drop_item()
+ var/obj/item/magic_tarot_card/MTC = new /obj/item/magic_tarot_card(get_turf(src), null, tarot_type)
+ user.put_in_hands(MTC)
+ to_chat(user, "You put your Vision into [src], and your Vision makes a work of Art! [MTC.name]... [MTC.card_desc]") //No period on purpose.
+ qdel(src)
+
+/obj/item/blank_tarot_card/choose //For admins mainly, to spawn a specific tarot card. Not recommended for ruins.
+ let_people_choose = TRUE
+
+/obj/item/magic_tarot_card
+ name = "XXII - The Unknown"
+ desc = "A beautiful tarot card. However, it feels like... more?"
+ icon = 'icons/obj/playing_cards.dmi'
+ icon_state = "tarot_the_unknown"
+ w_class = WEIGHT_CLASS_TINY
+ throw_speed = 3
+ throw_range = 10
+ throwforce = 0
+ force = 0
+ resistance_flags = FLAMMABLE
+ /// The deck that created us. Notifies it we have been deleted on use.
+ var/obj/item/tarot_generator/creator_deck
+ /// Our magic tarot card datum that lets the tarot card do stuff on use, or hitting someone
+ var/datum/tarot/our_tarot
+ /// Our fancy description given to use by the tarot datum.
+ var/card_desc = "Untold answers... wait what? This is a bug, report this as an issue on github!"
+ ///Is the card face down? Shows the card back, hides the examine / name.
+ var/face_down = FALSE
+
+/obj/item/magic_tarot_card/Initialize(mapload, obj/item/tarot_generator/source, datum/tarot/chosen_tarot)
+ . = ..()
+ if(source)
+ creator_deck = source
+ if(chosen_tarot)
+ our_tarot = new chosen_tarot
+ if(!istype(our_tarot))
+ var/tarotpath = pick(subtypesof(/datum/tarot) - /datum/tarot/reversed)
+ our_tarot = new tarotpath
+ name = our_tarot.name
+ card_desc = our_tarot.desc
+ icon_state = "tarot_[our_tarot.card_icon]"
+
+/obj/item/magic_tarot_card/Destroy()
+ if(creator_deck)
+ creator_deck.our_card_list -= src
+ return ..()
+
+/obj/item/magic_tarot_card/examine(mob/user)
+ . = ..()
+ if(!face_down)
+ . += "[card_desc]"
+ . += "Alt-Shift-Click to flip the card over."
+
+/obj/item/magic_tarot_card/attack_self(mob/user)
+ if(our_tarot)
+ INVOKE_ASYNC(our_tarot, TYPE_PROC_REF(/datum/tarot, activate), user)
+ poof()
+
+/obj/item/magic_tarot_card/throw_at(atom/target, range, speed, mob/thrower, spin, diagonals_first, datum/callback/callback, force, dodgeable)
+ if(face_down)
+ flip()
+ . = ..()
+
+/obj/item/magic_tarot_card/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+ . = ..()
+ if(isliving(hit_atom) && our_tarot)
+ INVOKE_ASYNC(our_tarot, TYPE_PROC_REF(/datum/tarot, activate), hit_atom)
+ poof()
+
+/obj/item/magic_tarot_card/AltShiftClick(mob/user)
+ flip()
+
+/obj/item/magic_tarot_card/proc/flip()
+ if(!face_down)
+ icon_state = "cardback[our_tarot.reversed ? "?" : ""]"
+ name = "Enchanted tarot card"
+ face_down = TRUE
+ else
+ name = our_tarot.name
+ icon_state = "tarot_[our_tarot.card_icon]"
+ face_down = FALSE
+
+/obj/item/magic_tarot_card/proc/poof()
+ new /obj/effect/temp_visual/revenant(get_turf(src))
+ qdel(src)
+
+/obj/item/magic_tarot_card/proc/dust()
+ visible_message("[src] disintegrates into dust!")
+ new /obj/effect/temp_visual/revenant(get_turf(src))
+ qdel(src)
+
+/datum/tarot
+ /// Name used for the card
+ var/name = "XXII - The Unknown."
+ /// Desc used for the card description of the card
+ var/desc = "Untold answers... wait what? This is a bug, report this as an issue on github!"
+ /// What icon is used for the card?
+ var/card_icon = "the_unknown"
+ /// Are we reversed? Used for the card back.
+ var/reversed = FALSE
+
+/datum/tarot/proc/activate(mob/living/target)
+ stack_trace("A bugged tarot card was spawned and used. Please make an issue report! Type was [src.type]")
+
+/datum/tarot/reversed
+ name = "XXII - The Unknown?"
+ desc = "Untold answers... wait what? This is a bug, report this as an issue on github! This one was a reversed arcana!"
+ card_icon = "the_unknown?"
+ reversed = TRUE
+
+/datum/tarot/the_fool
+ name = "0 - The Fool"
+ desc = "Where journey begins."
+ card_icon = "the_fool"
+
+/datum/tarot/the_fool/activate(mob/living/target)
+ target.forceMove(pick(GLOB.latejoin))
+ to_chat(target, "You are abruptly pulled through space!")
+
+/datum/tarot/the_magician
+ name = "I - The Magician"
+ desc = "May you never miss your goal."
+ card_icon = "the_magician"
+
+/datum/tarot/the_magician/activate(mob/living/target)
+ target.apply_status_effect(STATUS_EFFECT_BADASS)
+ to_chat(target, "You feel badass.")
+
+/datum/tarot/the_high_priestess
+ name = "II - The High Priestess"
+ desc = "Mother is watching you."
+ card_icon = "the_high_priestess"
+
+/datum/tarot/the_high_priestess/activate(mob/living/target)
+ new /obj/effect/abstract/bubblegum_rend_helper(get_turf(target), target, 20)
+
+/obj/effect/abstract/bubblegum_rend_helper
+ name = "bubblegum_rend_helper"
+
+/obj/effect/abstract/bubblegum_rend_helper/Initialize(mapload, mob/living/owner, damage)
+ . = ..()
+ INVOKE_ASYNC(src, PROC_REF(rend), owner, damage)
+
+/obj/effect/abstract/bubblegum_rend_helper/proc/rend(mob/living/owner, damage)
+ if(!owner)
+ for(var/mob/living/L in shuffle(view(9, src)))
+ owner = L
+ break
+ owner.Immobilize(3 SECONDS)
+ for(var/i in 1 to 3)
+ var/turf/first_turf = get_turf(owner)
+ new /obj/effect/decal/cleanable/blood/bubblegum(first_turf)
+ if(prob(50))
+ new /obj/effect/temp_visual/bubblegum_hands/rightsmack(first_turf)
+ else
+ new /obj/effect/temp_visual/bubblegum_hands/leftsmack(first_turf)
+ sleep(6)
+ var/turf/second_turf = get_turf(owner)
+ to_chat(owner, "Something huge rends you!")
+ playsound(second_turf, 'sound/misc/demon_attack1.ogg', 100, TRUE, -1)
+ owner.adjustBruteLoss(damage)
+ qdel(src)
+
+/datum/tarot/the_empress
+ name = "III - The Empress"
+ desc = "May your rage bring power."
+ card_icon = "the_empress"
+
+/datum/tarot/the_empress/activate(mob/living/target)
+ if(ishuman(target))
+ var/mob/living/carbon/human/H = target
+ H.reagents.add_reagent("mephedrone", 4.5)
+ H.reagents.add_reagent("mitocholide", 12)
+
+/datum/tarot/the_emperor
+ name = "IV - The Emperor"
+ desc = "Challenge me!"
+ card_icon = "the_emperor"
+
+/datum/tarot/the_emperor/activate(mob/living/target)
+ var/list/L = list()
+ for(var/turf/T in get_area_turfs(/area/station/command/bridge))
+ if(is_blocked_turf(T))
+ continue
+ L.Add(T)
+
+ if(!length(L))
+ to_chat(target, "Huh. No bridge? Well, that sucks.")
+ return
+
+ target.forceMove(pick(L))
+ to_chat(target, "You are abruptly pulled through space!")
+
+/datum/tarot/the_hierophant
+ name = "V - The Hierophant"
+ desc = "Two prayers for the lost."
+ card_icon = "the_hierophant"
+
+/datum/tarot/the_hierophant/activate(mob/living/target)
+ if(!ishuman(target))
+ return
+ var/mob/living/carbon/human/H = target
+ if(!H.wear_suit)
+ return
+ H.wear_suit.setup_hierophant_shielding()
+ H.update_appearance(UPDATE_ICON)
+
+/datum/tarot/the_lovers
+ name = "VI - The Lovers"
+ desc = "May you prosper and be in good health."
+ card_icon = "the_lovers"
+
+/datum/tarot/the_lovers/activate(mob/living/target)
+ if(ishuman(target))
+ var/mob/living/carbon/human/H = target
+ H.adjustBruteLoss(-40, robotic = TRUE)
+ H.adjustFireLoss(-40, robotic = TRUE)
+ H.blood_volume = min(H.blood_volume + 100, BLOOD_VOLUME_NORMAL)
+ else
+ target.adjustBruteLoss(-40)
+ target.adjustFireLoss(-40)
+ target.adjustOxyLoss(-40)
+ target.adjustToxLoss(-40)
+
+/datum/tarot/the_chariot
+ name = "VII - The Chariot"
+ desc = "May nothing stand before you."
+ card_icon = "the_chariot"
+
+/datum/tarot/the_chariot/activate(mob/living/target)
+ target.apply_status_effect(STATUS_EFFECT_BLOOD_RUSH)
+ target.apply_status_effect(STATUS_EFFECT_BLOODDRUNK_CHARIOT)
+
+/datum/tarot/justice
+ name = "VIII - Justice"
+ desc = "May your future become balanced."
+ card_icon = "justice"
+
+/datum/tarot/justice/activate(mob/living/target)
+ var/turf/target_turf = get_turf(target)
+ new /obj/item/storage/firstaid/regular(target_turf)
+ new /obj/item/grenade/chem_grenade/waterpotassium(target_turf)
+ new /obj/item/card/emag/magic_key(target_turf)
+ new /obj/item/stack/spacecash/c100(target_turf)
+
+/datum/tarot/the_hermit
+ name = "IX - The Hermit"
+ desc = "May you see what life has to offer."
+ card_icon = "the_hermit"
+
+/datum/tarot/the_hermit/activate(mob/living/target)
+ var/list/viable_vendors = list()
+ for(var/obj/machinery/economy/vending/candidate in GLOB.machines)
+ if(!is_station_level(candidate.z))
+ continue
+ viable_vendors += candidate
+
+ if(!length(viable_vendors))
+ to_chat(target, "No vending machines? Well, with luck cargo will have something to offer. If you go there yourself.")
+ return
+
+ target.forceMove(get_turf(pick(viable_vendors)))
+ to_chat(target, "You are abruptly pulled through space!")
+
+/datum/tarot/wheel_of_fortune
+ name = "X - Wheel of Fortune"
+ desc = "Spin the wheel of destiny."
+ card_icon = "wheel_of_fortune"
+
+/datum/tarot/wheel_of_fortune/activate(mob/living/target)
+ var/list/static/bad_vendors = list(
+ /obj/machinery/economy/vending/liberationstation,
+ /obj/machinery/economy/vending/toyliberationstation,
+ /obj/machinery/economy/vending/wallmed
+ )
+ var/turf/target_turf = get_turf(target)
+ var/vendorpath = pick(subtypesof(/obj/machinery/economy/vending) - bad_vendors)
+ new vendorpath(target_turf)
+
+/datum/tarot/strength
+ name = "XI - Strength"
+ desc = "May your power bring rage."
+ card_icon = "strength"
+
+/datum/tarot/strength/activate(mob/living/target)
+ target.apply_status_effect(STATUS_EFFECT_VAMPIRE_GLADIATOR)
+ target.apply_status_effect(STATUS_EFFECT_BLOOD_SWELL)
+
+/datum/tarot/the_hanged_man
+ name = "XII - The Hanged Man"
+ desc = "May you find enlightenment."
+ card_icon = "the_hanged_man"
+
+/datum/tarot/the_hanged_man/activate(mob/living/target)
+ if(target.flying)
+ return
+ target.flying = TRUE
+ addtimer(VARSET_CALLBACK(target, flying, FALSE), 60 SECONDS)
+
+/datum/tarot/death
+ name = "XIII - Death"
+ desc = "Lay waste to all that oppose you."
+ card_icon = "death"
+
+/datum/tarot/death/activate(mob/living/target)
+ for(var/mob/living/L in oview(9, target))
+ L.adjustBruteLoss(20)
+ L.adjustFireLoss(20)
+
+/datum/tarot/temperance
+ name = "XIV - Temperance"
+ desc = "May you be pure in heart."
+ card_icon = "temperance"
+
+/datum/tarot/temperance/activate(mob/living/target)
+ if(!ishuman(target))
+ return
+ var/mob/living/carbon/human/H = target
+ var/obj/item/organ/internal/body_egg/egg = H.get_int_organ(/obj/item/organ/internal/body_egg)
+ if(egg)
+ egg.remove(H)
+ H.vomit()
+ egg.forceMove(get_turf(H))
+ H.reagents.add_reagent("mutadone", 1)
+ for(var/obj/item/organ/internal/I in H.internal_organs)
+ I.heal_internal_damage(60)
+ H.apply_status_effect(STATUS_EFFECT_PANACEA)
+ for(var/thing in H.viruses)
+ var/datum/disease/D = thing
+ if(D.severity == NONTHREAT)
+ continue
+ D.cure()
+
+/datum/tarot/the_devil
+ name = "XV - The Devil"
+ desc = "Revel in the power of darkness."
+ card_icon = "the_devil"
+
+/datum/tarot/the_devil/activate(mob/living/target)
+ target.apply_status_effect(STATUS_EFFECT_SHADOW_MEND_DEVIL)
+
+/datum/tarot/the_tower
+ name = "XVI - The Tower"
+ desc = "Destruction brings creation."
+ card_icon = "the_tower"
+
+/datum/tarot/the_tower/activate(mob/living/target)
+ var/obj/item/grenade/clusterbuster/ied/bakoom = new(get_turf(target))
+ bakoom.prime()
+
+/// I'm sorry matt, this is very funny.
+/datum/tarot/the_stars
+ name = "XVII - The Stars"
+ desc = "May you find what you desire."
+ card_icon = "the_stars"
+
+/datum/tarot/the_stars/activate(mob/living/target)
+ var/list/L = list()
+ for(var/turf/T in get_area_turfs(/area/station/security/evidence))
+ if(is_blocked_turf(T))
+ continue
+ L.Add(T)
+
+ if(!length(L))
+ to_chat(target, "Huh. No evidence? Well, that means they can't charge you with a crime, right?")
+ return
+
+ target.forceMove(pick(L))
+ to_chat(target, "You are abruptly pulled through space!")
+ for(var/obj/structure/closet/C in shuffle(view(9, target)))
+ if(istype(C, /obj/structure/closet/secure_closet))
+ var/obj/structure/closet/secure_closet/SC = C
+ SC.locked = FALSE
+ C.open()
+ break //Only open one locker
+
+/datum/tarot/the_moon
+ name = "XVIII - The Moon"
+ desc = "May you find all you have lost."
+ card_icon = "the_moon"
+
+/datum/tarot/the_moon/activate(mob/living/target)
+ var/list/funny_ruin_list = list()
+ var/turf/target_turf = get_turf(target)
+ for(var/I in GLOB.ruin_landmarks)
+ var/obj/effect/landmark/ruin/ruin_landmark = I
+ if(ruin_landmark.z == target_turf.z)
+ funny_ruin_list += ruin_landmark
+
+ if(length(funny_ruin_list))
+ var/turf/T = get_turf(pick(funny_ruin_list))
+ target.forceMove(T)
+ to_chat(target, "You are abruptly pulled through space!")
+ T.ChangeTurf(/turf/simulated/floor/plating) //we give them plating so they are not trapped in a wall, and a pickaxe to avoid being trapped in a wall
+ new /obj/item/pickaxe/emergency(T)
+ target.update_parallax_contents()
+ return
+ //We did not find a ruin on the same level. Well. I hope you have a space suit, but we'll go space ruins as they are mostly sorta kinda safer.
+ for(var/I in GLOB.ruin_landmarks)
+ var/obj/effect/landmark/ruin/ruin_landmark = I
+ if(!is_mining_level(ruin_landmark.z))
+ funny_ruin_list += ruin_landmark
+
+ if(!length(funny_ruin_list))
+ to_chat(target, "Huh. No space ruins? Well, this card is RUINED!")
+
+ var/turf/T = get_turf(pick(funny_ruin_list))
+ target.forceMove(T)
+ to_chat(target, "You are abruptly pulled through space!")
+ T.ChangeTurf(/turf/simulated/floor/plating) //we give them plating so they are not trapped in a wall, and a pickaxe to avoid being trapped in a wall
+ new /obj/item/pickaxe/emergency(T)
+ target.update_parallax_contents()
+ return
+
+/datum/tarot/the_sun
+ name = "XIX - The Sun"
+ desc = "May the light heal and enlighten you."
+ card_icon = "the_sun"
+
+/datum/tarot/the_sun/activate(mob/living/target)
+ target.revive()
+
+/datum/tarot/judgement
+ name = "XX - Judgement"
+ desc = "Judge lest ye be judged."
+ card_icon = "judgement"
+
+/datum/tarot/judgement/activate(mob/living/target)
+ notify_ghosts("[target] has used a judgment card. Judge them. Or not, up to you.", enter_link = "(Click to judge)", source = target, action = NOTIFY_FOLLOW)
+
+/datum/tarot/the_world
+ name = "XXI - The World"
+ desc = "Open your eyes and see."
+ card_icon = "the_world"
+
+/datum/tarot/the_world/activate(mob/living/target)
+ var/datum/effect_system/smoke_spread/bad/smoke = new()
+ smoke.set_up(10, FALSE, target)
+ smoke.start()
+ target.apply_status_effect(STATUS_EFFECT_XRAY)
+
+////////////////////////////////
+////////REVERSED ARCANA/////////
+////////////////////////////////
+
+/datum/tarot/reversed/the_fool
+ name = "0 - The Fool?"
+ desc = "Let go and move on."
+ card_icon = "the_fool?"
+
+/datum/tarot/reversed/the_fool/activate(mob/living/target)
+ if(!ishuman(target))
+ return
+ var/mob/living/carbon/human/H = target
+ for(var/obj/item/I in H)
+ if(istype(/obj/item/bio_chip, I))
+ continue
+ H.unEquip(I)
+
+/datum/tarot/reversed/the_magician
+ name = "I - The Magician?"
+ desc = "May no harm come to you."
+ card_icon = "the_magician?"
+
+/datum/tarot/reversed/the_magician/activate(mob/living/target)
+ var/list/thrown_atoms = list()
+ var/sparkle_path = /obj/effect/temp_visual/gravpush
+ for(var/turf/T in range(5, target)) //Done this way so things don't get thrown all around hilariously.
+ for(var/atom/movable/AM in T)
+ thrown_atoms += AM
+
+ for(var/atom/movable/AM as anything in thrown_atoms)
+ if(AM == target || AM.anchored || (ismob(AM) && !isliving(AM)))
+ continue
+
+ var/throw_target = get_edge_target_turf(target, get_dir(target, get_step_away(AM, target)))
+ var/dist_from_user = get_dist(target, AM)
+ if(dist_from_user == 0)
+ if(isliving(AM))
+ var/mob/living/M = AM
+ M.Weaken(6 SECONDS)
+ M.adjustBruteLoss(10)
+ to_chat(M, "You're slammed into the floor by [name]!")
+ add_attack_logs(target, M, "[M] was thrown by [target]'s [name]", ATKLOG_ALMOSTALL)
+ else
+ new sparkle_path(get_turf(AM), get_dir(target, AM))
+ if(isliving(AM))
+ var/mob/living/M = AM
+ to_chat(M, "You're thrown back by [name]!")
+ add_attack_logs(target, M, "[M] was thrown by [target]'s [name]", ATKLOG_ALMOSTALL)
+ INVOKE_ASYNC(AM, TYPE_PROC_REF(/atom/movable, throw_at), throw_target, ((clamp((3 - (clamp(dist_from_user - 2, 0, dist_from_user))), 3, 3))), 1) //So stuff gets tossed around at the same time.
+
+/datum/tarot/reversed/the_high_priestess
+ name = "II - The High Priestess?"
+ desc = "Run."
+ card_icon = "the_high_priestess?"
+
+/datum/tarot/reversed/the_high_priestess/activate(mob/living/target)
+ target.visible_message("WHO DARES TO TRY TO USE MY POWER IN A CARD?")
+ target.apply_status_effect(STATUS_EFFECT_REVERSED_HIGH_PRIESTESS)
+
+/datum/tarot/reversed/the_empress
+ name = "III - The Empress?"
+ desc = "May your love bring protection."
+ card_icon = "the_empress?"
+
+/datum/tarot/reversed/the_empress/activate(mob/living/target)
+ for(var/mob/living/L in oview(9, target))
+ L.apply_status_effect(STATUS_EFFECT_PACIFIED)
+
+/datum/tarot/reversed/the_emperor
+ name = "IV - The Emperor?"
+ desc = "May you find a worthy opponent."
+ card_icon = "the_emperor?"
+
+/datum/tarot/reversed/the_emperor/activate(mob/living/target)
+ var/list/L = list()
+ var/list/heads = SSticker.mode.get_all_heads()
+ for(var/datum/mind/head in heads)
+ if(ishuman(head.current))
+ L.Add(head.current)
+
+ if(!length(L))
+ to_chat(target, "Huh. No command members? I hope you didn't kill them all already...")
+ return
+
+ target.forceMove(get_turf(pick(L)))
+ to_chat(target, "You are abruptly pulled through space!")
+
+/datum/tarot/reversed/the_hierophant
+ name = "V - The Hierophant?"
+ desc = "Two prayers for the forgotten."
+ card_icon = "the_hierophant?"
+
+/datum/tarot/reversed/the_hierophant/activate(mob/living/target)
+ var/active_chasers = 0
+ for(var/mob/living/M in shuffle(orange(7, target)))
+ if(M.stat == DEAD) //Let us not have dead mobs be used to make a disco inferno.
+ continue
+ if(active_chasers >= 2)
+ return
+ var/obj/effect/temp_visual/hierophant/chaser/C = new(get_turf(target), target, M, 1, FALSE)
+ C.moving = 2
+ C.standard_moving_before_recalc = 2
+ C.moving_dir = text2dir(pick("NORTH", "SOUTH", "EAST", "WEST"))
+ active_chasers++
+
+/datum/tarot/reversed/the_lovers
+ name = "VI - The Lovers?"
+ desc = "May your heart shatter to pieces."
+ card_icon = "the_lovers?"
+
+/datum/tarot/reversed/the_lovers/activate(mob/living/target)
+ if(!ishuman(target))
+ return
+ var/mob/living/carbon/human/H = target
+ H.apply_damage(20, BRUTE, BODY_ZONE_CHEST)
+ H.bleed(120)
+ var/obj/item/organ/external/chest = H.get_organ(BODY_ZONE_CHEST)
+ chest.fracture()
+ var/datum/organ/heart/datum_heart = H.get_int_organ_datum(ORGAN_DATUM_HEART)
+ var/obj/item/organ/internal/our_heart = datum_heart.linked_organ
+ our_heart.receive_damage(20, TRUE)
+
+/datum/tarot/reversed/the_chariot
+ name = "VII - The Chariot?"
+ desc = "May nothing walk past you."
+ card_icon = "the_chariot?"
+
+/datum/tarot/reversed/the_chariot/activate(mob/living/target)
+ target.Stun(4 SECONDS)
+ new /obj/structure/closet/statue/indestructible(get_turf(target), target)
+
+/datum/tarot/reversed/justice
+ name = "VIII - Justice?"
+ desc = "May your sins come back to torment you."
+ card_icon = "justice?"
+
+/datum/tarot/reversed/justice/activate(mob/living/target)
+ var/list/static/ignored_supply_pack_types = list(
+ /datum/supply_packs/abstract,
+ /datum/supply_packs/abstract/shuttle
+ )
+ var/chosen = pick(SSeconomy.supply_packs - ignored_supply_pack_types)
+ var/datum/supply_packs/the_pack = new chosen()
+ var/spawn_location = get_turf(target)
+ var/obj/structure/closet/crate/crate = the_pack.create_package(spawn_location)
+ crate.name = "magic [crate.name]"
+ qdel(the_pack)
+
+/datum/tarot/reversed/the_hermit
+ name = "IX - The Hermit?"
+ desc = "May you see the value of all things in life."
+ card_icon = "the_hermit?"
+
+/datum/tarot/reversed/the_hermit/activate(mob/living/target) //Someone can improve this in the future (hopefully comment will not be here in 10 years.)
+ for(var/obj/item/I in view(7, target))
+ if(istype(I, /obj/item/gun))
+ new /obj/item/stack/spacecash/c200(get_turf(I))
+ qdel(I)
+ continue
+ if(istype(I, /obj/item/grenade))
+ new /obj/item/stack/spacecash/c50(get_turf(I))
+ qdel(I)
+ if(istype(I, /obj/item/clothing/suit/armor))
+ new /obj/item/stack/spacecash/c100(get_turf(I))
+ qdel(I)
+ if(istype(I, /obj/item/melee/baton))
+ new /obj/item/stack/spacecash/c100(get_turf(I))
+ qdel(I)
+
+/datum/tarot/reversed/wheel_of_fortune
+ name = "X - Wheel of Fortune?"
+ desc = "Throw the dice of fate."
+ card_icon = "wheel_of_fortune?"
+
+/datum/tarot/reversed/wheel_of_fortune/activate(mob/living/target)
+ var/obj/item/dice/d20/fate/one_use/gonna_roll_a_one = new /obj/item/dice/d20/fate/one_use(get_turf(target))
+ gonna_roll_a_one.diceroll(target)
+
+/datum/tarot/reversed/strength
+ name = "XI - Strength?"
+ desc = "May you break their resolve."
+ card_icon = "strength?"
+
+/datum/tarot/reversed/strength/activate(mob/living/target)
+ for(var/mob/living/M in oview(9, target))
+ M.Hallucinate(2 MINUTES)
+ new /obj/effect/hallucination/delusion(get_turf(M), M)
+ M.adjustBrainLoss(30)
+
+/datum/tarot/reversed/the_hanged_man
+ name = "XII - The Hanged Man?"
+ desc = "May your greed know no bounds."
+ card_icon = "the_hanged_man?"
+
+/datum/tarot/reversed/the_hanged_man/activate(mob/living/target)
+ var/obj/structure/cursed_slot_machine/pull_the_lever_kronk = new /obj/structure/cursed_slot_machine(get_turf(target))
+ if(ishuman(target))
+ var/mob/living/carbon/human/WRONG_LEVER = target
+ pull_the_lever_kronk.attack_hand(WRONG_LEVER)
+
+/datum/tarot/reversed/death
+ name = "XIII - Death?"
+ desc = "May life spring forth from the fallen."
+ card_icon = "death?"
+
+/datum/tarot/reversed/death/activate(mob/living/target)
+ new /obj/structure/constructshell(get_turf(target))
+ new /obj/item/soulstone/anybody(get_turf(target))
+
+/datum/tarot/reversed/temperance
+ name = "XIV - Temperance?"
+ desc = "May your hunger be satiated."
+ card_icon = "temperance?"
+
+/datum/tarot/reversed/temperance/activate(mob/living/target)
+ if(!ishuman(target))
+ return
+ var/mob/living/carbon/human/H = target
+ for(var/i in 1 to 5)
+ var/datum/reagents/R = new /datum/reagents(10)
+ R.add_reagent(get_unrestricted_random_reagent_id(), 10)
+ R.reaction(H, REAGENT_INGEST)
+ R.trans_to(H, 10)
+ target.visible_message("[target] consumes 5 pills rapidly!")
+
+/datum/tarot/reversed/the_devil
+ name = "XV - The Devil?"
+ desc = "Bask in the light of your mercy."
+ card_icon = "the_devil?"
+
+/datum/tarot/reversed/the_devil/activate(mob/living/target)
+ var/obj/item/grenade/clusterbuster/i_hate_nians = new(get_turf(target))
+ i_hate_nians.prime()
+
+/datum/tarot/reversed/the_tower
+ name = "XVI - The Tower?"
+ desc = "Creation brings destruction."
+ card_icon = "the_tower?"
+
+/datum/tarot/reversed/the_tower/activate(mob/living/target)
+ for(var/turf/T in RANGE_TURFS(9, target))
+ if(locate(/mob/living) in T)
+ continue
+ if(istype(T, /turf/simulated/wall/indestructible))
+ continue
+ if(prob(66))
+ continue
+ T.ChangeTurf(/turf/simulated/mineral/random/labormineral)
+
+/datum/tarot/reversed/the_stars
+ name = "XVII - The Stars?"
+ desc = "May your loss bring fortune."
+ card_icon = "the_stars?"
+
+/datum/tarot/reversed/the_stars/activate(mob/living/target) //Heavy clone damage hit, but gain 2 cards. Not teathered to the card producer. Could lead to card stacking, but would require the sun to fix easily
+ if(!ishuman(target))
+ return
+ var/mob/living/carbon/human/H = target
+ H.adjustCloneLoss(50)
+ for(var/obj/item/organ/external/E in shuffle(H.bodyparts))
+ switch(rand(1,3))
+ if(1)
+ E.fracture()
+ if(2)
+ E.cause_internal_bleeding()
+ if(3)
+ E.cause_burn_wound()
+ break // I forgot the break the first time. Very funny.
+
+ H.drop_l_hand()
+ H.drop_r_hand()
+ var/obj/item/magic_tarot_card/MTC = new /obj/item/magic_tarot_card(get_turf(src))
+ var/obj/item/magic_tarot_card/MPC = new /obj/item/magic_tarot_card(get_turf(src))
+ H.put_in_hands(MTC)
+ H.put_in_hands(MPC)
+
+/datum/tarot/reversed/the_moon
+ name = "XVIII - The Moon?"
+ desc = "May you remember lost memories."
+ card_icon = "the_moon?"
+
+/datum/tarot/reversed/the_moon/activate(mob/living/target)
+ for(var/mob/living/L in view(5, target)) //Shorter range as this kinda can give away antagonists, though that is also funny.
+ target.mind.show_memory(L, 0) //Safe code? Bank accounts? PDA codes? It's yours my friend, as long as you have enough tarots
+
+/datum/tarot/reversed/the_sun
+ name = "XIX - The Sun?"
+ desc = "May the darkness swallow all around you."
+ card_icon = "the_sun?"
+
+/datum/tarot/reversed/the_sun/activate(mob/living/target)
+ target.apply_status_effect(STATUS_EFFECT_REVERSED_SUN)
+
+/datum/tarot/reversed/judgement
+ name = "XX - Judgement?"
+ desc = "May you redeem those found wanting" //Who wants more, but ghosts for something interesting
+ card_icon = "judgement?"
+
+/datum/tarot/reversed/judgement/activate(mob/living/target)
+ var/datum/event_container/EC = SSevents.event_containers[EVENT_LEVEL_MODERATE]
+ var/decrease = 5 MINUTES
+ EC.next_event_time -= decrease
+ log_and_message_admins("decreased timer for [GLOB.severity_to_string[EC.severity]] events by 5 minutes by use of a [src].")
+
+/datum/tarot/reversed/the_world
+ name = "XXI - The World?"
+ desc = "Step into the abyss."
+ card_icon = "the_world?"
+
+/datum/tarot/reversed/the_world/activate(mob/living/target)
+ var/list/L = list()
+ for(var/turf/T in get_area_turfs(/area/mine/outpost)) //Lavaland is the abyss, but also too hot to send people too. Mining base should be fair!
+ if(is_blocked_turf(T))
+ continue
+ L.Add(T)
+
+ if(!length(L))
+ to_chat(target, "Hmm. No base? A miner issue.")
+ return
+
+ target.forceMove(pick(L))
+ to_chat(target, "You are abruptly pulled through space!")
diff --git a/code/game/gamemodes/wizard/rightandwrong.dm b/code/game/gamemodes/wizard/rightandwrong.dm
index 48e603a0994f..0b2c52f36609 100644
--- a/code/game/gamemodes/wizard/rightandwrong.dm
+++ b/code/game/gamemodes/wizard/rightandwrong.dm
@@ -79,7 +79,9 @@ GLOBAL_LIST_INIT(summoned_magic, list(
/obj/item/scrying,
/obj/item/clothing/suit/space/hardsuit/wizard,
/obj/item/immortality_talisman,
- /obj/item/melee/ghost_sword))
+ /obj/item/melee/ghost_sword,
+ /obj/item/tarot_card_pack,
+ /obj/item/tarot_card_pack/jumbo))
GLOBAL_LIST_INIT(summoned_special_magic, list(
/obj/item/gun/magic/staff/animate,
@@ -87,7 +89,8 @@ GLOBAL_LIST_INIT(summoned_special_magic, list(
/obj/item/contract,
/obj/item/gun/magic/staff/chaos,
/obj/item/necromantic_stone,
- /obj/item/blood_contract))
+ /obj/item/blood_contract,
+ /obj/item/tarot_generator))
//everything above except for single use spellbooks, because they are counted separately (and are for basic bitches anyways)
GLOBAL_LIST_INIT(summoned_magic_objectives, list(
@@ -100,7 +103,8 @@ GLOBAL_LIST_INIT(summoned_magic_objectives, list(
/obj/item/necromantic_stone,
/obj/item/scrying,
/obj/item/spellbook,
- /obj/item/storage/belt/wands/full))
+ /obj/item/storage/belt/wands/full,
+ /obj/item/tarot_generator))
// If true, it's the probability of triggering "survivor" antag.
GLOBAL_VAR_INIT(summon_guns_triggered, FALSE)
diff --git a/code/game/gamemodes/wizard/soulstone.dm b/code/game/gamemodes/wizard/soulstone.dm
index 2190b08c3e15..fdf530ea1198 100644
--- a/code/game/gamemodes/wizard/soulstone.dm
+++ b/code/game/gamemodes/wizard/soulstone.dm
@@ -11,8 +11,8 @@
slot_flags = SLOT_FLAG_BELT
origin_tech = "bluespace=4;materials=5"
- /// The body/brain of the player inside this construct, transferred over from the soulstone.
- var/atom/movable/held_body
+ /// Should we show rays? Triggered by a held body
+ var/animate_rays = FALSE
/// Does this soulstone ask the victim whether they want to be turned into a shade
var/optional = FALSE
/// Can this soul stone be used by anyone, or only cultists/wizards?
@@ -26,13 +26,6 @@
var/opt_in = FALSE
var/purified = FALSE
-/obj/item/soulstone/proc/add_held_body(atom/movable/body)
- held_body = body
- RegisterSignal(body, COMSIG_PARENT_QDELETING, PROC_REF(remove_held_body))
-
-/obj/item/soulstone/proc/remove_held_body()
- SIGNAL_HANDLER
- held_body = null
/obj/item/soulstone/proc/can_use(mob/living/user)
if(IS_CULTIST(user) && purified && !iswizard(user))
@@ -78,13 +71,12 @@
/obj/item/soulstone/Destroy() //Stops the shade from being qdel'd immediately and their ghost being sent back to the arrival shuttle.
for(var/mob/living/simple_animal/shade/A in src)
A.death()
- remove_held_body()
STOP_PROCESSING(SSobj, src)
return ..()
/obj/item/soulstone/process()
. = ..()
- if(held_body)
+ if(animate_rays)
var/new_filter = isnull(get_filter("ray"))
if(!purified)
ray_filter_helper(1, 40,"#c926ae", 6, 20)
@@ -242,20 +234,19 @@
to_chat(user, "The shard feels too tough to shatter, you are not holy enough to free its captive!")
return
- if(!do_after_once(user, 10 SECONDS, FALSE, src) || !held_body)
+ if(!do_after_once(user, 10 SECONDS, FALSE, src))
return
- user.visible_message("[user] shatters the soulstone apart! Releasing [held_body] from their prison!", "You shatter the soulstone holding [held_body], binding them free!", "You hear something shatter with a ghastly crack.")
- if(ismob(held_body))
- var/mob/M = held_body
- M.key = S.key
- else if(istype(held_body, /obj/item/organ/internal/brain))
- var/obj/item/organ/internal/brain/B = held_body
- B.brainmob.key = S.key
- S.cancel_camera()
- held_body.forceMove(get_turf(src))
- SSticker.mode?.cult_team?.add_cult_immunity(held_body)
- remove_held_body()
+ if(!S)
+ return
+
+ var/datum/component/construct_held_body/body_holder = S.GetComponent(/datum/component/construct_held_body)
+ var/atom/movable/dropped_body = body_holder.held_body
+ body_holder.drop_body()
+ if(!dropped_body)
+ return
+
+ user.visible_message("[user] shatters the soulstone apart! Releasing [dropped_body] from their prison!", "You shatter the soulstone holding [dropped_body], binding them free!", "You hear something shatter with a ghastly crack.")
new /obj/effect/temp_visual/cult/sparks(get_turf(src))
playsound(src, 'sound/effects/pylon_shatter.ogg', 40, TRUE)
qdel(src)
@@ -276,6 +267,7 @@
was_used()
remove_filter("ray")
STOP_PROCESSING(SSobj, src)
+ animate_rays = FALSE
///////////////////////////Transferring to constructs/////////////////////////////////////////////////////
/obj/structure/constructshell
@@ -320,9 +312,8 @@
to_chat(user, "Capture failed! The soul has already fled its mortal frame. You attempt to bring it back...")
T.Paralyse(40 SECONDS)
if(!get_cult_ghost(T, user, TRUE))
- add_held_body(T)
- T.forceMove(src) //If we can't get a ghost, shard the body anyways.
- START_PROCESSING(SSobj, src)
+ // no luck, dont shard them.
+ to_chat(user, "No soul responds to the soul stone.")
if("VICTIM")
var/mob/living/carbon/human/T = target
@@ -361,6 +352,7 @@
name = "soulstone : [T.name]"
to_chat(T, "Your soul has been recaptured by the soul stone, its arcane energies are reknitting your ethereal form")
to_chat(user, "Capture successful! [T.name]'s has been recaptured and stored within the soul stone.")
+ animate_rays = TRUE
START_PROCESSING(SSobj, src)
if("CONSTRUCT")
@@ -413,9 +405,8 @@
to_chat(src, "You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.")
else
to_chat(src, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.")
- SS.held_body.forceMove(src)
- add_held_body(SS.held_body)
- SS.remove_held_body()
+
+ SEND_SIGNAL(shade, COMSIG_SHADE_TO_CONSTRUCT_TRANSFER, src)
cancel_camera()
qdel(shell)
qdel(shade)
@@ -465,13 +456,13 @@
if(!isrobot(M))
for(var/obj/item/I in M)
M.unEquip(I)
+
+ var/target_body = M
if(isbrain(M))
- var/obj/item/organ/internal/brain/brain_obj = M.loc
- add_held_body(brain_obj)
- brain_obj.forceMove(src)
- else
- add_held_body(M)
- M.forceMove(src)
+ target_body = M.loc // get the brain organ instead of the brain mob
+
+ S.AddComponent(/datum/component/construct_held_body, target_body)
+ animate_rays = TRUE
/obj/item/soulstone/proc/get_shade_type()
if(purified)
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
index 743079d34e55..d175fcbf6ad7 100644
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ b/code/game/gamemodes/wizard/spellbook.dm
@@ -483,6 +483,15 @@
cost = 1
category = "Artefacts"
+/datum/spellbook_entry/item/tarot_generator
+ name = "Enchanted tarot card deck"
+ desc = "An magic tarot card deck, enchanted with special Ink. \
+ Capable of producing magic tarot cards of the 22 major arcana, both normal and reversed. Each card has a different effect. \
+ Throw the card at someone to use it on them, or use it in hand to apply it to yourself. Unlimited uses, 12 second cooldown, can have up to 5 cards in the world."
+ item_path = /obj/item/tarot_generator/wizard
+ cost = 2
+ category = "Artefacts"
+
//Weapons and Armors
/datum/spellbook_entry/item/battlemage
name = "Battlemage Armor"
diff --git a/code/game/jobs/job_exp.dm b/code/game/jobs/job_exp.dm
index f4d62e08a8f7..c8481db135a1 100644
--- a/code/game/jobs/job_exp.dm
+++ b/code/game/jobs/job_exp.dm
@@ -32,18 +32,6 @@ GLOBAL_LIST_INIT(role_playtime_requirements, list(
ROLE_ABDUCTOR = 20,
))
-// Client Verbs
-
-/client/verb/cmd_check_own_playtime()
- set category = "Special Verbs"
- set name = "Check my playtime"
-
- if(!GLOB.configuration.jobs.enable_exp_tracking)
- to_chat(src, "Playtime tracking is not enabled.")
- return
-
- to_chat(src, "Your [EXP_TYPE_CREW] playtime is [get_exp_type(EXP_TYPE_CREW)].")
-
// Admin Verbs
/client/proc/cmd_mentor_check_player_exp() //Allows admins to determine who the newer players are.
diff --git a/code/game/objects/effects/temporary_visuals/cult_visuals.dm b/code/game/objects/effects/temporary_visuals/cult_visuals.dm
index a1b2d4320a97..3f6d09dbaf6f 100644
--- a/code/game/objects/effects/temporary_visuals/cult_visuals.dm
+++ b/code/game/objects/effects/temporary_visuals/cult_visuals.dm
@@ -9,6 +9,13 @@
name = "blood sparks"
icon_state = "bloodsparkles"
+/obj/effect/temp_visual/cult/sparks/hierophant
+ icon = 'icons/effects/effects.dmi'
+ randomdir = TRUE
+ duration = 12
+ name = "purple sparks"
+ icon_state = "hierophant_blast"
+
/obj/effect/temp_visual/dir_setting/cult/phase
name = "phase glow"
duration = 12
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 49c45f27fe8c..18b6d11151a8 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -514,7 +514,7 @@ GLOBAL_LIST_INIT(cult_recipes, list (
new /datum/stack_recipe/cult("runed girder (used to make cult walls)", /obj/structure/girder/cult, 1, time = 1 SECONDS, one_per_turf = TRUE, on_floor = TRUE, cult_structure = TRUE),
new /datum/stack_recipe/cult("pylon (heals nearby cultists)", /obj/structure/cult/functional/pylon, 4, time = 4 SECONDS, one_per_turf = TRUE, on_floor = TRUE, cult_structure = TRUE),
new /datum/stack_recipe/cult("forge (crafts shielded robes, flagellant's robes, and mirror shields)", /obj/structure/cult/functional/forge, 3, time = 4 SECONDS, one_per_turf = TRUE, on_floor = TRUE, cult_structure = TRUE),
- new /datum/stack_recipe/cult("archives (crafts zealot's blindfolds, shuttle curse orbs, veil shifters, and reality sunderers)", /obj/structure/cult/functional/archives, 3, time = 4 SECONDS, one_per_turf = TRUE, on_floor = TRUE, cult_structure = TRUE),
+ new /datum/stack_recipe/cult("archives (crafts zealot's blindfolds, shuttle curse orbs, veil shifters, reality sunderers, and blank tarot cards)", /obj/structure/cult/functional/archives, 3, time = 4 SECONDS, one_per_turf = TRUE, on_floor = TRUE, cult_structure = TRUE),
new /datum/stack_recipe/cult("altar (crafts eldritch whetstones, construct shells, and flasks of unholy water)", /obj/structure/cult/functional/altar, 3, time = 4 SECONDS, one_per_turf = TRUE, on_floor = TRUE, cult_structure = TRUE),
))
diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm
index 7a825263df9d..cc9c370b9735 100644
--- a/code/game/objects/items/weapons/cards_ids.dm
+++ b/code/game/objects/items/weapons/cards_ids.dm
@@ -47,6 +47,21 @@
return
A.emag_act(user)
+/obj/item/card/emag/magic_key
+ name = "magic key"
+ desc = "It's a magic key, that will open one door!"
+ icon_state = "magic_key"
+ origin_tech = "magnets=2"
+
+/obj/item/card/emag/magic_key/afterattack(atom/target, mob/user, proximity)
+ if(!istype(target, /obj/machinery/door))
+ return
+ var/obj/machinery/door/D = target
+ D.locked = FALSE
+ update_icon()
+ . = ..()
+ qdel(src)
+
/obj/item/card/cmag
desc = "It's a card coated in a slurry of electromagnetic bananium."
name = "jestographic sequencer"
diff --git a/code/game/objects/items/weapons/grenades/clusterbuster.dm b/code/game/objects/items/weapons/grenades/clusterbuster.dm
index f08704aa25d3..a03f07be8b0a 100644
--- a/code/game/objects/items/weapons/grenades/clusterbuster.dm
+++ b/code/game/objects/items/weapons/grenades/clusterbuster.dm
@@ -227,6 +227,11 @@
desc = "For when you need to knock out EVERYONE."
payload = /obj/item/grenade/gas/knockout
+/obj/item/grenade/clusterbuster/ied
+ name = "\improper IED Cluster Grenade"
+ desc = "For when you need to do something between everything and nothing."
+ payload = /obj/item/grenade/iedcasing
+
////////////Clusterbuster of Clusterbusters////////////
//As a note: be extrodinarily careful about make the payload clusterbusters as it can quickly destroy the MC/Server
diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm
index 453c0e0c8cdb..dbc5d3f5ced8 100644
--- a/code/game/objects/items/weapons/storage/backpack.dm
+++ b/code/game/objects/items/weapons/storage/backpack.dm
@@ -720,7 +720,8 @@
/obj/item/warp_cube/red = 1,
/obj/item/reagent_containers/drinks/everfull = 2,
/obj/item/clothing/suit/space/hardsuit/wizard = 2,
- /obj/item/immortality_talisman = 1 ) //spells recharge when invincible
+ /obj/item/immortality_talisman = 1, //spells recharge when invincible
+ /obj/item/tarot_generator/wizard = 2)
var/obj/item/pickeda = pick(list_a)
value += list_a[pickeda]
new pickeda(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/statue.dm b/code/game/objects/structures/crates_lockers/closets/statue.dm
index 5dffb626cf73..0be43090cc96 100644
--- a/code/game/objects/structures/crates_lockers/closets/statue.dm
+++ b/code/game/objects/structures/crates_lockers/closets/statue.dm
@@ -117,3 +117,17 @@
user.dust()
dump_contents()
visible_message("[src] shatters!")
+
+/obj/structure/closet/statue/indestructible
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+ timer = 240 SECONDS_TO_LIFE_CYCLES
+
+/obj/structure/closet/statue/indestructible/ex_act(severity)
+ return //No delimbing them
+
+/obj/structure/closet/statue/indestructible/shatter(mob/user)
+ return //No. Failsafe.
+
+/obj/structure/closet/statue/indestructible/singularity_act()
+ return //I mean maybe but no.
+
diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm
index 53b526bad412..a16ff51d0d44 100644
--- a/code/game/verbs/ooc.dm
+++ b/code/game/verbs/ooc.dm
@@ -112,61 +112,6 @@ GLOBAL_VAR_INIT(admin_ooc_colour, "#b82e00")
if(GLOB.configuration.general.auto_disable_ooc && GLOB.ooc_enabled != on)
toggle_ooc()
-/client/proc/set_ooc(newColor as color)
- set name = "Set Player OOC Colour"
- set desc = "Modifies the default player OOC color."
- set category = "Server"
-
- if(!check_rights(R_SERVER)) return
-
- GLOB.normal_ooc_colour = newColor
- message_admins("[key_name_admin(usr)] has set the default player OOC color to [newColor]")
- log_admin("[key_name(usr)] has set the default player OOC color to [newColor]")
-
-
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Player OOC")
-
-/client/proc/reset_ooc()
- set name = "Reset Player OOC Color"
- set desc = "Returns the default player OOC color to default."
- set category = "Server"
-
- if(!check_rights(R_SERVER)) return
-
- GLOB.normal_ooc_colour = DEFAULT_PLAYER_OOC_COLOUR
- message_admins("[key_name_admin(usr)] has reset the default player OOC color")
- log_admin("[key_name(usr)] has reset the default player OOC color")
-
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Reset Player OOC")
-
-/client/proc/colorooc()
- set name = "Set Your OOC Color"
- set desc = "Allows you to pick a custom OOC color."
- set category = "Preferences"
-
- if(!check_rights(R_ADMIN)) return
-
- var/new_ooccolor = input(src, "Please select your OOC color.", "OOC color", prefs.ooccolor) as color|null
- if(new_ooccolor)
- prefs.ooccolor = new_ooccolor
- prefs.save_preferences(src)
- to_chat(usr, "Your OOC color has been set to [new_ooccolor].")
-
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Own OOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/proc/resetcolorooc()
- set name = "Reset Your OOC Color"
- set desc = "Returns your OOC color to default."
- set category = "Preferences"
-
- if(!check_rights(R_ADMIN)) return
-
- prefs.ooccolor = initial(prefs.ooccolor)
- prefs.save_preferences(src)
- to_chat(usr, "Your OOC color has been reset.")
-
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Reset Own OOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
/client/verb/looc(msg = "" as text)
set name = "LOOC"
set desc = "Local OOC, seen only by those in view."
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 49d8b4f0817a..0b9abc3abf31 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -2,8 +2,6 @@
GLOBAL_LIST_INIT(admin_verbs_default, list(
/client/proc/deadmin_self, /*destroys our own admin datum so we can play as a regular player*/
/client/proc/hide_verbs, /*hides all our adminverbs*/
- /client/proc/toggleadminhelpsound,
- /client/proc/togglementorhelpsound,
/client/proc/cmd_mentor_check_new_players,
/client/proc/cmd_mentor_check_player_exp /* shows players by playtime */
))
@@ -13,8 +11,6 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list(
/client/proc/player_panel_new, /*shows an interface for all players, with links to various panels*/
/client/proc/invisimin, /*allows our mob to go invisible/visible*/
/datum/admins/proc/announce, /*priority announce something to all clients.*/
- /client/proc/colorooc, /*allows us to set a custom colour for everything we say in ooc*/
- /client/proc/resetcolorooc, /*allows us to set a reset our ooc color*/
/client/proc/admin_ghost, /*allows us to ghost/reenter body at will*/
/client/proc/toggle_view_range, /*changes how far we can see*/
/client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/
@@ -35,8 +31,6 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list(
/client/proc/manage_silicon_laws, /* Allows viewing and editing silicon laws. */
/client/proc/admin_memo, /*admin memo system. show/delete/write. +SERVER needed to delete admin memos of others*/
/client/proc/dsay, /*talk in deadchat using our ckey/fakekey*/
- /client/proc/toggleprayers, /*toggles prayers on/off*/
- /client/proc/toggle_hear_radio, /*toggles whether we hear the radio*/
/client/proc/investigate_show, /*various admintools for investigation. Such as a singulo grief-log*/
/datum/admins/proc/toggleooc, /*toggles ooc on/off for everyone*/
/datum/admins/proc/togglelooc, /*toggles looc on/off for everyone*/
@@ -49,9 +43,6 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list(
/client/proc/cmd_mentor_say,
/datum/admins/proc/show_player_notes,
/client/proc/free_slot, /*frees slot for chosen job*/
- /client/proc/toggleattacklogs,
- /client/proc/toggleadminlogs,
- /client/proc/toggledebuglogs,
/client/proc/update_mob_sprite,
/client/proc/man_up,
/client/proc/global_man_up,
@@ -140,8 +131,6 @@ GLOBAL_LIST_INIT(admin_verbs_server, list(
/client/proc/view_asays,
/client/proc/toggle_antagHUD_use,
/client/proc/toggle_antagHUD_restrictions,
- /client/proc/set_ooc,
- /client/proc/reset_ooc,
/client/proc/set_next_map,
/client/proc/manage_queue,
/client/proc/add_queue_server_bypass
@@ -155,7 +144,6 @@ GLOBAL_LIST_INIT(admin_verbs_debug, list(
/client/proc/cmd_debug_del_sing,
/client/proc/restart_controller,
/client/proc/enable_debug_verbs,
- /client/proc/toggledebuglogs,
/client/proc/cmd_display_del_log,
/client/proc/cmd_display_del_log_simple,
/client/proc/check_bomb_impacts,
@@ -181,8 +169,7 @@ GLOBAL_LIST_INIT(admin_verbs_debug, list(
/client/proc/debug_timers,
/client/proc/force_verb_bypass,
/client/proc/show_gc_queues,
- /client/proc/debug_global_variables,
- /client/proc/toggle_mctabs
+ /client/proc/debug_global_variables
))
GLOBAL_LIST_INIT(admin_verbs_possess, list(
/proc/possess,
@@ -215,7 +202,6 @@ GLOBAL_LIST_INIT(admin_verbs_mentor, list(
/client/proc/cmd_admin_pm_panel, /*admin-pm list*/
/client/proc/cmd_admin_pm_by_key_panel, /*admin-pm list by key*/
/client/proc/openMentorTicketUI,
- /client/proc/toggleMentorTicketLogs,
/client/proc/cmd_mentor_say /* mentor say*/
// cmd_mentor_say is added/removed by the toggle_mentor_chat verb
))
@@ -226,9 +212,7 @@ GLOBAL_LIST_INIT(admin_verbs_proccall, list(
))
GLOBAL_LIST_INIT(admin_verbs_ticket, list(
/client/proc/openAdminTicketUI,
- /client/proc/toggleticketlogs,
/client/proc/openMentorTicketUI,
- /client/proc/toggleMentorTicketLogs,
/client/proc/resolveAllAdminTickets,
/client/proc/resolveAllMentorTickets
))
@@ -246,15 +230,13 @@ GLOBAL_LIST_INIT(view_runtimes_verbs, list(
/client/proc/view_runtimes,
/client/proc/cmd_display_del_log,
/client/proc/cmd_display_del_log_simple,
- /client/proc/toggledebuglogs,
/client/proc/debug_variables, /*allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify*/
/client/proc/ss_breakdown,
/client/proc/show_gc_queues,
/client/proc/debug_global_variables,
/client/proc/visualise_active_turfs,
/client/proc/debug_timers,
- /client/proc/timer_log,
- /client/proc/toggle_mctabs
+ /client/proc/timer_log
))
/client/proc/add_admin_verbs()
@@ -880,91 +862,6 @@ GLOBAL_LIST_INIT(view_runtimes_verbs, list(
log_admin("[key_name(usr)] has freed a job slot for [job].")
message_admins("[key_name_admin(usr)] has freed a job slot for [job].")
-/client/proc/toggleattacklogs()
- set name = "Attack Log Messages"
- set category = "Preferences.Toggle"
-
- if(!check_rights(R_ADMIN))
- return
-
- if(prefs.atklog == ATKLOG_ALL)
- prefs.atklog = ATKLOG_ALMOSTALL
- to_chat(usr, "Your attack logs preference is now: show ALMOST ALL attack logs (notable exceptions: NPCs attacking other NPCs, vampire bites, equipping/stripping, people pushing each other over)")
- else if(prefs.atklog == ATKLOG_ALMOSTALL)
- prefs.atklog = ATKLOG_MOST
- to_chat(usr, "Your attack logs preference is now: show MOST attack logs (like ALMOST ALL, except that it also hides player v. NPC combat, and certain areas like lavaland syndie base and thunderdome)")
- else if(prefs.atklog == ATKLOG_MOST)
- prefs.atklog = ATKLOG_FEW
- to_chat(usr, "Your attack logs preference is now: show FEW attack logs (only the most important stuff: attacks on SSDs, use of explosives, messing with the engine, gibbing, AI wiping, forcefeeding, acid sprays, and organ extraction)")
- else if(prefs.atklog == ATKLOG_FEW)
- prefs.atklog = ATKLOG_NONE
- to_chat(usr, "Your attack logs preference is now: show NO attack logs")
- else if(prefs.atklog == ATKLOG_NONE)
- prefs.atklog = ATKLOG_ALL
- to_chat(usr, "Your attack logs preference is now: show ALL attack logs")
- else
- prefs.atklog = ATKLOG_ALL
- to_chat(usr, "Your attack logs preference is now: show ALL attack logs (your preference was set to an invalid value, it has been reset)")
-
- prefs.save_preferences(src)
-
-
-/client/proc/toggleadminlogs()
- set name = "Admin Log Messages"
- set category = "Preferences.Toggle"
-
- if(!check_rights(R_ADMIN))
- return
-
- prefs.toggles ^= PREFTOGGLE_CHAT_NO_ADMINLOGS
- prefs.save_preferences(src)
- if(prefs.toggles & PREFTOGGLE_CHAT_NO_ADMINLOGS)
- to_chat(usr, "You now won't get admin log messages.")
- else
- to_chat(usr, "You now will get admin log messages.")
-
-/client/proc/toggleMentorTicketLogs()
- set name = "Mentor Ticket Messages"
- set category = "Preferences.Toggle"
-
- if(!check_rights(R_MENTOR|R_ADMIN))
- return
-
- prefs.toggles ^= PREFTOGGLE_CHAT_NO_MENTORTICKETLOGS
- prefs.save_preferences(src)
- if(prefs.toggles & PREFTOGGLE_CHAT_NO_MENTORTICKETLOGS)
- to_chat(usr, "You now won't get mentor ticket messages.")
- else
- to_chat(usr, "You now will get mentor ticket messages.")
-
-/client/proc/toggleticketlogs()
- set name = "Admin Ticket Messgaes"
- set category = "Preferences.Toggle"
-
- if(!check_rights(R_ADMIN))
- return
-
- prefs.toggles ^= PREFTOGGLE_CHAT_NO_TICKETLOGS
- prefs.save_preferences(src)
- if(prefs.toggles & PREFTOGGLE_CHAT_NO_TICKETLOGS)
- to_chat(usr, "You now won't get admin ticket messages.")
- else
- to_chat(usr, "You now will get admin ticket messages.")
-
-/client/proc/toggledebuglogs()
- set name = "Debug Log Messages"
- set category = "Preferences.Toggle"
-
- if(!check_rights(R_VIEWRUNTIMES | R_DEBUG))
- return
-
- prefs.toggles ^= PREFTOGGLE_CHAT_DEBUGLOGS
- prefs.save_preferences(src)
- if(prefs.toggles & PREFTOGGLE_CHAT_DEBUGLOGS)
- to_chat(usr, "You now will get debug log messages")
- else
- to_chat(usr, "You now won't get debug log messages")
-
/client/proc/man_up(mob/T as mob in GLOB.player_list)
set name = "\[Admin\] Man Up"
set desc = "Tells mob to man up and deal with it."
diff --git a/code/modules/admin/verbs/custom_event.dm b/code/modules/admin/verbs/custom_event.dm
index 1284f9e0fafe..e68cb9832731 100644
--- a/code/modules/admin/verbs/custom_event.dm
+++ b/code/modules/admin/verbs/custom_event.dm
@@ -28,12 +28,14 @@
set category = "OOC"
set name = "Custom Event Info"
+ var/list/custom_event_information = list()
if(!GLOB.custom_event_msg || GLOB.custom_event_msg == "")
- to_chat(src, "There currently is no known custom event taking place.")
- to_chat(src, "Keep in mind: it is possible that an admin has not properly set this.")
+ custom_event_information += "There currently is no known custom event taking place."
+ custom_event_information += "Keep in mind: it is possible that an admin has not properly set this."
+ to_chat(src, chat_box_regular(custom_event_information.Join("
")))
return
- to_chat(src, "Custom Event
")
- to_chat(src, "A custom event is taking place. OOC Info:
")
- to_chat(src, "[html_encode(GLOB.custom_event_msg)]")
- to_chat(src, "
")
+ custom_event_information += "Custom Event
"
+ custom_event_information += "A custom event is taking place. OOC Info:
"
+ custom_event_information += "[html_encode(GLOB.custom_event_msg)]"
+ to_chat(src, chat_box_regular(custom_event_information.Join("
")))
diff --git a/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm b/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm
index d69d28fe36aa..641894c0eabd 100644
--- a/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/gargantua_powers.dm
@@ -117,7 +117,6 @@
action_icon_state = "demonic_grasp"
- panel = "Vampire"
school = "vampire"
action_background_icon_state = "bg_vampire"
sound = null
diff --git a/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm b/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm
index f086ac67f57f..220ba634df79 100644
--- a/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/hemomancer_powers.dm
@@ -250,7 +250,6 @@
gain_desc = "You have gained the ability to shift into a pool of blood, allowing you to evade pursuers with great mobility."
jaunt_duration = 3 SECONDS
clothes_req = FALSE
- panel = "Vampire"
school = "vampire"
action_background_icon_state = "bg_vampire"
action_icon_state = "blood_pool"
diff --git a/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm b/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm
index ecf21d68ed4d..f2b54d4c8567 100644
--- a/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/vampire_powers.dm
@@ -13,7 +13,6 @@
return TRUE
/datum/spell/vampire
- panel = "Vampire"
school = "vampire"
action_background_icon_state = "bg_vampire"
human_req = TRUE
diff --git a/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm b/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm
index c872499557dc..be522b5ebb31 100644
--- a/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm
+++ b/code/modules/awaymissions/mission_code/ruins/wizardcrash.dm
@@ -18,8 +18,12 @@
/obj/item/guardiancreator = 1, // jackpot.
/obj/item/spellbook/oneuse/knock = 1, // tresspassing charges incoming
/obj/item/gun/magic/wand/resurrection = 1, // medbay's best friend
+ /obj/item/tarot_generator = 1, // A little bit of everything, all of the time.
/obj/item/spellbook/oneuse/charge = 20, // and now for less useful stuff to dilute the good loot chances
/obj/item/spellbook/oneuse/summonitem = 20,
/obj/item/spellbook/oneuse/forcewall = 10,
- /obj/item/book/granter/spell/summon_cheese = 20 // hungry wizard stuff
+ /obj/item/tarot_card_pack = 10,
+ /obj/item/tarot_card_pack/jumbo = 6,
+ /obj/item/tarot_card_pack/mega = 4,
+ /obj/item/book/granter/spell/summon_cheese = 15 // hungry wizard stuff
)
diff --git a/code/modules/client/preference/link_processing.dm b/code/modules/client/preference/link_processing.dm
index 04198097d385..9d7b78b6e239 100644
--- a/code/modules/client/preference/link_processing.dm
+++ b/code/modules/client/preference/link_processing.dm
@@ -1285,6 +1285,10 @@
init_keybindings(keybindings_overrides)
save_preferences(user) //Ideally we want to save people's keybinds when they enter them
+ if("preference_toggles")
+ if(href_list["toggle"])
+ var/datum/preference_toggle/toggle = locateUID(href_list["toggle"])
+ toggle.set_toggles(user.client)
ShowChoices(user)
- return 1
+ return TRUE
diff --git a/code/modules/client/preference/preferences.dm b/code/modules/client/preference/preferences.dm
index b8a53d743a7a..1cd021814b27 100644
--- a/code/modules/client/preference/preferences.dm
+++ b/code/modules/client/preference/preferences.dm
@@ -167,6 +167,7 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
dat += "Antagonists"
dat += "Loadout"
dat += "Key Bindings"
+ dat += "General Preferences"
dat += ""
dat += "
"
@@ -484,6 +485,12 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
else
var/is_special = (i in src.be_special)
dat += "Be [capitalize(i)]:[(is_special) ? "Yes" : "No"]
"
+
+ dat += "Total Playtime:
"
+ if(!GLOB.configuration.jobs.enable_exp_tracking)
+ dat += "Playtime tracking is not enabled."
+ else
+ dat += "Your [EXP_TYPE_CREW] playtime is [user.client.get_exp_type(EXP_TYPE_CREW)]
"
dat += ""
if(TAB_GEAR)
@@ -595,6 +602,42 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
dat += ""
+ if(TAB_TOGGLES)
+ dat += "Preference Toggles: "
+
+ dat += "
"
+
+ // Lookup lists to make our life easier
+ var/static/list/pref_toggles_by_category
+ if(!pref_toggles_by_category)
+ pref_toggles_by_category = list()
+ for(var/datum/preference_toggle/toggle as anything in GLOB.preference_toggles)
+ pref_toggles_by_category["[toggle.preftoggle_category]"] += list(toggle)
+
+ for(var/category in GLOB.preference_toggle_groups)
+ dat += "
|
"
+ dat += "[category] |
"
+ for(var/datum/preference_toggle/toggle as anything in pref_toggles_by_category["[GLOB.preference_toggle_groups[category]]"])
+ dat += ""
+ dat += "[toggle.name] | "
+ dat += "[toggle.description] | "
+ if(toggle.preftoggle_category == PREFTOGGLE_CATEGORY_ADMIN)
+ if(!check_rights(toggle.rights_required, 0, (user)))
+ dat += "Admin Restricted. | "
+ dat += "
"
+ continue
+ switch(toggle.preftoggle_toggle)
+ if(PREFTOGGLE_SPECIAL)
+ dat += "Adjust | "
+ if(PREFTOGGLE_TOGGLE1)
+ dat += "[(toggles & toggle.preftoggle_bitflag) ? "Enabled" : "Disabled"] | "
+ if(PREFTOGGLE_TOGGLE2)
+ dat += "[(toggles2 & toggle.preftoggle_bitflag) ? "Enabled" : "Disabled"] | "
+ if(PREFTOGGLE_SOUND)
+ dat += "[(sound & toggle.preftoggle_bitflag) ? "Enabled" : "Disabled"] | "
+ dat += ""
+ dat += "
|
"
+
dat += "
"
if(!IsGuestKey(user.key))
diff --git a/code/modules/client/preference/preferences_toggles.dm b/code/modules/client/preference/preferences_toggles.dm
index ecae3e50c1c1..31cfd405f5c3 100644
--- a/code/modules/client/preference/preferences_toggles.dm
+++ b/code/modules/client/preference/preferences_toggles.dm
@@ -1,361 +1,528 @@
-//toggles
-/client/verb/toggle_ghost_ears()
- set name = "GhostEars"
- set category = "Preferences.Show/Hide"
- set desc = "Toggle Between seeing all mob speech, and only speech of nearby mobs"
- prefs.toggles ^= PREFTOGGLE_CHAT_GHOSTEARS
- to_chat(src, "As a ghost, you will now [(prefs.toggles & PREFTOGGLE_CHAT_GHOSTEARS) ? "see all speech in the world" : "only see speech from nearby mobs"].")
- prefs.save_preferences(src)
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle GhostEars") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/toggle_ghost_sight()
- set name = "GhostSight"
- set category = "Preferences.Show/Hide"
- set desc = "Toggle Between seeing all mob emotes, and only emotes of nearby mobs"
- prefs.toggles ^= PREFTOGGLE_CHAT_GHOSTSIGHT
- to_chat(src, "As a ghost, you will now [(prefs.toggles & PREFTOGGLE_CHAT_GHOSTSIGHT) ? "see all emotes in the world" : "only see emotes from nearby mobs"].")
- prefs.save_preferences(src)
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle GhostSight") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/toggle_ghost_radio()
- set name = "GhostRadio"
- set category = "Preferences.Show/Hide"
- set desc = "Toggle between hearing all radio chatter, or only from nearby speakers"
- prefs.toggles ^= PREFTOGGLE_CHAT_GHOSTRADIO
- to_chat(src, "As a ghost, you will now [(prefs.toggles & PREFTOGGLE_CHAT_GHOSTRADIO) ? "hear all radio chat in the world" : "only hear from nearby speakers"].")
- prefs.save_preferences(src)
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle GhostRadio")
-
-/client/proc/toggle_hear_radio()
- set name = "RadioChatter"
- set category = "Preferences.Show/Hide"
- set desc = "Toggle seeing radiochatter from radios and speakers"
- if(!check_rights(R_ADMIN))
- return
- prefs.toggles ^= PREFTOGGLE_CHAT_RADIO
- prefs.save_preferences(src)
- to_chat(usr, "You will [(prefs.toggles & PREFTOGGLE_CHAT_RADIO) ? "now" : "no longer"] see radio chatter from radios or speakers")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle RadioChatter") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/toggle_ai_voice_annoucements()
- set name = "AI Voice Announcements"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggle hearing AI annoucements in voice form or in text form"
- prefs.sound ^= SOUND_AI_VOICE
- prefs.save_preferences(src)
- to_chat(usr, "[(prefs.sound & SOUND_AI_VOICE) ? "You will now hear AI announcements." : "AI annoucements will now be converted to text."] ")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle AI Voice") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/proc/toggleadminhelpsound()
- set name = "Admin Bwoinks"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggle hearing a notification when admin PMs are received"
- if(!check_rights(R_ADMIN))
- return
- prefs.sound ^= SOUND_ADMINHELP
- prefs.save_preferences(src)
- to_chat(usr, "You will [(prefs.sound & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Admin Bwoinks") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/proc/togglementorhelpsound()
- set name = "Mentorhelp Bwoinks"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggle hearing a notification when mentorhelps are received"
- if(!check_rights(R_ADMIN|R_MENTOR))
- return
- prefs.sound ^= SOUND_MENTORHELP
- prefs.save_preferences(src)
- to_chat(usr, "You will [(prefs.sound & SOUND_MENTORHELP) ? "now" : "no longer"] hear a sound when mentorhelps arrive.")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Mentor Bwoinks") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/deadchat() // Deadchat toggle is usable by anyone.
- set name = "Deadchat"
- set category = "Preferences.Show/Hide"
- set desc ="Toggles seeing deadchat"
- prefs.toggles ^= PREFTOGGLE_CHAT_DEAD
- prefs.save_preferences(src)
-
- if(src.holder)
- to_chat(src, "You will [(prefs.toggles & PREFTOGGLE_CHAT_DEAD) ? "now" : "no longer"] see deadchat.")
- else
- to_chat(src, "As a ghost, you will [(prefs.toggles & PREFTOGGLE_CHAT_DEAD) ? "now" : "no longer"] see deadchat.")
-
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Deadchat") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/proc/toggleprayers()
- set name = "Prayers"
- set category = "Preferences.Show/Hide"
- set desc = "Toggles seeing prayers"
- prefs.toggles ^= PREFTOGGLE_CHAT_PRAYER
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles & PREFTOGGLE_CHAT_PRAYER) ? "now" : "no longer"] see prayerchat.")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Prayers") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/toggleprayernotify()
- set name = "Prayer Notification Sound"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggles hearing when prayers are made"
- prefs.sound ^= SOUND_PRAYERNOTIFY
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.sound & SOUND_PRAYERNOTIFY) ? "now" : "no longer"] hear when prayers are made.")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Prayer Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/togglescoreboard()
- set name = "End Round Scoreboard"
- set category = "Preferences.Show/Hide"
- set desc = "Toggles displaying end of round scoreboard"
- prefs.toggles ^= PREFTOGGLE_DISABLE_SCOREBOARD
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles & PREFTOGGLE_DISABLE_SCOREBOARD) ? "no longer" : "now"] see the end of round scoreboard.")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Scoreboard") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/toggletitlemusic()
- set name = "LobbyMusic"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggles hearing the GameLobby music"
- prefs.sound ^= SOUND_LOBBY
- prefs.save_preferences(src)
- if(prefs.sound & SOUND_LOBBY)
- to_chat(src, "You will now hear music in the game lobby.")
+/client/verb/setup_character()
+ set name = "Game Preferences"
+ set category = "Special Verbs"
+ prefs.current_tab = 1
+ prefs.ShowChoices(usr)
+
+// Preference toggles
+/datum/preference_toggle
+ /// Name of the preference toggle. Don't set this if you don't want it to appear in game
+ var/name
+ /// Bitflag this datum will set to
+ var/preftoggle_bitflag
+ /// Category of the toggle
+ var/preftoggle_category
+ /// What toggles to set this to?
+ var/preftoggle_toggle
+ /// Description of what the pref setting does
+ var/description
+ /// Message to display when this toggle is enabled
+ var/enable_message
+ /// Message to display when this toggle is disabled
+ var/disable_message
+ /// Message for the blackbox, legacy verbs so we can't just use the name
+ var/blackbox_message
+ /// Rights required to be able to use this pref option
+ var/rights_required
+
+/datum/preference_toggle/proc/set_toggles(client/user)
+ var/datum/preferences/our_prefs = user.prefs
+ switch(preftoggle_toggle)
+ if(PREFTOGGLE_SPECIAL)
+ CRASH("[src] did not have it's set_toggles overriden even though it was a special toggle, please use the special_toggle path!")
+ if(PREFTOGGLE_TOGGLE1)
+ our_prefs.toggles ^= preftoggle_bitflag
+ to_chat(user, "[(our_prefs.toggles & preftoggle_bitflag) ? enable_message : disable_message]")
+ if(PREFTOGGLE_TOGGLE2)
+ our_prefs.toggles2 ^= preftoggle_bitflag
+ to_chat(user, "[(our_prefs.toggles2 & preftoggle_bitflag) ? enable_message : disable_message]")
+ if(PREFTOGGLE_SOUND)
+ our_prefs.sound ^= preftoggle_bitflag
+ to_chat(user, "[(our_prefs.sound & preftoggle_bitflag) ? enable_message : disable_message]")
+
+ SSblackbox.record_feedback("tally", "toggle_verbs", 1, blackbox_message)
+ our_prefs.save_preferences(user)
+
+/datum/preference_toggle/toggle_ghost_ears
+ name = "Toggle Hearing All Speech as a Ghost"
+ description = "Toggle Between seeing all mob speech, and only speech of nearby mobs"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_GHOSTEARS
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_GHOST
+ enable_message = "As a ghost, you will now only see speech from nearby mobs."
+ disable_message = "As a ghost, you will now see all speech in the world."
+ blackbox_message = "Toggle GhostEars"
+
+/datum/preference_toggle/toggle_ghost_sight
+ name = "Toggle Ghost Emote Viewing"
+ description = "Toggle Between seeing all mob emotes, and only emotes of nearby mobs"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_GHOSTSIGHT
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_GHOST
+ enable_message = "As a ghost, you will now only see speech from nearby mobs."
+ disable_message = "As a ghost, you will now see all emotes in the world."
+ blackbox_message = "Toggle GhostSight"
+
+/datum/preference_toggle/toggle_ghost_radio
+ name = "Toggle Ghost Radio"
+ description = "Toggle between hearing all radio chatter, or only from nearby speakers"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_GHOSTRADIO
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_GHOST
+ enable_message = "As a ghost, you will now only hear from nearby speakers."
+ disable_message = "As a ghost, you will now hear all radio chat in the world."
+ blackbox_message = "Toggle GhostRadio"
+
+/datum/preference_toggle/toggle_admin_radio
+ name = "Admin Radio"
+ description = "Toggle seeing radiochatter from radios and speakers"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_RADIO
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_ADMIN
+ rights_required = R_ADMIN
+ enable_message = "You will no longer see radio chatter from radios or speakers."
+ disable_message = "You will now see radio chatter from radios or speakers."
+ blackbox_message = "Toggle RadioChatter"
+
+/datum/preference_toggle/toggle_ai_voice_annoucements
+ name = "AI Voice Announcements"
+ description = "Toggle hearing AI annoucements in voice form or in text form"
+ preftoggle_bitflag = SOUND_AI_VOICE
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now hear AI announcements."
+ disable_message = "You will now hear AI announcements."
+ blackbox_message = "Toggle AI Voice"
+
+/datum/preference_toggle/toggle_admin_pm_sound
+ name = "Admin PM sound"
+ description = "Toggle hearing a notification when admin PMs are received"
+ preftoggle_bitflag = SOUND_ADMINHELP
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_ADMIN
+ rights_required = R_ADMIN
+ enable_message = "You will now hear a sound when adminhelp is sent."
+ disable_message = "You will no longer hear a sound when adminhelp is sent."
+ blackbox_message = "Toggle Admin Bwoinks"
+
+/datum/preference_toggle/toggle_mentor_pm_sound
+ name = "Mentor PM sound"
+ description = "Toggle hearing a notification when mentor PMs are received"
+ preftoggle_bitflag = SOUND_MENTORHELP
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_ADMIN
+ rights_required = R_MENTOR
+ enable_message = "You will now hear a sound when mentorhelp is sent."
+ disable_message = "You will no longer hear a sound when mentorhelp is sent."
+ blackbox_message = "Toggle Mentor Bwoinks"
+
+/datum/preference_toggle/toggle_deadchat_visibility
+ name = "Toggle Deadchat visibility"
+ description = "Toggles Dchat's visibility"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_DEAD
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now see deadchat."
+ disable_message = "You will no longer see deadchat."
+ blackbox_message = "Toggle Deadchat"
+
+/datum/preference_toggle/end_of_round_scoreboard
+ name = "Toggle the End of Round Scoreboard"
+ description = "Prevents you from seeing the end of round scoreboard"
+ preftoggle_bitflag = PREFTOGGLE_DISABLE_SCOREBOARD
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now see the end of round scoreboard."
+ disable_message = "You will no longer see see the end of round scoreboard."
+ blackbox_message = "Toggle Scoreboard"
+
+/datum/preference_toggle/title_music
+ name = "Toggle Lobby Music"
+ description = "Toggles hearing the GameLobby music"
+ preftoggle_bitflag = SOUND_LOBBY
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now hear music in the game lobby."
+ disable_message = "You will no longer hear music in the game lobby."
+ blackbox_message = "Toggle Lobby Music"
+
+/datum/preference_toggle/title_music/set_toggles(client/user)
+ . = ..()
+ if(user.prefs.sound & SOUND_LOBBY)
if(isnewplayer(usr))
- usr.client.playtitlemusic()
+ user.playtitlemusic()
else
- to_chat(src, "You will no longer hear music in the game lobby.")
usr.stop_sound_channel(CHANNEL_LOBBYMUSIC)
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Lobby Music") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/togglemidis()
- set name = "Midis"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggles hearing sounds uploaded by admins"
- prefs.sound ^= SOUND_MIDI
- prefs.save_preferences(src)
- if(prefs.sound & SOUND_MIDI)
- to_chat(src, "You will now hear any sounds uploaded by admins.")
- else
+/datum/preference_toggle/toggle_admin_midis
+ name = "Toggle Admin Midis"
+ description = "Toggles hearing sounds uploaded by admins"
+ preftoggle_bitflag = SOUND_MIDI
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now hear any sounds uploaded by admins."
+ disable_message = "You will no longer hear sounds uploaded by admins; any currently playing midis have been disabled."
+ blackbox_message = "Toggle MIDIs"
+
+/datum/preference_toggle/toggle_admin_midis/set_toggles(client/user)
+ . = ..()
+ if(user.prefs.sound & ~SOUND_LOBBY)
usr.stop_sound_channel(CHANNEL_ADMIN)
- to_chat(src, "You will no longer hear sounds uploaded by admins; any currently playing midis have been disabled.")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle MIDIs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/listen_ooc()
- set name = "OOC (Out of Character)"
- set category = "Preferences.Show/Hide"
- set desc = "Toggles seeing OutOfCharacter chat"
- prefs.toggles ^= PREFTOGGLE_CHAT_OOC
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles & PREFTOGGLE_CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel.")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle OOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-
-/client/verb/listen_looc()
- set name = "LOOC (Local Out of Character)"
- set category = "Preferences.Show/Hide"
- set desc = "Toggles seeing Local OutOfCharacter chat"
- prefs.toggles ^= PREFTOGGLE_CHAT_LOOC
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles & PREFTOGGLE_CHAT_LOOC) ? "now" : "no longer"] see messages on the LOOC channel.")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle LOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-
-/client/verb/Toggle_Soundscape() //All new ambience should be added here so it works with this verb until someone better at things comes up with a fix that isn't awful
- set name = "Ambience"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggles hearing ambient sound effects"
- prefs.sound ^= SOUND_AMBIENCE
- prefs.save_preferences(src)
- if(prefs.sound & SOUND_AMBIENCE)
- to_chat(src, "You will now hear ambient sounds.")
- else
- to_chat(src, "You will no longer hear ambient sounds.")
+/datum/preference_toggle/toggle_ooc
+ name = "Toggle OOC chat"
+ description = "Toggles seeing OutOfCharacter chat"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_OOC
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now see the OOC channel."
+ disable_message = "You will no longer see the OOC channel."
+ blackbox_message = "Toggle OOC"
+
+/datum/preference_toggle/toggle_looc
+ name = "Toggle LOOC chat"
+ description = "Toggles seeing Local OutOfCharacter chat"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_LOOC
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now see the LOOC channel."
+ disable_message = "You will no longer see the LOOC channel."
+ blackbox_message = "Toggle LOOC"
+
+/datum/preference_toggle/toggle_ambience
+ name = "Toggle Ambient sounds"
+ description = "Toggles hearing ambient sound effects"
+ preftoggle_bitflag = SOUND_AMBIENCE
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You now hear ambient sounds."
+ disable_message = "Ambience is now silenced."
+ blackbox_message = "Toggle Ambience"
+
+/datum/preference_toggle/toggle_ambience/set_toggles(client/user)
+ . = ..()
+ if(user.prefs.sound & ~SOUND_AMBIENCE)
usr.stop_sound_channel(CHANNEL_AMBIENCE)
- update_ambience_pref()
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Ambience") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/Toggle_Parallax_Dark() //All new ambience should be added here so it works with this verb until someone better at things comes up with a fix that isn't awful
- set name = "Parallax in darkness"
- set category = "Preferences.Show/Hide"
- set desc = "If enabled, drawing parallax if you see in dark instead of black tiles."
- prefs.toggles2 ^= PREFTOGGLE_2_PARALLAX_IN_DARKNESS
- prefs.save_preferences(src)
- if(prefs.toggles2 & PREFTOGGLE_2_PARALLAX_IN_DARKNESS)
- to_chat(src, "You will now see parallax in dark with nightvisions.")
- else
- to_chat(src, "You will no longer see parallax in dark with nightvisions.")
+ user.update_ambience_pref()
+
+/datum/preference_toggle/toggle_parallax_in_darkness
+ name = "Toggle Parallax in darkness"
+ description = "Toggles seeing space tiles instead of blank tiles"
+ preftoggle_bitflag = PREFTOGGLE_2_PARALLAX_IN_DARKNESS
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now see parallax in dark with nightvision."
+ disable_message = "You will no longer see parallax in dark with nightvision."
+ blackbox_message = "Toggle Parallax Darkness"
+
+/datum/preference_toggle/toggle_parallax_in_darkness/set_toggles(client/user)
+ . = ..()
usr.hud_used?.update_parallax_pref()
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Parallax Darkness") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/Toggle_Buzz() //No more headaches because headphones bump up shipambience.ogg to insanity levels.
- set name = "White Noise"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggles hearing ambient white noise"
- prefs.sound ^= SOUND_BUZZ
- prefs.save_preferences(src)
- if(prefs.sound & SOUND_BUZZ)
- to_chat(src, "You will now hear ambient white noise.")
- else
- to_chat(src, "You will no longer hear ambient white noise.")
- usr.stop_sound_channel(CHANNEL_BUZZ)
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Whitenoise") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+/datum/preference_toggle/toggle_white_noise
+ name = "Toggle White Noise"
+ description = "Toggles hearing White Noise"
+ preftoggle_bitflag = SOUND_BUZZ
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now hear ambient white noise."
+ disable_message = "You will no longer hear ambient white noise."
+ blackbox_message = "Toggle Whitenoise"
+
+/datum/preference_toggle/toggle_white_noise/set_toggles(client/user)
+ . = ..()
+ if(user.prefs.sound & ~SOUND_BUZZ)
+ usr.stop_sound_channel(CHANNEL_BUZZ)
-/client/verb/Toggle_Heartbeat() //to toggle off heartbeat sounds, in case they get too annoying
- set name = "Heartbeat"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggles hearing heart beating sound effects"
- prefs.sound ^= SOUND_HEARTBEAT
- prefs.save_preferences(src)
- if(prefs.sound & SOUND_HEARTBEAT)
- to_chat(src, "You will now hear heartbeat sounds.")
- else
- to_chat(src, "You will no longer hear heartbeat sounds.")
+/datum/preference_toggle/toggle_heartbeat_noise
+ name = "Toggle Heartbeat noise"
+ description = "Toggles hearing heartbeat sounds"
+ preftoggle_bitflag = SOUND_HEARTBEAT
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now hear heartbeat sounds."
+ disable_message = "You will no longer hear heartbeat sounds."
+ blackbox_message = "Toggle Hearbeat"
+
+/datum/preference_toggle/toggle_heartbeat_noise/set_toggles(client/user)
+ . = ..()
+ if(user.prefs.sound & ~SOUND_HEARTBEAT)
usr.stop_sound_channel(CHANNEL_HEARTBEAT)
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Hearbeat") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-// This needs a toggle because you people are awful and spammed terrible music
-/client/verb/toggle_instruments()
- set name = "Instruments"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggles hearing musical instruments like the violin and piano"
- prefs.sound ^= SOUND_INSTRUMENTS
- prefs.save_preferences(src)
- if(prefs.sound & SOUND_INSTRUMENTS)
- to_chat(src, "You will now hear people playing musical instruments.")
- else
- to_chat(src, "You will no longer hear musical instruments.")
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Instruments") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/client/verb/toggle_input()
- set name = "TGUI Input"
- set category = "Preferences.Toggle"
- set desc = "Switches inputs between the TGUI and the standard one"
- prefs.toggles2 ^= PREFTOGGLE_2_DISABLE_TGUI_INPUT
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles2 & PREFTOGGLE_2_DISABLE_TGUI_INPUT) ? "no longer" : "now"] use TGUI Inputs.")
-
-/client/verb/Toggle_disco() //to toggle off the disco machine locally, in case it gets too annoying
- set name = "Dance Machine"
- set category = "Preferences.Hear/Silence"
- set desc = "Toggles hearing and dancing to the radiant dance machine"
- prefs.sound ^= SOUND_DISCO
- prefs.save_preferences(src)
- if(prefs.sound & SOUND_DISCO)
- to_chat(src, "You will now hear and dance to the radiant dance machine.")
- else
- to_chat(src, "You will no longer hear or dance to the radiant dance machine.")
- usr.stop_sound_channel(CHANNEL_JUKEBOX)
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Dance Machine") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/client/verb/setup_character()
- set name = "Game Preferences"
- set category = "Preferences"
- set desc = "Allows you to access the Setup Character screen. Changes to your character won't take effect until next round, but other changes will."
- prefs.current_tab = 1
- prefs.ShowChoices(usr)
+/datum/preference_toggle/toggle_instruments
+ name = "Toggle Instruments"
+ description = "Toggles hearing musical instruments like the violin and piano"
+ preftoggle_bitflag = SOUND_INSTRUMENTS
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now hear people playing musical instruments."
+ disable_message = "You will no longer hear musical instruments."
+ blackbox_message = "Toggle Instruments"
+
+/datum/preference_toggle/toggle_disco
+ name = "Toggle Disco Machine Music"
+ description = "Toggles hearing musical instruments like the violin and piano"
+ preftoggle_bitflag = SOUND_DISCO
+ preftoggle_toggle = PREFTOGGLE_SOUND
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now hear and dance to the radiant dance machine."
+ disable_message = "You will no longer hear or dance to the radiant dance machine."
+ blackbox_message = "Toggle Dance Machine"
+
+/datum/preference_toggle/toggle_disco/set_toggles(client/user)
+ . = ..()
+ if(user.prefs.sound & ~SOUND_DISCO)
+ usr.stop_sound_channel(CHANNEL_JUKEBOX)
-/client/verb/toggle_ghost_pda()
- set name = "GhostPDA"
- set category = "Preferences.Show/Hide"
- set desc = "Toggle seeing PDA messages as an observer."
- prefs.toggles ^= PREFTOGGLE_CHAT_GHOSTPDA
- to_chat(src, "As a ghost, you will now [(prefs.toggles & PREFTOGGLE_CHAT_GHOSTPDA) ? "see all PDA messages" : "no longer see PDA messages"].")
- prefs.save_preferences(src)
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Ghost PDA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+/datum/preference_toggle/toggle_ghost_pda
+ name = "Toggle Ghost PDA messages"
+ description = "Toggle seeing PDA messages as an observer"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_GHOSTPDA
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_GHOST
+ enable_message = "As a ghost, you will now see all PDA messages."
+ disable_message = "As a ghost, you will no longer see PDA messages."
+ blackbox_message = "Toggle Ghost PDA"
/client/verb/silence_current_midi()
set name = "Silence Current Midi"
- set category = "Preferences"
+ set category = "Special Verbs"
set desc = "Silence the current admin midi playing"
usr.stop_sound_channel(CHANNEL_ADMIN)
to_chat(src, "The current admin midi has been silenced")
-
-/client/verb/toggle_runechat()
- set name = "Runechat"
- set category = "Preferences.Toggle"
- set desc = "Toggle runechat messages"
- prefs.toggles2 ^= PREFTOGGLE_2_RUNECHAT
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles2 & PREFTOGGLE_2_RUNECHAT) ? "now see" : "no longer see"] floating chat messages.")
-
-/client/verb/toggle_death_messages()
- set name = "Death Notifications"
- set category = "Preferences.Toggle"
- set desc = "Toggle player death notifications"
- prefs.toggles2 ^= PREFTOGGLE_2_DEATHMESSAGE
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles2 & PREFTOGGLE_2_DEATHMESSAGE) ? "now" : "no longer"] see a notification in deadchat when a player dies.")
-
-/client/verb/toggle_reverb()
- set name = "Reverb"
- set category = "Preferences.Toggle"
- set desc = "Toggle ingame reverb effects"
- prefs.toggles2 ^= PREFTOGGLE_2_REVERB_DISABLE
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles2 & PREFTOGGLE_2_REVERB_DISABLE) ? "no longer" : "now"] get reverb on ingame sounds.")
-
-/client/verb/toggle_forced_white_runechat()
- set name = "Runechat Colour Forcing"
- set category = "Preferences.Toggle"
- set desc = "Toggles forcing your runechat colour to white"
- prefs.toggles2 ^= PREFTOGGLE_2_FORCE_WHITE_RUNECHAT
- prefs.save_preferences(src)
- to_chat(src, "Your runechats will [(prefs.toggles2 & PREFTOGGLE_2_FORCE_WHITE_RUNECHAT) ? "now" : "no longer"] be forced to be white.")
-
-/client/verb/toggle_item_outlines()
- set name = "Item Outlines"
- set category = "Preferences.Toggle"
- set desc = "Toggles seeing item outlines on hover."
- prefs.toggles2 ^= PREFTOGGLE_2_SEE_ITEM_OUTLINES
- prefs.save_preferences(src)
- to_chat(usr, "You will [(prefs.toggles2 & PREFTOGGLE_2_SEE_ITEM_OUTLINES) ? "now" : "no longer"] see item outlines on hover.")
-
-/client/verb/toggle_item_tooltips()
- set name = "Hover-over Item Tooltips"
- set category = "Preferences.Toggle"
- set desc = "Toggles textboxes with the item descriptions after hovering on them in your inventory."
- prefs.toggles2 ^= PREFTOGGLE_2_HIDE_ITEM_TOOLTIPS
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles2 & PREFTOGGLE_2_HIDE_ITEM_TOOLTIPS) ? "no longer" : "now"] see item tooltips when you hover over items on your HUD.")
-
-/mob/verb/toggle_anonmode()
- set name = "Anonymous Mode"
- set category = "Preferences.Toggle"
- set desc = "Toggles showing your key in various parts of the game (deadchat, end round, etc)."
- client.prefs.toggles2 ^= PREFTOGGLE_2_ANON
- to_chat(src, "Your key will [(client.prefs.toggles2 & PREFTOGGLE_2_ANON) ? "no longer" : "now"] be shown in certain events (end round reports, deadchat, etc).")
- client.prefs.save_preferences(src)
-
-/client/verb/toggle_dance()
- set name = "Disco Machine Dancing"
- set category = "Preferences.Toggle"
- set desc = "Toggles automatic dancing from the radiant dance machine"
- prefs.toggles2 ^= PREFTOGGLE_2_DANCE_DISCO
- prefs.save_preferences(src)
- to_chat(usr, "You will [(prefs.toggles2 & PREFTOGGLE_2_DANCE_DISCO) ? "now" : "no longer"] dance to the radiant dance machine.")
-
-/client/verb/manage_adminsound_mutes()
- set name = "Manage Admin Sound Mutes"
- set category = "Preferences"
- set desc = "Manage admins that you wont hear played audio from"
-
- if(!length(prefs.admin_sound_ckey_ignore))
+/datum/preference_toggle/toggle_runechat
+ name = "Toggle Runechat"
+ description = "Toggle seeing Runechat messages"
+ preftoggle_bitflag = PREFTOGGLE_2_RUNECHAT
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now see runechat."
+ disable_message = "You will no longer see runechat."
+ blackbox_message = "Toggle Runechat"
+
+/datum/preference_toggle/toggle_runechat
+ name = "Toggle Ghost Death Notifications"
+ description = "Toggle a notification when a player dies"
+ preftoggle_bitflag = PREFTOGGLE_2_DEATHMESSAGE
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_GHOST
+ enable_message = "You will now see a notification in deadchat when a player dies."
+ disable_message = "You will no longer see a notification in deadchat when a player dies."
+ blackbox_message = "Toggle Death Notifications"
+
+/datum/preference_toggle/toggle_reverb
+ name = "Toggle Reverb"
+ description = "Toggles Reverb on specific sounds"
+ preftoggle_bitflag = PREFTOGGLE_2_REVERB_DISABLE
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now get reverb on some in game sounds."
+ disable_message = "You will no longer get reverb on some in game sounds."
+ blackbox_message = "Toggle reverb"
+
+/datum/preference_toggle/toggle_white_runechat
+ name = "Toggle Runechat Colour Forcing"
+ description = "Forces your runechat color to white"
+ preftoggle_bitflag = PREFTOGGLE_2_FORCE_WHITE_RUNECHAT
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "Your runechat messages are forced to be white."
+ disable_message = "Your runechat messages are no longer forced to be white."
+ blackbox_message = "Toggle runechat color"
+
+/datum/preference_toggle/toggle_simple_stat_panel
+ name = "Toggle item outlines"
+ description = "Toggles seeing item outlines on hover"
+ preftoggle_bitflag = PREFTOGGLE_2_SEE_ITEM_OUTLINES
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_LIVING
+ enable_message = "You no longer see item outlines when hovering over an item with your mouse."
+ disable_message = "You now see item outlines when hovering over an item with your mouse."
+ blackbox_message = "Toggle item outlines"
+
+/datum/preference_toggle/toggle_item_tooltips
+ name = "Toggle item tooltips"
+ description = "Toggles textboxes with the item descriptions after hovering on them in your inventory"
+ preftoggle_bitflag = PREFTOGGLE_2_HIDE_ITEM_TOOLTIPS
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_LIVING
+ enable_message = "You no longer see item tooltips."
+ disable_message = "You now see item tooltips."
+ blackbox_message = "Toggle item tooltips"
+
+/datum/preference_toggle/toggle_anonmode
+ name = "Toggle Anonymous Mode"
+ description = "Toggles showing your key in various parts of the game (deadchat, end round, etc)"
+ preftoggle_bitflag = PREFTOGGLE_2_ANON
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "Your key will no longer be shown in certain events (end round reports, deadchat, etc)."
+ disable_message = "Your key will now will be shown in certain events (end round reports, deadchat, etc)."
+ blackbox_message = "Toggle Anon mode"
+
+/datum/preference_toggle/toggle_disco_dance
+ name = "Toggle Disco Machine Dancing"
+ description = "Toggles automatic dancing from the radiant dance machine"
+ preftoggle_bitflag = PREFTOGGLE_2_DANCE_DISCO
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_LIVING
+ enable_message = "You will now dance to the radiant dance machine."
+ disable_message = "You will no longer dance to the radiant dance machine."
+ blackbox_message = "Toggle disco machine dancing"
+
+/datum/preference_toggle/toggle_typing_indicator
+ name = "Toggle Typing Indicator"
+ description = "Hides the typing indicator"
+ preftoggle_bitflag = PREFTOGGLE_SHOW_TYPING
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_LIVING
+ enable_message = "You will no longer display a typing indicator."
+ disable_message = "You will now display a typing indicator."
+ blackbox_message = "Toggle Typing Indicator (Speech)"
+
+/datum/preference_toggle/toggle_typing_indicator/set_toggles(client/user)
+ . = ..()
+ if(user.prefs.toggles & PREFTOGGLE_SHOW_TYPING)
+ if(istype(usr))
+ usr.set_typing_indicator(FALSE)
+ usr.set_thinking_indicator(FALSE)
+
+/datum/preference_toggle/toggle_tgui_input_lists
+ name = "Toggle TGUI Input"
+ description = "Switches input lists between the TGUI and the standard one"
+ preftoggle_bitflag = PREFTOGGLE_2_DISABLE_TGUI_INPUT
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now use TGUI Input."
+ disable_message = "You will no longer use TGUI Input."
+ blackbox_message = "Toggle TGUI Input"
+
+/datum/preference_toggle/toggle_admin_logs
+ name = "Toggle Admin Log Messages"
+ description = "Disables admin log messages"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_NO_ADMINLOGS
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_ADMIN
+ rights_required = R_ADMIN
+ enable_message = "Admin logs disabled."
+ disable_message = "Admin logs re-enabled."
+ blackbox_message = "Admin logs toggled"
+
+/datum/preference_toggle/toggle_mhelp_notification
+ name = "Toggle Mentor Ticket Messages"
+ description = "Disables mentor ticket notifications"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_NO_MENTORTICKETLOGS
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_ADMIN
+ rights_required = R_MENTOR | R_ADMIN
+ enable_message = "You now won't get mentor ticket messages."
+ disable_message = "You now will get mentor ticket messages."
+ blackbox_message = "Mentor ticket notification toggled"
+
+/datum/preference_toggle/toggle_ahelp_notification
+ name = "Toggle Admin Ticket Messages"
+ description = "Disables admin ticket notifications"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_NO_TICKETLOGS
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_ADMIN
+ rights_required = R_ADMIN
+ enable_message = "You now won't get admin ticket messages."
+ disable_message = "You now will get admin ticket messages."
+ blackbox_message = "Admin ticket notification toggled"
+
+/datum/preference_toggle/toggle_debug_logs
+ name = "Toggle Debug Log Messages"
+ description = "Disables debug notifications (Runtimes, ghost role notifications, weird checks that weren't removed)"
+ preftoggle_bitflag = PREFTOGGLE_CHAT_DEBUGLOGS
+ preftoggle_toggle = PREFTOGGLE_TOGGLE1
+ preftoggle_category = PREFTOGGLE_CATEGORY_ADMIN
+ rights_required = R_VIEWRUNTIMES | R_DEBUG
+ enable_message = "You now won't get debug logs."
+ disable_message = "You now will get debug logs."
+ blackbox_message = "Debug logs toggled"
+
+/datum/preference_toggle/toggle_mctabs
+ name = "Toggle MC tab"
+ description = "Toggles MC tab visibility"
+ preftoggle_bitflag = PREFTOGGLE_2_MC_TAB
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_ADMIN
+ rights_required = R_VIEWRUNTIMES | R_DEBUG
+ enable_message = "You'll now see subsystem information in the verb panel."
+ disable_message = "You'll no longer see subsystem information in the verb panel."
+ blackbox_message = "MC tabs toggled"
+
+/datum/preference_toggle/special_toggle
+ preftoggle_toggle = PREFTOGGLE_SPECIAL
+
+/datum/preference_toggle/special_toggle/set_toggles(client/user)
+ SSblackbox.record_feedback("tally", "toggle_verbs", 1, blackbox_message)
+ user.prefs.save_preferences(user)
+
+/datum/preference_toggle/special_toggle/toggle_adminsound_mutes
+ name = "Manage Admin Sound Mutes"
+ description = "Manage admins that you wont hear played audio from"
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ blackbox_message = "MC tabs toggled"
+
+/datum/preference_toggle/special_toggle/toggle_adminsound_mutes/set_toggles(client/user)
+ if(!length(user.prefs.admin_sound_ckey_ignore))
to_chat(usr, "You have no admins with muted sounds.")
return
- var/choice = input(usr, "Select an admin to unmute sounds from.", "Pick an admin") as null|anything in prefs.admin_sound_ckey_ignore
+ var/choice = input(usr, "Select an admin to unmute sounds from.", "Pick an admin") as null|anything in user.prefs.admin_sound_ckey_ignore
if(!choice)
return
- prefs.admin_sound_ckey_ignore -= choice
+ user.prefs.admin_sound_ckey_ignore -= choice
to_chat(usr, "You will now hear sounds from [choice]
again.")
- prefs.save_preferences(src)
-
-/client/proc/toggle_mctabs()
- set name = "MC Tab"
- set category = "Preferences.Show/Hide"
- set desc = "Shows or hides the MC tab."
- prefs.toggles2 ^= PREFTOGGLE_2_MC_TAB
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles2 & PREFTOGGLE_2_MC_TAB) ? "now" : "no longer"] see the MC tab on the top right.")
+ return ..()
+
+/datum/preference_toggle/special_toggle/set_ooc_color
+ name = "Set Your OOC Color"
+ description = "Pick a custom OOC color"
+ preftoggle_category = PREFTOGGLE_CATEGORY_ADMIN
+ rights_required = R_ADMIN
+ blackbox_message = "Set Own OOC"
+
+/datum/preference_toggle/special_toggle/set_ooc_color/set_toggles(client/user)
+ var/new_ooccolor = input(usr, "Please select your OOC color.", "OOC color", user.prefs.ooccolor) as color|null
+ if(new_ooccolor)
+ user.prefs.ooccolor = new_ooccolor
+ to_chat(usr, "Your OOC color has been set to [new_ooccolor].")
+ else
+ user.prefs.ooccolor = initial(user.prefs.ooccolor)
+ to_chat(usr, "Your OOC color has been reset.")
+ return ..()
+
+/datum/preference_toggle/special_toggle/set_attack_logs
+ name = "Change Attack Log settings"
+ description = "Changes what attack logs you see, ranges from all attacklogs to no attacklogs"
+ preftoggle_category = PREFTOGGLE_CATEGORY_ADMIN
+ rights_required = R_ADMIN
+ blackbox_message = "changed attack log settings"
+
+/datum/preference_toggle/special_toggle/set_attack_logs/set_toggles(client/user)
+ var/static/list/attack_log_settings = list("All attack logs" = ATKLOG_ALL, "Almost all attack logs" = ATKLOG_ALMOSTALL, "Most attack logs" = ATKLOG_MOST, "Few attack logs" = ATKLOG_FEW, "No attack logs" = ATKLOG_NONE)
+ var/input = input(usr, "Please select your Attack Log settings.") as null|anything in attack_log_settings
+ if(!input)
+ return
+ var/attack_log_type = attack_log_settings[input]
+ switch(attack_log_type)
+ if(ATKLOG_ALL)
+ user.prefs.atklog = ATKLOG_ALL
+ to_chat(usr, "Your attack logs preference is now: show ALL attack logs")
+ if(ATKLOG_ALMOSTALL)
+ user.prefs.atklog = ATKLOG_ALMOSTALL
+ to_chat(usr, "Your attack logs preference is now: show ALMOST ALL attack logs (notable exceptions: NPCs attacking other NPCs, vampire bites, equipping/stripping, people pushing each other over)")
+ if(ATKLOG_MOST)
+ user.prefs.atklog = ATKLOG_MOST
+ to_chat(usr, "Your attack logs preference is now: show MOST attack logs (like ALMOST ALL, except that it also hides player v. NPC combat, and certain areas like lavaland syndie base and thunderdome)")
+ if(ATKLOG_FEW)
+ user.prefs.atklog = ATKLOG_FEW
+ to_chat(usr, "Your attack logs preference is now: show FEW attack logs (only the most important stuff: attacks on SSDs, use of explosives, messing with the engine, gibbing, AI wiping, forcefeeding, acid sprays, and organ extraction)")
+ if(ATKLOG_NONE)
+ user.prefs.atklog = ATKLOG_NONE
+ to_chat(usr, "Your attack logs preference is now: show NO attack logs")
+ return ..()
diff --git a/code/modules/client/preference/preferences_volume_mixer.dm b/code/modules/client/preference/preferences_volume_mixer.dm
index c9e84373153b..61f3e134f65b 100644
--- a/code/modules/client/preference/preferences_volume_mixer.dm
+++ b/code/modules/client/preference/preferences_volume_mixer.dm
@@ -88,7 +88,7 @@
/client/verb/volume_mixer()
set name = "Open Volume Mixer"
- set category = "Preferences"
+ set category = null
set hidden = TRUE
var/datum/ui_module/volume_mixer/VM = new()
diff --git a/code/modules/client/view.dm b/code/modules/client/view.dm
index 7832ba0419fc..18966f49678a 100644
--- a/code/modules/client/view.dm
+++ b/code/modules/client/view.dm
@@ -4,9 +4,6 @@
* Also includes
*/
-/* Defines */
-#define CUSTOM_VIEWRANGES list(1, 2, 3, 4, 5, 6, "RESET")
-
/client/proc/AddViewMod(id, size)
var/datum/viewmod/V = new /datum/viewmod(id, size)
ViewMods[V.id] = V
@@ -69,22 +66,3 @@
/* Client verbs */
/proc/viewNum_to_text(view)
return "[(view * 2) + 1]x[(view * 2) + 1]"
-
-/client/verb/set_view_range()
- set name = "Set View Range"
- set category = "Preferences"
-
- var/view_range = tgui_input_list(src.mob, "Select a view range", "Set View Range", CUSTOM_VIEWRANGES, "RESET")
-
- if(!view_range)
- return
-
- RemoveViewMod("custom")
- if(view_range == "RESET")
- to_chat(src, "View range reset.")
- return
-
- to_chat(src, "View range set to [viewNum_to_text(view_range)]")
- AddViewMod("custom", view_range)
-
-#undef CUSTOM_VIEWRANGES
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 1a057782f162..72465c1a2a44 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -611,6 +611,23 @@
/obj/item/clothing/suit/proc/setup_shielding()
return
+///Hierophant card shielding. Saves me time.
+/obj/item/clothing/suit/proc/setup_hierophant_shielding()
+ var/datum/component/shielded/shield = GetComponent(/datum/component/shielded)
+ if(!shield)
+ AddComponent(/datum/component/shielded, recharge_start_delay = 0 SECONDS, shield_icon = "shield-hierophant", run_hit_callback = CALLBACK(src, PROC_REF(hierophant_shield_damaged)))
+ return
+ if(shield.shield_icon == "shield-hierophant") //If the hierophant shield has been used, recharge it. Otherwise, it's a shielded component we don't want to touch
+ shield.current_charges = 3
+
+/// A proc for callback when the shield breaks, since I am stupid and want custom effects.
+/obj/item/clothing/suit/proc/hierophant_shield_damaged(mob/living/wearer, attack_text, new_current_charges)
+ wearer.visible_message("[attack_text] is deflected in a burst of dark-purple sparks!")
+ new /obj/effect/temp_visual/cult/sparks/hierophant(get_turf(wearer))
+ playsound(wearer,'sound/magic/blind.ogg', 200, TRUE, -2)
+ if(new_current_charges == 0)
+ wearer.visible_message("The runed shield around [wearer] suddenly disappears!")
+
//Proc that opens and closes jackets.
/obj/item/clothing/suit/proc/adjustsuit(mob/user)
if(ignore_suitadjust)
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index 65386ffc6de1..099d24c5e85e 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -69,6 +69,18 @@
if(23)
new /obj/item/borg/upgrade/modkit/lifesteal(src)
new /obj/item/bedsheet/cult(src)
+ if(24)
+ switch(rand(1, 11))
+ if(1)
+ new /obj/item/blank_tarot_card(src)
+ if(2 to 5)
+ new /obj/item/tarot_card_pack(src)
+ if(6 to 8)
+ new /obj/item/tarot_card_pack/jumbo(src)
+ if(9, 10)
+ new /obj/item/tarot_card_pack/mega(src)
+ if(11)
+ new /obj/item/tarot_generator(src) // ~1/250? Seems reasonable
//KA modkit design discs
/obj/item/disk/design_disk/modkit_disk
diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm
index 7a51a6bc812d..aa68095ad627 100644
--- a/code/modules/mob/living/simple_animal/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs.dm
@@ -46,38 +46,12 @@
set_light(2, 3, l_color = GET_CULT_DATA(construct_glow, LIGHT_COLOR_BLOOD_MAGIC))
-/mob/living/simple_animal/hostile/construct/Destroy()
- mind?.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
- mind?.remove_antag_datum(/datum/antagonist/wizard/construct, silent_removal = TRUE)
- remove_held_body()
- return ..()
-
/mob/living/simple_animal/hostile/construct/death(gibbed)
- mind?.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
- mind?.remove_antag_datum(/datum/antagonist/wizard/construct, silent_removal = TRUE)
- if(held_body) // Null check for empty bodies
- held_body.forceMove(get_turf(src))
- SSticker.mode?.cult_team?.add_cult_immunity(held_body)
- if(ismob(held_body)) // Check if the held_body is a mob
- held_body.key = key
- else if(istype(held_body, /obj/item/organ/internal/brain)) // Check if the held_body is a brain
- var/obj/item/organ/internal/brain/brain = held_body
- if(brain.brainmob) // Check if the brain has a brainmob
- brain.brainmob.key = key // Set the key to the brainmob
- brain.brainmob.mind.transfer_to(brain.brainmob) // Transfer the mind to the brainmob
- held_body.cancel_camera()
+ // we also drop our heldbody from the /construct_held_body component, as well as our cult/wiz construct antag datums
new /obj/effect/temp_visual/cult/sparks(get_turf(src))
playsound(src, 'sound/effects/pylon_shatter.ogg', 40, TRUE)
return ..()
-/mob/living/simple_animal/hostile/construct/proc/add_held_body(atom/movable/body)
- held_body = body
- RegisterSignal(body, COMSIG_PARENT_QDELETING, PROC_REF(remove_held_body))
-
-/mob/living/simple_animal/hostile/construct/proc/remove_held_body()
- SIGNAL_HANDLER
- held_body = null
-
/mob/living/simple_animal/hostile/construct/examine(mob/user)
. = ..()
diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm
index d89aed14643b..2fecb79c254c 100644
--- a/code/modules/mob/living/simple_animal/shade.dm
+++ b/code/modules/mob/living/simple_animal/shade.dm
@@ -34,11 +34,6 @@
deathmessage = "lets out a contented sigh as their form unwinds."
var/holy = FALSE
-/mob/living/simple_animal/shade/Destroy()
- mind?.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
- mind?.remove_antag_datum(/datum/antagonist/wizard/construct, silent_removal = TRUE)
- return ..()
-
/mob/living/simple_animal/shade/attackby(obj/item/O, mob/user) //Marker -Agouri
if(istype(O, /obj/item/soulstone))
var/obj/item/soulstone/SS = O
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index f2ff761520e0..ed9505e5c13b 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -152,7 +152,6 @@
real_name = name
if(!loc)
stack_trace("Simple animal being instantiated in nullspace")
- remove_verb(src, /mob/verb/observe)
if(can_hide)
var/datum/action/innate/hide/hide = new()
hide.Grant(src)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index bca60f87eee1..158b59b5ff9b 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -769,112 +769,8 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
else
return "[copytext_preserve_html(msg, 1, 37)]... More..."
-// Nobody in their right mind will have this enabled on the production server, uncomment if you want this for some reason
-/*
-/mob/verb/abandon_mob()
- set name = "Respawn"
- set category = "OOC"
-
- if(!GLOB.configuration.general.respawn_enabled)
- to_chat(usr, "Respawning is disabled.")
- return
-
- if(stat != DEAD || !SSticker)
- to_chat(usr, "You must be dead to use this!")
- return
-
- log_game("[key_name(usr)] has respawned.")
-
- to_chat(usr, "Make sure to play a different character, and please roleplay correctly!")
-
- if(!client)
- log_game("[key_name(usr)] respawn failed due to disconnect.")
- return
- client.screen.Cut()
- client.screen += client.void
-
- if(!client)
- log_game("[key_name(usr)] respawn failed due to disconnect.")
- return
-
- var/mob/new_player/M = new /mob/new_player()
- if(!client)
- log_game("[key_name(usr)] respawn failed due to disconnect.")
- qdel(M)
- return
-
- M.key = key
- return
-
-*/
-/mob/verb/observe()
- set name = "Observe"
- set category = "OOC"
- var/is_admin = 0
-
- if(client.holder && (client.holder.rights & R_ADMIN))
- is_admin = 1
- else if(stat != DEAD || isnewplayer(src))
- to_chat(usr, "You must be observing to use this!")
- return
-
- if(is_admin && stat == DEAD)
- is_admin = 0
-
- var/list/names = list()
- var/list/namecounts = list()
- var/list/creatures = list()
-
- for(var/obj/O in GLOB.poi_list)
- if(!O.loc)
- continue
- if(istype(O, /obj/item/disk/nuclear))
- var/name = "Nuclear Disk"
- if(names.Find(name))
- namecounts[name]++
- name = "[name] ([namecounts[name]])"
- else
- names.Add(name)
- namecounts[name] = 1
- creatures[name] = O
-
- if(istype(O, /obj/singularity))
- var/name = "Singularity"
- if(names.Find(name))
- namecounts[name]++
- name = "[name] ([namecounts[name]])"
- else
- names.Add(name)
- namecounts[name] = 1
- creatures[name] = O
-
-
- for(var/mob/M in sortAtom(GLOB.mob_list))
- var/name = M.name
- if(names.Find(name))
- namecounts[name]++
- name = "[name] ([namecounts[name]])"
- else
- names.Add(name)
- namecounts[name] = 1
-
- creatures[name] = M
-
-
- client.perspective = EYE_PERSPECTIVE
-
- var/eye_name = null
-
- var/ok = "[is_admin ? "Admin Observe" : "Observe"]"
- eye_name = tgui_input_list(usr, "Please, select a player!", ok, creatures)
-
- if(!eye_name)
- return
-
- var/mob/mob_eye = creatures[eye_name]
-
- if(client && mob_eye)
- client.eye = mob_eye
+/mob/proc/is_dead()
+ return stat == DEAD
/mob/verb/cancel_camera()
set name = "Cancel Camera View"
diff --git a/code/modules/mob/typing_indicator.dm b/code/modules/mob/typing_indicator.dm
index dc361ba9e2dd..02df559452db 100644
--- a/code/modules/mob/typing_indicator.dm
+++ b/code/modules/mob/typing_indicator.dm
@@ -92,19 +92,3 @@ GLOBAL_LIST_EMPTY(thinking_indicator)
set_typing_indicator(FALSE)
if(message)
me_verb(message)
-
-/client/verb/typing_indicator()
- set name = "Typing Indicator"
- set category = "Preferences.Show/Hide"
- set desc = "Toggles showing a typing/thought indicator when you have TGUIsay open."
- prefs.toggles ^= PREFTOGGLE_SHOW_TYPING
- prefs.save_preferences(src)
- to_chat(src, "You will [(prefs.toggles & PREFTOGGLE_SHOW_TYPING) ? "no longer" : "now"] display a typing/thought indicator when you have TGUIsay open.")
-
- // Clear out any existing typing indicator.
- if(prefs.toggles & PREFTOGGLE_SHOW_TYPING)
- if(istype(mob))
- mob.set_typing_indicator(FALSE)
- mob.set_thinking_indicator(FALSE)
-
- SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Typing Indicator (Speech)") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/projectiles/ammunition/ammo_casings.dm b/code/modules/projectiles/ammunition/ammo_casings.dm
index 7e43f0ada910..bc55296544c2 100644
--- a/code/modules/projectiles/ammunition/ammo_casings.dm
+++ b/code/modules/projectiles/ammunition/ammo_casings.dm
@@ -245,6 +245,7 @@
/obj/item/ammo_casing/shotgun/ion
name = "ion shell"
desc = "An advanced 12 gauge shell that fires a spread of ion bolts."
+ icon_state = "ionshell"
projectile_type = /obj/item/projectile/ion/weak
pellets = 4
variance = 35
diff --git a/code/modules/reagents/chemistry/reagents_holder.dm b/code/modules/reagents/chemistry/reagents_holder.dm
index 851788b90d88..4d68ff36daa4 100644
--- a/code/modules/reagents/chemistry/reagents_holder.dm
+++ b/code/modules/reagents/chemistry/reagents_holder.dm
@@ -969,6 +969,17 @@
var/picked_reagent = pick(random_reagents)
return picked_reagent
+/// Returns a random reagent ID, with real non blacklisted balance boosting action!
+/proc/get_unrestricted_random_reagent_id()
+ var/static/list/random_reagents
+ if(!length(random_reagents))
+ random_reagents = list()
+ for(var/datum/reagent/thing as anything in subtypesof(/datum/reagent))
+ var/R = initial(thing.id)
+ random_reagents += R
+ var/picked_reagent = pick(random_reagents)
+ return picked_reagent
+
/datum/reagents/proc/get_reagent_from_id(id)
var/datum/reagent/result = null
for(var/A in reagent_list)
diff --git a/code/modules/station_goals/bluespace_tap.dm b/code/modules/station_goals/bluespace_tap.dm
index cfa51561e127..3f88756eeb36 100644
--- a/code/modules/station_goals/bluespace_tap.dm
+++ b/code/modules/station_goals/bluespace_tap.dm
@@ -116,7 +116,11 @@
/obj/item/bedsheet/cult = 2,
/obj/item/bedsheet/wiz = 2,
/obj/item/stack/sheet/mineral/tranquillite/fifty = 3,
- /obj/item/clothing/gloves/combat = 5
+ /obj/item/clothing/gloves/combat = 5,
+ /obj/item/blank_tarot_card = 5,
+ /obj/item/tarot_card_pack = 5,
+ /obj/item/tarot_card_pack/jumbo = 3,
+ /obj/item/tarot_card_pack/mega = 2
)
/obj/effect/spawner/lootdrop/bluespace_tap/organic
diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm
index c2e847de6a97..9dac53823a47 100644
--- a/code/modules/surgery/organs/heart.dm
+++ b/code/modules/surgery/organs/heart.dm
@@ -292,7 +292,7 @@
/obj/item/organ/internal/heart/cybernetic/upgraded/proc/shock_heart(mob/living/carbon/human/source, intensity)
- SIGNAL_HANDLER_DOES_SLEEP
+ SIGNAL_HANDLER // COMSIG_LIVING_MINOR_SHOCK + COMSIG_LIVING_ELECTROCUTE_ACT
if(!ishuman(owner))
return
@@ -305,10 +305,11 @@
if(emagged && !(status & ORGAN_DEAD))
if(prob(numHigh))
to_chat(owner, "Your [name] spasms violently!")
- owner.adjustBruteLoss(numHigh)
+ // invoke asyncs here because this sleeps
+ INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob/living/carbon/human, adjustBruteLoss), numHigh)
if(prob(numHigh))
to_chat(owner, "Your [name] shocks you painfully!")
- owner.adjustFireLoss(numHigh)
+ INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob/living/carbon/human, adjustFireLoss), numHigh)
if(prob(numMid))
to_chat(owner, "Your [name] lurches awkwardly!")
owner.ForceContractDisease(new /datum/disease/critical/heart_failure(0))
@@ -318,14 +319,14 @@
heart_datum.change_beating(FALSE) // Rambunctious Crew - Stop My Fucking Heart
if(prob(numLow))
to_chat(owner, "Your [name] shuts down!")
- necrotize()
+ INVOKE_ASYNC(src, PROC_REF(necrotize))
else if(!emagged && !(status & ORGAN_DEAD))
if(prob(numMid))
to_chat(owner, "Your [name] spasms violently!")
- owner.adjustBruteLoss(numMid)
+ INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob/living/carbon/human, adjustBruteLoss), numMid)
if(prob(numMid))
to_chat(owner, "Your [name] shocks you painfully!")
- owner.adjustFireLoss(numMid)
+ INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob/living/carbon/human, adjustFireLoss), numMid)
if(prob(numLow))
to_chat(owner, "Your [name] lurches awkwardly!")
owner.ForceContractDisease(new /datum/disease/critical/heart_failure(0))
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index d24193821713..656a8380279d 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/obj/card.dmi b/icons/obj/card.dmi
index 7596aae9be05..3818dd761e92 100644
Binary files a/icons/obj/card.dmi and b/icons/obj/card.dmi differ
diff --git a/icons/obj/playing_cards.dmi b/icons/obj/playing_cards.dmi
index 866cf0a5fc42..58d9508af72d 100644
Binary files a/icons/obj/playing_cards.dmi and b/icons/obj/playing_cards.dmi differ
diff --git a/modular_ss220/text_to_speech/code/tts_component.dm b/modular_ss220/text_to_speech/code/tts_component.dm
index 85579d2c9406..6acd969be636 100644
--- a/modular_ss220/text_to_speech/code/tts_component.dm
+++ b/modular_ss220/text_to_speech/code/tts_component.dm
@@ -89,8 +89,10 @@
return new_tts_seed
/datum/component/tts_component/proc/tts_seed_change(atom/being_changed, mob/chooser, override = FALSE, fancy_voice_input_tgui = FALSE, list/new_traits = null)
- SIGNAL_HANDLER_DOES_SLEEP
- set waitfor = FALSE
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, PROC_REF(set_tts_seed), being_changed, chooser, override, fancy_voice_input_tgui, new_traits)
+
+/datum/component/tts_component/proc/set_tts_seed(atom/being_changed, mob/chooser, override = FALSE, fancy_voice_input_tgui = FALSE, list/new_traits = null)
var/datum/tts_seed/new_tts_seed = select_tts_seed(chooser = chooser, override = override, fancy_voice_input_tgui = fancy_voice_input_tgui, new_traits = new_traits)
if(!new_tts_seed)
return null
diff --git a/paradise.dme b/paradise.dme
index 89bc1814f21d..63585d4056ae 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -200,6 +200,7 @@
#include "code\_globalvars\lists\mob_lists.dm"
#include "code\_globalvars\lists\names.dm"
#include "code\_globalvars\lists\objects.dm"
+#include "code\_globalvars\lists\preference_toggle_lists.dm"
#include "code\_globalvars\lists\reagents_lists.dm"
#include "code\_globalvars\lists\typecache.dm"
#include "code\_onclick\adjacent.dm"
@@ -402,6 +403,7 @@
#include "code\datums\components\codeword_hearing.dm"
#include "code\datums\components\connect_mob_behalf.dm"
#include "code\datums\components\corpse_description.dm"
+#include "code\datums\components\cult_held_body.dm"
#include "code\datums\components\deadchat_control.dm"
#include "code\datums\components\decal.dm"
#include "code\datums\components\defibrillator.dm"
@@ -754,6 +756,7 @@
#include "code\game\gamemodes\vampire\vampire_gamemode.dm"
#include "code\game\gamemodes\wizard\artefact.dm"
#include "code\game\gamemodes\wizard\godhand.dm"
+#include "code\game\gamemodes\wizard\magic_tarot.dm"
#include "code\game\gamemodes\wizard\raginmages.dm"
#include "code\game\gamemodes\wizard\rightandwrong.dm"
#include "code\game\gamemodes\wizard\soulstone.dm"