From 3333fd051ff6e6feb70690782215d936cbaa245d Mon Sep 17 00:00:00 2001
From: larentoun <31931237+larentoun@users.noreply.github.com>
Date: Wed, 24 Apr 2024 22:38:47 +0300
Subject: [PATCH 01/14] if you have PROCCALL rights, you can call it from VV
(#25030)
---
code/datums/datumvars.dm | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
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
From 0c5fdaa57be0b8c2fec282f71b40a44295b3adab Mon Sep 17 00:00:00 2001
From: Luc <89928798+lewcc@users.noreply.github.com>
Date: Wed, 24 Apr 2024 15:40:45 -0400
Subject: [PATCH 02/14] Updates contributing guide regarding signals, removes
SIGNAL_HANDLER_DOES_SLEEP (#24824)
* Updates contributing guide, removes DOES_SLEEP
* Update .github/CONTRIBUTING.md
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
---------
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
---
.github/CONTRIBUTING.md | 30 ++++++++++++++++++++++++++--
code/__DEFINES/dcs/dcs_helpers.dm | 4 ----
code/modules/surgery/organs/heart.dm | 13 ++++++------
3 files changed, 35 insertions(+), 12 deletions(-)
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/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/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))
From e615738e22d572b2d9cf2f7ece45b73991e20cc5 Mon Sep 17 00:00:00 2001
From: S34N <12197162+S34NW@users.noreply.github.com>
Date: Wed, 24 Apr 2024 20:42:12 +0100
Subject: [PATCH 03/14] All your TGUI belong to us (#25250)
---
.github/CODEOWNERS | 5 +++++
1 file changed, 5 insertions(+)
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
From 69d6a8df2110f1b7d7fdec9306f3e84ed8c36aa8 Mon Sep 17 00:00:00 2001
From: GDN <96800819+GDNgit@users.noreply.github.com>
Date: Wed, 24 Apr 2024 16:21:11 -0500
Subject: [PATCH 04/14] Overhauls player facing verb UI (#24060)
* Preference verb overhaul
* this too
* Update code/__DEFINES/preferences_defines.dm
* Update code/modules/client/preference/preferences.dm
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
* fixes + better html
* removes unused define
---------
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
---
code/__DEFINES/preferences_defines.dm | 18 +-
code/__HELPERS/global_lists.dm | 5 +
.../lists/preference_toggle_lists.dm | 8 +
code/_onclick/hud/human_hud.dm | 13 -
.../sections/general_configuration.dm | 1 -
.../subsystem/non_firing/SSchangelog.dm | 2 +-
code/datums/action.dm | 2 +-
code/datums/spell.dm | 2 -
code/datums/spell_cooldown/spell_charges.dm | 2 +-
code/datums/spell_cooldown/spell_cooldown.dm | 2 +-
code/datums/spells/bloodcrawl.dm | 1 -
code/datums/spells/mime.dm | 4 -
code/game/dna/mutations/disabilities.dm | 1 -
code/game/dna/mutations/mutation_powers.dm | 6 -
.../demons/shadow_demon/shadow_demon.dm | 2 -
.../demons/slaughter_demon/slaughter.dm | 1 -
.../pulsedemon/pulsedemon_abilities.dm | 1 -
.../miniantags/revenant/revenant_abilities.dm | 4 -
code/game/jobs/job_exp.dm | 12 -
code/game/verbs/ooc.dm | 55 --
code/modules/admin/admin_verbs.dm | 107 +--
code/modules/admin/verbs/custom_event.dm | 14 +-
.../vampire_powers/gargantua_powers.dm | 1 -
.../vampire_powers/hemomancer_powers.dm | 1 -
.../vampire/vampire_powers/vampire_powers.dm | 1 -
.../client/preference/link_processing.dm | 6 +-
code/modules/client/preference/preferences.dm | 43 +
.../client/preference/preferences_toggles.dm | 835 +++++++++++-------
.../preference/preferences_volume_mixer.dm | 2 +-
code/modules/client/view.dm | 22 -
.../mob/living/simple_animal/simple_animal.dm | 1 -
code/modules/mob/mob.dm | 108 +--
code/modules/mob/typing_indicator.dm | 16 -
config/example/config.toml | 2 -
paradise.dme | 1 +
35 files changed, 597 insertions(+), 705 deletions(-)
create mode 100644 code/_globalvars/lists/preference_toggle_lists.dm
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/__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..923bc2f4e61a 100644
--- a/code/controllers/configuration/sections/general_configuration.dm
+++ b/code/controllers/configuration/sections/general_configuration.dm
@@ -93,7 +93,6 @@
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(forbid_singulo_possession, data["prevent_admin_singlo_possession"])
CONFIG_LOAD_BOOL(popup_admin_pm, data["popup_admin_pm"])
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/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/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/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/jobs/job_exp.dm b/code/game/jobs/job_exp.dm
index d60339f08e69..79a7af64eac4 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/verbs/ooc.dm b/code/game/verbs/ooc.dm
index 043e2d7b6904..fe2374e5940a 100644
--- a/code/game/verbs/ooc.dm
+++ b/code/game/verbs/ooc.dm
@@ -106,61 +106,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/client/preference/link_processing.dm b/code/modules/client/preference/link_processing.dm
index 76c8ac966f16..7ed56e997593 100644
--- a/code/modules/client/preference/link_processing.dm
+++ b/code/modules/client/preference/link_processing.dm
@@ -1265,6 +1265,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 ad75383205cd..bb2816be5567 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 += "
"
@@ -476,6 +477,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)
@@ -587,6 +594,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/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 666f0c493092..cee530c18506 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/config/example/config.toml b/config/example/config.toml
index fedba7ae4a34..c1d747f02fa8 100644
--- a/config/example/config.toml
+++ b/config/example/config.toml
@@ -283,8 +283,6 @@ guest_ban = true
allow_antag_hud = true
# Forbid players from rejoining if they use antag hud
restrict_antag_hud_rejoin = true
-# Do we want to allow player respawns?
-respawn_enabled = false
# Enable/disable the buster for the CID randomiser DLL
enable_cid_randomiser_buster = false
# Prevent admins from possessing the singularity
diff --git a/paradise.dme b/paradise.dme
index bdac76b51058..be76cb94d91e 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -197,6 +197,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"
From af5d187014386ee61ce11f7a63b893f5e4e66f2b Mon Sep 17 00:00:00 2001
From: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com>
Date: Wed, 24 Apr 2024 19:10:46 -0400
Subject: [PATCH 05/14] Adds magic tarot cards [SERIOUS DO NOT CLOSE AFTER AFD]
(#24933)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* starts it
* Update magic_tarot.dm
* MORE PROGRESS
* So many thanks to Cloudbucket, for sprites
* The Work, the Art, continues.
* more changes
* pushes the Work foward. All cards done.
* and more thanks github
* adds to BSH
* tochat
* Finishes QWERTYTODO. Magic key. World smoke
* Isaac and his mother lived alone in a small house on a hill. Isaac kept to himself, drawing pictures and playing with his toys as his mom watched Christian broadcasts on the television. Life was simple, and they were both happy. That was, until the day Isaac’s mom heard a voice from above. “Your son has become corrupted by sin. He needs to be saved.” “I will do my best to save him, my Lord,” Isaac’s mother replied, rushing into Isaac’s room, removing all that was evil from his life. Again the voice called to her, "Isaac’s soul is still corrupt. He needs to be cut off from all that is evil in this world and confess his sins." “I will follow your instructions, Lord. I have faith in thee,” Isaac’s mother replied, as she locked Isaac in his room, away from the evils of the world. One last time, Isaac’s mom heard the voice of God calling to her. “You've done as I've asked, but I still question your devotion to me. To prove your faith, I will ask one more thing of you." "Yes Lord, anything!” Isaac’s mother begged. "To prove your love and devotion, I require a sacrifice. Your son, Isaac, will be this sacrifice. Go into his room and end his life, as an offering to me to prove you love me above all else." "Yes Lord", she replied, grabbing a butcher’s knife from the kitchen. Isaac, watching through a crack in his door, trembled in fear. Scrambling around his room to find a hiding place, he noticed a trap door to the basement hidden under his rug. Without hesitation, he flung open the hatch, just as his mother burst through his door, and threw himself down into the unknown depths below.
* nanny bag it before I forget
* Apply suggestions from code review
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
* returns early
* Apply suggestions from code review
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
* RE-RE-RE-REVIEW
* Apply suggestions from code review
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
Co-authored-by: synthtee <127706731+SynthTwo@users.noreply.github.com>
* requested changes and such
* duh
* Apply suggestions from code review
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
* returns early
* Update code/datums/status_effects/buffs.dm
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
* new hrefs here to troll CI :trollface:
* Update code/game/gamemodes/wizard/magic_tarot.dm
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
Signed-off-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com>
---------
Signed-off-by: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com>
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Co-authored-by: synthtee <127706731+SynthTwo@users.noreply.github.com>
---
.../SpaceRuins/intactemptyship.dmm | 7 +-
.../RandomRuins/SpaceRuins/wizardcrash.dmm | 1 +
code/__DEFINES/status_effects.dm | 12 +
code/datums/status_effects/buffs.dm | 105 ++-
code/datums/status_effects/debuffs.dm | 26 +
code/datums/uplink_items/uplink_traitor.dm | 11 +
code/game/gamemodes/cult/cult_structures.dm | 2 +-
code/game/gamemodes/wizard/magic_tarot.dm | 885 ++++++++++++++++++
code/game/gamemodes/wizard/rightandwrong.dm | 10 +-
code/game/gamemodes/wizard/spellbook.dm | 9 +
.../effects/temporary_visuals/cult_visuals.dm | 7 +
.../items/stacks/sheets/sheet_types.dm | 2 +-
code/game/objects/items/weapons/cards_ids.dm | 15 +
.../items/weapons/grenades/clusterbuster.dm | 5 +
.../objects/items/weapons/storage/backpack.dm | 3 +-
.../crates_lockers/closets/statue.dm | 14 +
.../mission_code/ruins/wizardcrash.dm | 6 +-
code/modules/clothing/clothing.dm | 17 +
.../mining/lavaland/necropolis_chests.dm | 12 +
.../reagents/chemistry/reagents_holder.dm | 11 +
code/modules/station_goals/bluespace_tap.dm | 6 +-
icons/effects/effects.dmi | Bin 495955 -> 489526 bytes
icons/obj/card.dmi | Bin 15698 -> 15978 bytes
icons/obj/playing_cards.dmi | Bin 8109 -> 37789 bytes
paradise.dme | 1 +
25 files changed, 1154 insertions(+), 13 deletions(-)
create mode 100644 code/game/gamemodes/wizard/magic_tarot.dm
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/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/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/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/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/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/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/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/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/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/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 852149ec7715..a339ad902a09 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/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/icons/effects/effects.dmi b/icons/effects/effects.dmi
index d24193821713d28d284fd6b92f509ee812063693..656a8380279dfdef2675fcc9ceda93378d7b742c 100644
GIT binary patch
literal 489526
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