From f87aa0ac5b2e8cc7f12dfa27dde38c10f6b1495e Mon Sep 17 00:00:00 2001
From: Letter N <24603524+LetterN@users.noreply.github.com>
Date: Mon, 8 Feb 2021 18:46:05 +0800
Subject: [PATCH] toast. And DiscoFox:tm:
---
code/__DEFINES/achievements.dm | 99 ++
code/__DEFINES/colors.dm | 6 +
code/__DEFINES/medal.dm | 29 -
code/__DEFINES/misc.dm | 7 +-
code/__DEFINES/subsystems.dm | 2 +-
code/__DEFINES/vv.dm | 3 +
code/__HELPERS/filters.dm | 319 ++++++
code/__HELPERS/roundend.dm | 35 +
.../lists/{medals.dm => achievements.dm} | 0
code/_onclick/item_attack.dm | 3 +
code/controllers/subsystem/achievements.dm | 74 ++
code/controllers/subsystem/medals.dm | 87 --
code/controllers/subsystem/shuttle.dm | 69 +-
code/datums/achievements/_achievement_data.dm | 148 +++
code/datums/achievements/_awards.dm | 115 +++
code/datums/achievements/boss_achievements.dm | 130 +++
code/datums/achievements/boss_scores.dm | 54 +
code/datums/achievements/hardcore_random.dm | 4 +
.../datums/achievements/mafia_achievements.dm | 97 ++
code/datums/achievements/misc_achievements.dm | 155 +++
.../datums/achievements/skill_achievements.dm | 10 +
code/datums/components/pellet_cloud.dm | 5 +
code/game/atoms.dm | 51 +-
code/game/gamemodes/meteor/meteors.dm | 83 +-
code/game/machinery/autolathe.dm | 82 +-
code/game/machinery/computer/arcade.dm | 74 +-
code/game/machinery/computer/arcade/battle.dm | 517 +++++++---
.../machinery/computer/arcade/minesweeper.dm | 2 +-
.../machinery/computer/arcade/misc_arcade.dm | 13 +-
.../machinery/computer/arcade/orion_trail.dm | 420 +++++++-
code/game/objects/items/his_grace.dm | 25 +-
code/game/objects/items/weaponry.dm | 195 ++--
.../structures/lavaland/necropolis_tendril.dm | 6 +-
code/game/objects/structures/watercloset.dm | 9 +-
code/game/turfs/simulated/minerals.dm | 10 +-
code/modules/admin/admin_verbs.dm | 233 +++--
code/modules/admin/holder2.dm | 2 +
code/modules/admin/topic.dm | 3 -
code/modules/admin/verbs/debug.dm | 7 +-
code/modules/admin/verbs/diagnostics.dm | 28 +
code/modules/admin/{ => verbs}/secrets.dm | 928 ++++++++----------
.../admin/view_variables/filterrific.dm | 99 ++
.../view_variables/mass_edit_variables.dm | 7 +-
.../admin/view_variables/modify_variables.dm | 2 +-
code/modules/admin/view_variables/topic.dm | 6 +
.../admin/view_variables/topic_basic.dm | 2 +-
.../eldritch_cult/knowledge/ash_lore.dm | 3 +-
.../eldritch_cult/knowledge/flesh_lore.dm | 7 +-
.../eldritch_cult/knowledge/rust_lore.dm | 3 +-
.../modules/awaymissions/super_secret_room.dm | 2 +-
code/modules/client/client_procs.dm | 23 +
code/modules/client/player_details.dm | 17 +
code/modules/events/immovable_rod.dm | 1 +
code/modules/mafia/controller.dm | 366 +++----
code/modules/mafia/roles.dm | 81 +-
.../mob/living/carbon/human/human_defines.dm | 1 +
.../mob/living/simple_animal/bot/cleanbot.dm | 57 +-
.../hostile/megafauna/blood_drunk_miner.dm | 3 +
.../hostile/megafauna/bubblegum.dm | 21 +-
.../hostile/megafauna/colossus.dm | 7 +-
.../hostile/megafauna/demonic_frost_miner.dm | 3 +
.../simple_animal/hostile/megafauna/drake.dm | 5 +-
.../hostile/megafauna/hierophant.dm | 5 +-
.../simple_animal/hostile/megafauna/legion.dm | 5 +-
.../hostile/megafauna/megafauna.dm | 83 +-
.../hostile/megafauna/swarmer.dm | 5 +-
code/modules/surgery/surgery_step.dm | 15 +-
code/modules/vending/_vending.dm | 3 +
icons/UI_Icons/Achievements/Boss/bbgum.png | Bin 0 -> 4864 bytes
icons/UI_Icons/Achievements/Boss/colossus.png | Bin 0 -> 6049 bytes
icons/UI_Icons/Achievements/Boss/drake.png | Bin 0 -> 7847 bytes
.../UI_Icons/Achievements/Boss/hierophant.png | Bin 0 -> 2345 bytes
icons/UI_Icons/Achievements/Boss/legion.png | Bin 0 -> 7945 bytes
icons/UI_Icons/Achievements/Boss/miner.png | Bin 0 -> 3454 bytes
icons/UI_Icons/Achievements/Boss/swarmer.png | Bin 0 -> 4002 bytes
icons/UI_Icons/Achievements/Boss/tendril.png | Bin 0 -> 6594 bytes
.../UI_Icons/Achievements/Mafia/assistant.png | Bin 0 -> 2374 bytes
.../Achievements/Mafia/changeling.png | Bin 0 -> 2363 bytes
.../UI_Icons/Achievements/Mafia/chaplain.png | Bin 0 -> 2367 bytes
icons/UI_Icons/Achievements/Mafia/clown.png | Bin 0 -> 2363 bytes
.../UI_Icons/Achievements/Mafia/detective.png | Bin 0 -> 2357 bytes
.../UI_Icons/Achievements/Mafia/fugitive.png | Bin 0 -> 2387 bytes
icons/UI_Icons/Achievements/Mafia/hated.png | Bin 0 -> 2467 bytes
icons/UI_Icons/Achievements/Mafia/hop.png | Bin 0 -> 2358 bytes
icons/UI_Icons/Achievements/Mafia/lawyer.png | Bin 0 -> 2362 bytes
icons/UI_Icons/Achievements/Mafia/md.png | Bin 0 -> 2352 bytes
.../UI_Icons/Achievements/Mafia/nightmare.png | Bin 0 -> 2350 bytes
.../UI_Icons/Achievements/Mafia/obsessed.png | Bin 0 -> 2391 bytes
.../Achievements/Mafia/psychologist.png | Bin 0 -> 2348 bytes
.../Achievements/Mafia/thoughtfeeder.png | Bin 0 -> 2366 bytes
icons/UI_Icons/Achievements/Mafia/traitor.png | Bin 0 -> 2330 bytes
.../UI_Icons/Achievements/Misc/ascension.png | Bin 0 -> 3110 bytes
.../UI_Icons/Achievements/Misc/ashascend.png | Bin 0 -> 9641 bytes
.../UI_Icons/Achievements/Misc/clownking.png | Bin 0 -> 5118 bytes
.../Achievements/Misc/clownthanks.png | Bin 0 -> 3441 bytes
.../Achievements/Misc/featofstrength.png | Bin 0 -> 2969 bytes
.../Achievements/Misc/fleshascend.png | Bin 0 -> 8138 bytes
.../Achievements/Misc/frenchingthebubble.png | Bin 0 -> 4864 bytes
icons/UI_Icons/Achievements/Misc/helbital.png | Bin 0 -> 3413 bytes
icons/UI_Icons/Achievements/Misc/jackpot.png | Bin 0 -> 3037 bytes
.../UI_Icons/Achievements/Misc/longshift.png | Bin 0 -> 2436 bytes
icons/UI_Icons/Achievements/Misc/meteors.png | Bin 0 -> 7797 bytes
icons/UI_Icons/Achievements/Misc/rule8.png | Bin 0 -> 2924 bytes
.../UI_Icons/Achievements/Misc/rustascend.png | Bin 0 -> 8390 bytes
icons/UI_Icons/Achievements/Misc/snail.png | Bin 0 -> 2766 bytes
.../UI_Icons/Achievements/Misc/timewaste.png | Bin 0 -> 1468 bytes
.../Achievements/Misc/toolbox_soul.png | Bin 0 -> 6579 bytes
icons/UI_Icons/Achievements/Misc/upgrade.png | Bin 0 -> 3292 bytes
.../UI_Icons/Achievements/Misc/voidascend.png | Bin 0 -> 982 bytes
icons/UI_Icons/Achievements/Skills/mining.png | Bin 0 -> 3406 bytes
icons/UI_Icons/Achievements/baseboss.png | Bin 0 -> 1697 bytes
icons/UI_Icons/Achievements/basemafia.png | Bin 0 -> 2251 bytes
icons/UI_Icons/Achievements/basemisc.png | Bin 0 -> 2244 bytes
icons/UI_Icons/Achievements/baseskill.png | Bin 0 -> 2247 bytes
icons/UI_Icons/Achievements/default.png | Bin 0 -> 3483 bytes
icons/program_icons/robotact.gif | Bin 0 -> 134 bytes
strings/arcade.json | 282 ++++++
tgstation.dme | 18 +-
118 files changed, 3889 insertions(+), 1382 deletions(-)
create mode 100644 code/__DEFINES/achievements.dm
delete mode 100644 code/__DEFINES/medal.dm
create mode 100644 code/__HELPERS/filters.dm
rename code/_globalvars/lists/{medals.dm => achievements.dm} (100%)
mode change 100755 => 100644
create mode 100644 code/controllers/subsystem/achievements.dm
delete mode 100644 code/controllers/subsystem/medals.dm
create mode 100644 code/datums/achievements/_achievement_data.dm
create mode 100644 code/datums/achievements/_awards.dm
create mode 100644 code/datums/achievements/boss_achievements.dm
create mode 100644 code/datums/achievements/boss_scores.dm
create mode 100644 code/datums/achievements/hardcore_random.dm
create mode 100644 code/datums/achievements/mafia_achievements.dm
create mode 100644 code/datums/achievements/misc_achievements.dm
create mode 100644 code/datums/achievements/skill_achievements.dm
rename code/modules/admin/{ => verbs}/secrets.dm (53%)
create mode 100644 code/modules/admin/view_variables/filterrific.dm
create mode 100644 icons/UI_Icons/Achievements/Boss/bbgum.png
create mode 100644 icons/UI_Icons/Achievements/Boss/colossus.png
create mode 100644 icons/UI_Icons/Achievements/Boss/drake.png
create mode 100644 icons/UI_Icons/Achievements/Boss/hierophant.png
create mode 100644 icons/UI_Icons/Achievements/Boss/legion.png
create mode 100644 icons/UI_Icons/Achievements/Boss/miner.png
create mode 100644 icons/UI_Icons/Achievements/Boss/swarmer.png
create mode 100644 icons/UI_Icons/Achievements/Boss/tendril.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/assistant.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/changeling.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/chaplain.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/clown.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/detective.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/fugitive.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/hated.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/hop.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/lawyer.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/md.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/nightmare.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/obsessed.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/psychologist.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/thoughtfeeder.png
create mode 100644 icons/UI_Icons/Achievements/Mafia/traitor.png
create mode 100644 icons/UI_Icons/Achievements/Misc/ascension.png
create mode 100644 icons/UI_Icons/Achievements/Misc/ashascend.png
create mode 100644 icons/UI_Icons/Achievements/Misc/clownking.png
create mode 100644 icons/UI_Icons/Achievements/Misc/clownthanks.png
create mode 100644 icons/UI_Icons/Achievements/Misc/featofstrength.png
create mode 100644 icons/UI_Icons/Achievements/Misc/fleshascend.png
create mode 100644 icons/UI_Icons/Achievements/Misc/frenchingthebubble.png
create mode 100644 icons/UI_Icons/Achievements/Misc/helbital.png
create mode 100644 icons/UI_Icons/Achievements/Misc/jackpot.png
create mode 100644 icons/UI_Icons/Achievements/Misc/longshift.png
create mode 100644 icons/UI_Icons/Achievements/Misc/meteors.png
create mode 100644 icons/UI_Icons/Achievements/Misc/rule8.png
create mode 100644 icons/UI_Icons/Achievements/Misc/rustascend.png
create mode 100644 icons/UI_Icons/Achievements/Misc/snail.png
create mode 100644 icons/UI_Icons/Achievements/Misc/timewaste.png
create mode 100644 icons/UI_Icons/Achievements/Misc/toolbox_soul.png
create mode 100644 icons/UI_Icons/Achievements/Misc/upgrade.png
create mode 100644 icons/UI_Icons/Achievements/Misc/voidascend.png
create mode 100644 icons/UI_Icons/Achievements/Skills/mining.png
create mode 100644 icons/UI_Icons/Achievements/baseboss.png
create mode 100644 icons/UI_Icons/Achievements/basemafia.png
create mode 100644 icons/UI_Icons/Achievements/basemisc.png
create mode 100644 icons/UI_Icons/Achievements/baseskill.png
create mode 100644 icons/UI_Icons/Achievements/default.png
create mode 100644 icons/program_icons/robotact.gif
create mode 100644 strings/arcade.json
diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm
new file mode 100644
index 0000000000..1d2eb90ee4
--- /dev/null
+++ b/code/__DEFINES/achievements.dm
@@ -0,0 +1,99 @@
+// Keep the identifiers here below 32 characters, you can put the full display name in the actual achievement datum
+
+#define ACHIEVEMENT_DEFAULT "default"
+#define ACHIEVEMENT_SCORE "score"
+
+//Misc Medal hub IDs
+#define MEDAL_METEOR "Your Life Before Your Eyes"
+#define MEDAL_PULSE "Jackpot"
+#define MEDAL_TIMEWASTE "Overextended The Joke"
+#define MEDAL_RODSUPLEX "Feat of Strength"
+#define MEDAL_CLOWNCARKING "Round and Full"
+#define MEDAL_THANKSALOT "The Best Driver"
+#define MEDAL_HELBITALJANKEN "Hel-bent on Winning"
+#define MEDAL_MATERIALCRAFT "Getting an Upgrade"
+#define MEDAL_DISKPLEASE "Disk, Please!"
+#define MEDAL_GAMER "I'm Not Important"
+#define MEDAL_VENDORSQUISH "Teenage Anarchist"
+#define MEDAL_SWIRLIE "Bowl-d"
+#define MEDAL_SELFOUCH "Hands???"
+#define MEDAL_SANDMAN "Mister Sandman"
+#define MEDAL_CLEANBOSS "Cleanboss"
+#define MEDAL_RULE8 "Rule 8"
+#define MEDAL_LONGSHIFT "longshift"
+#define MEDAL_SNAIL "KKKiiilll mmmeee"
+#define MEDAL_LOOKOUTSIR "Look Out, Sir!"
+#define MEDAL_GOTTEM "GOTTEM"
+#define MEDAL_ASCENSION "Ascension"
+#define MEDAL_FRENCHING "FrenchingTheBubble"
+#define MEDAL_ASH_ASCENSION "Ash"
+#define MEDAL_FLESH_ASCENSION "Flesh"
+#define MEDAL_RUST_ASCENSION "Rust"
+#define MEDAL_VOID_ASCENSION "Void"
+#define MEDAL_TOOLBOX_SOUL "Toolsoul"
+
+//Skill medal hub IDs
+#define MEDAL_LEGENDARY_MINER "Legendary Miner"
+
+//Mafia medal hub IDs (wins)
+#define MAFIA_MEDAL_ASSISTANT "Assistant"
+#define MAFIA_MEDAL_DETECTIVE "Detective"
+#define MAFIA_MEDAL_PSYCHOLOGIST "Psychologist"
+#define MAFIA_MEDAL_CHAPLAIN "Chaplain"
+#define MAFIA_MEDAL_MD "Medical Doctor"
+#define MAFIA_MEDAL_LAWYER "Lawyer"
+#define MAFIA_MEDAL_HOP "Head of Personnel"
+#define MAFIA_MEDAL_CHANGELING "CHANGELING"
+#define MAFIA_MEDAL_THOUGHTFEEDER "Thoughtfeeder"
+#define MAFIA_MEDAL_TRAITOR "Traitor"
+#define MAFIA_MEDAL_NIGHTMARE "Nightmare"
+#define MAFIA_MEDAL_FUGITIVE "Fugitive"
+#define MAFIA_MEDAL_OBSESSED "Obsessed"
+#define MAFIA_MEDAL_CLOWN "Clown"
+
+//Mafia medal hub IDs (misc stuff)
+#define MAFIA_MEDAL_HATED "Universally Hated"
+
+//Boss medals
+
+// Medal hub IDs for boss medals (Pre-fixes)
+#define BOSS_MEDAL_ANY "Boss Killer"
+#define BOSS_MEDAL_MINER "Blood-drunk Miner Killer"
+#define BOSS_MEDAL_FROSTMINER "Demonic-frost Miner Killer"
+#define BOSS_MEDAL_BUBBLEGUM "Bubblegum Killer"
+#define BOSS_MEDAL_COLOSSUS "Colossus Killer"
+#define BOSS_MEDAL_DRAKE "Drake Killer"
+#define BOSS_MEDAL_HIEROPHANT "Hierophant Killer"
+#define BOSS_MEDAL_LEGION "Legion Killer"
+#define BOSS_MEDAL_TENDRIL "Tendril Exterminator"
+#define BOSS_MEDAL_SWARMERS "Swarmer Beacon Killer"
+#define BOSS_MEDAL_WENDIGO "Wendigo Killer"
+#define BOSS_MEDAL_KINGGOAT "King Goat Killer"
+
+#define BOSS_MEDAL_MINER_CRUSHER "Blood-drunk Miner Crusher"
+#define BOSS_MEDAL_FROSTMINER_CRUSHER "Demonic-frost Miner Crusher"
+#define BOSS_MEDAL_BUBBLEGUM_CRUSHER "Bubblegum Crusher"
+#define BOSS_MEDAL_COLOSSUS_CRUSHER "Colossus Crusher"
+#define BOSS_MEDAL_DRAKE_CRUSHER "Drake Crusher"
+#define BOSS_MEDAL_HIEROPHANT_CRUSHER "Hierophant Crusher"
+#define BOSS_MEDAL_LEGION_CRUSHER "Legion Crusher"
+#define BOSS_MEDAL_SWARMERS_CRUSHER "Swarmer Beacon Crusher"
+#define BOSS_MEDAL_WENDIGO_CRUSHER "Wendigo Crusher"
+#define BOSS_MEDAL_KINGGOAT_CRUSHER "King Goat Crusher"
+
+// Medal hub IDs for boss-kill scores
+#define BOSS_SCORE "Bosses Killed"
+#define MINER_SCORE "BDMs Killed"
+#define FROST_MINER_SCORE "DFMs Killed"
+#define BUBBLEGUM_SCORE "Bubblegum Killed"
+#define COLOSSUS_SCORE "Colossus Killed"
+#define DRAKE_SCORE "Drakes Killed"
+#define HIEROPHANT_SCORE "Hierophants Killed"
+#define LEGION_SCORE "Legion Killed"
+#define SWARMER_BEACON_SCORE "Swarmer Beacs Killed"
+#define WENDIGO_SCORE "Wendigos Killed"
+#define KINGGOAT_SCORE "King Goat Killed"
+#define TENDRIL_CLEAR_SCORE "Tendrils Killed"
+
+// DB IDs for hardcore random mode
+#define HARDCORE_RANDOM_SCORE "Hardcore Random Score"
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index fb461acfa4..9f45d8da79 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -52,3 +52,9 @@
#define COLOR_ASSEMBLY_BLUE "#38559E"
#define COLOR_ASSEMBLY_PURPLE "#6F6192"
#define COLOR_ASSEMBLY_PINK "#ff4adc"
+
+#define COLOR_WHITE "#FFFFFF"
+#define COLOR_VERY_LIGHT_GRAY "#EEEEEE"
+#define COLOR_SILVER "#C0C0C0"
+#define COLOR_GRAY "#808080"
+#define COLOR_HALF_TRANSPARENT_BLACK "#0000007A"
diff --git a/code/__DEFINES/medal.dm b/code/__DEFINES/medal.dm
deleted file mode 100644
index e723c7504e..0000000000
--- a/code/__DEFINES/medal.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-// Medal names
-#define BOSS_KILL_MEDAL "Killer"
-#define ALL_KILL_MEDAL "Exterminator" //Killing all of x type
-#define BOSS_KILL_MEDAL_CRUSHER "Crusher"
-
-//Defines for boss medals
-#define BOSS_MEDAL_MINER "Blood-drunk Miner"
-#define BOSS_MEDAL_BUBBLEGUM "Bubblegum"
-#define BOSS_MEDAL_COLOSSUS "Colossus"
-#define BOSS_MEDAL_DRAKE "Drake"
-#define BOSS_MEDAL_HIEROPHANT "Hierophant"
-#define BOSS_MEDAL_LEGION "Legion"
-#define BOSS_MEDAL_TENDRIL "Tendril"
-#define BOSS_MEDAL_SWARMERS "Swarmer Beacon"
-
-// Score names
-#define HIEROPHANT_SCORE "Hierophants Killed"
-#define BOSS_SCORE "Bosses Killed"
-#define BUBBLEGUM_SCORE "Bubblegum Killed"
-#define COLOSSUS_SCORE "Colossus Killed"
-#define DRAKE_SCORE "Drakes Killed"
-#define LEGION_SCORE "Legion Killed"
-#define SWARMER_BEACON_SCORE "Swarmer Beacons Killed"
-#define TENDRIL_CLEAR_SCORE "Tendrils Killed"
-
-//Misc medals
-#define MEDAL_METEOR "Your Life Before Your Eyes"
-#define MEDAL_PULSE "Jackpot"
-#define MEDAL_TIMEWASTE "Overextended The Joke"
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 30162594d6..51d1b618fc 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -435,8 +435,13 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
//text files
#define BRAIN_DAMAGE_FILE "traumas.json"
#define ION_FILE "ion_laws.json"
-#define REDPILL_FILE "redpill.json"
#define PIRATE_NAMES_FILE "pirates.json"
+#define REDPILL_FILE "redpill.json"
+#define ARCADE_FILE "arcade.json"
+// #define BOOMER_FILE "boomer.json"
+// #define LOCATIONS_FILE "locations.json"
+// #define WANTED_FILE "wanted_message.json"
+// #define VISTA_FILE "steve.json"
#define FLESH_SCAR_FILE "wounds/flesh_scar_desc.json"
#define BONE_SCAR_FILE "wounds/bone_scar_desc.json"
#define SCAR_LOC_FILE "wounds/scar_loc.json"
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index cbf701e1d3..77186cd07e 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -108,7 +108,7 @@
#define INIT_ORDER_SOUNDS 83
#define INIT_ORDER_INSTRUMENTS 82
#define INIT_ORDER_VIS 80
-// #define INIT_ORDER_ACHIEVEMENTS 77
+#define INIT_ORDER_ACHIEVEMENTS 77
#define INIT_ORDER_RESEARCH 75
#define INIT_ORDER_EVENTS 70
#define INIT_ORDER_JOBS 65
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index fe46fdc710..99a2e9d0ab 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -90,6 +90,9 @@
#define VV_HK_TRIGGER_EMP "empulse"
#define VV_HK_TRIGGER_EXPLOSION "explode"
#define VV_HK_AUTO_RENAME "auto_rename"
+// #define VV_HK_RADIATE "radiate"
+#define VV_HK_EDIT_FILTERS "edit_filters"
+// #define VV_HK_ADD_AI "add_ai"
// /obj
#define VV_HK_OSAY "osay"
diff --git a/code/__HELPERS/filters.dm b/code/__HELPERS/filters.dm
new file mode 100644
index 0000000000..7be7ca5d73
--- /dev/null
+++ b/code/__HELPERS/filters.dm
@@ -0,0 +1,319 @@
+#define ICON_NOT_SET "Not Set"
+
+//This is stored as a nested list instead of datums or whatever because it json encodes nicely for usage in tgui
+GLOBAL_LIST_INIT(master_filter_info, list(
+ "alpha" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "icon" = ICON_NOT_SET,
+ "render_source" = "",
+ "flags" = 0
+ ),
+ "flags" = list(
+ "MASK_INVERSE" = MASK_INVERSE,
+ "MASK_SWAP" = MASK_SWAP
+ )
+ ),
+ "angular_blur" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 1
+ )
+ ),
+ /* Not supported because making a proper matrix editor on the frontend would be a huge dick pain.
+ Uncomment if you ever implement it
+ "color" = list(
+ "defaults" = list(
+ "color" = matrix(),
+ "space" = FILTER_COLOR_RGB
+ )
+ ),
+ */
+ "displace" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = null,
+ "icon" = ICON_NOT_SET,
+ "render_source" = ""
+ )
+ ),
+ "drop_shadow" = list(
+ "defaults" = list(
+ "x" = 1,
+ "y" = -1,
+ "size" = 1,
+ "offset" = 0,
+ "color" = COLOR_HALF_TRANSPARENT_BLACK
+ )
+ ),
+ "blur" = list(
+ "defaults" = list(
+ "size" = 1
+ )
+ ),
+ "layer" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "icon" = ICON_NOT_SET,
+ "render_source" = "",
+ "flags" = FILTER_OVERLAY,
+ "color" = "",
+ "transform" = null,
+ "blend_mode" = BLEND_DEFAULT
+ )
+ ),
+ "motion_blur" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0
+ )
+ ),
+ "outline" = list(
+ "defaults" = list(
+ "size" = 0,
+ "color" = COLOR_BLACK,
+ "flags" = NONE
+ ),
+ "flags" = list(
+ "OUTLINE_SHARP" = OUTLINE_SHARP,
+ "OUTLINE_SQUARE" = OUTLINE_SQUARE
+ )
+ ),
+ "radial_blur" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 0.01
+ )
+ ),
+ "rays" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 16,
+ "color" = COLOR_WHITE,
+ "offset" = 0,
+ "density" = 10,
+ "threshold" = 0.5,
+ "factor" = 0,
+ "flags" = FILTER_OVERLAY | FILTER_UNDERLAY
+ ),
+ "flags" = list(
+ "FILTER_OVERLAY" = FILTER_OVERLAY,
+ "FILTER_UNDERLAY" = FILTER_UNDERLAY
+ )
+ ),
+ "ripple" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 1,
+ "repeat" = 2,
+ "radius" = 0,
+ "falloff" = 1,
+ "flags" = NONE
+ ),
+ "flags" = list(
+ "WAVE_BOUNDED" = WAVE_BOUNDED
+ )
+ ),
+ "wave" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 1,
+ "offset" = 0,
+ "flags" = NONE
+ ),
+ "flags" = list(
+ "WAVE_SIDEWAYS" = WAVE_SIDEWAYS,
+ "WAVE_BOUNDED" = WAVE_BOUNDED
+ )
+ )
+))
+
+#undef ICON_NOT_SET
+
+//Helpers to generate lists for filter helpers
+//This is the only practical way of writing these that actually produces sane lists
+/proc/alpha_mask_filter(x, y, icon/icon, render_source, flags)
+ . = list("type" = "alpha")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(icon))
+ .["icon"] = icon
+ if(!isnull(render_source))
+ .["render_source"] = render_source
+ if(!isnull(flags))
+ .["flags"] = flags
+
+/proc/angular_blur_filter(x, y, size)
+ . = list("type" = "angular_blur")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(size))
+ .["size"] = size
+
+/proc/color_matrix_filter(matrix/in_matrix, space)
+ . = list("type" = "color")
+ .["color"] = in_matrix
+ if(!isnull(space))
+ .["space"] = space
+
+/proc/displacement_map_filter(icon, render_source, x, y, size = 32)
+ . = list("type" = "displace")
+ if(!isnull(icon))
+ .["icon"] = icon
+ if(!isnull(render_source))
+ .["render_source"] = render_source
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(size))
+ .["size"] = size
+
+/proc/drop_shadow_filter(x, y, size, offset, color)
+ . = list("type" = "drop_shadow")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(offset))
+ .["offset"] = offset
+ if(!isnull(color))
+ .["color"] = color
+
+/proc/gauss_blur_filter(size)
+ . = list("type" = "blur")
+ if(!isnull(size))
+ .["size"] = size
+
+/proc/layering_filter(icon, render_source, x, y, flags, color, transform, blend_mode)
+ . = list("type" = "layer")
+ if(!isnull(icon))
+ .["icon"] = icon
+ if(!isnull(render_source))
+ .["render_source"] = render_source
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(color))
+ .["color"] = color
+ if(!isnull(flags))
+ .["flags"] = flags
+ if(!isnull(transform))
+ .["transform"] = transform
+ if(!isnull(blend_mode))
+ .["blend_mode"] = blend_mode
+
+/proc/motion_blur_filter(x, y)
+ . = list("type" = "motion_blur")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+
+/proc/outline_filter(size, color, flags)
+ . = list("type" = "outline")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(color))
+ .["color"] = color
+ if(!isnull(flags))
+ .["flags"] = flags
+
+/proc/radial_blur_filter(size, x, y)
+ . = list("type" = "radial_blur")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+
+/proc/rays_filter(size, color, offset, density, threshold, factor, x, y, flags)
+ . = list("type" = "rays")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(color))
+ .["color"] = color
+ if(!isnull(offset))
+ .["offset"] = offset
+ if(!isnull(density))
+ .["density"] = density
+ if(!isnull(threshold))
+ .["threshold"] = threshold
+ if(!isnull(factor))
+ .["factor"] = factor
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(flags))
+ .["flags"] = flags
+
+/proc/ripple_filter(radius, size, falloff, repeat, x, y, flags)
+ . = list("type" = "ripple")
+ if(!isnull(radius))
+ .["radius"] = radius
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(falloff))
+ .["falloff"] = falloff
+ if(!isnull(repeat))
+ .["repeat"] = repeat
+ if(!isnull(flags))
+ .["flags"] = flags
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+
+/proc/wave_filter(x, y, size, offset, flags)
+ . = list("type" = "wave")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(offset))
+ .["offset"] = offset
+ if(!isnull(flags))
+ .["flags"] = flags
+
+/proc/apply_wibbly_filters(atom/in_atom, length)
+ for(var/i in 1 to 7)
+ //This is a very baffling and strange way of doing this but I am just preserving old functionality
+ var/X
+ var/Y
+ var/rsq
+ do
+ X = 60*rand() - 30
+ Y = 60*rand() - 30
+ rsq = X*X + Y*Y
+ while(rsq<100 || rsq>900) // Yeah let's just loop infinitely due to bad luck what's the worst that could happen?
+ var/random_roll = rand()
+ in_atom.add_filter("wibbly-[i]", 5, wave_filter(x = X, y = Y, size = rand() * 2.5 + 0.5, offset = random_roll))
+ var/filter = in_atom.get_filter("wibbly-[i]")
+ animate(filter, offset = random_roll, time = 0, loop = -1, flags = ANIMATION_PARALLEL)
+ animate(offset = random_roll - 1, time = rand() * 20 + 10)
+
+/proc/remove_wibbly_filters(atom/in_atom)
+ var/filter
+ for(var/i in 1 to 7)
+ filter = in_atom.get_filter("wibbly-[i]")
+ animate(filter)
+ in_atom.remove_filter("wibbly-[i]")
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index a860a8cd3f..39c1b9103f 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -169,6 +169,28 @@
file_data["wanted"] = list("author" = "[GLOB.news_network.wanted_issue.scannedUser]", "criminal" = "[GLOB.news_network.wanted_issue.criminal]", "description" = "[GLOB.news_network.wanted_issue.body]", "photo file" = "[GLOB.news_network.wanted_issue.photo_file]")
WRITE_FILE(json_file, json_encode(file_data))
+///Handles random hardcore point rewarding if it applies.
+/datum/controller/subsystem/ticker/proc/HandleRandomHardcoreScore(client/player_client)
+ if(!ishuman(player_client.mob))
+ return FALSE
+ var/mob/living/carbon/human/human_mob = player_client.mob
+ if(!human_mob.hardcore_survival_score) ///no score no glory
+ return FALSE
+
+ if(human_mob.mind && (human_mob.mind.special_role || length(human_mob.mind.antag_datums) > 0))
+ var/didthegamerwin = TRUE
+ for(var/a in human_mob.mind.antag_datums)
+ var/datum/antagonist/antag_datum = a
+ for(var/i in antag_datum.objectives)
+ var/datum/objective/objective_datum = i
+ if(!objective_datum.check_completion())
+ didthegamerwin = FALSE
+ if(!didthegamerwin)
+ return FALSE
+ player_client.give_award(/datum/award/score/hardcore_random, human_mob, round(human_mob.hardcore_survival_score))
+ else if(human_mob.onCentCom())
+ player_client.give_award(/datum/award/score/hardcore_random, human_mob, round(human_mob.hardcore_survival_score))
+
/datum/controller/subsystem/ticker/proc/declare_completion()
set waitfor = FALSE
@@ -186,6 +208,19 @@
C.RollCredits()
C.playtitlemusic(40)
CONFIG_SET(flag/suicide_allowed,TRUE) // EORG suicides allowed
+
+ var/speed_round = FALSE
+ if(world.time - SSticker.round_start_time <= 300 SECONDS)
+ speed_round = TRUE
+
+ for(var/client/C in GLOB.clients)
+ if(!C.credits)
+ C.RollCredits()
+ C.playtitlemusic(40)
+ if(speed_round)
+ C.give_award(/datum/award/achievement/misc/speed_round, C.mob)
+ HandleRandomHardcoreScore(C)
+
var/popcount = gather_roundend_feedback()
display_report(popcount)
diff --git a/code/_globalvars/lists/medals.dm b/code/_globalvars/lists/achievements.dm
old mode 100755
new mode 100644
similarity index 100%
rename from code/_globalvars/lists/medals.dm
rename to code/_globalvars/lists/achievements.dm
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 7a614da07b..f63e594675 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -97,6 +97,9 @@
M.lastattacker = user.real_name
M.lastattackerckey = user.ckey
+ if(force && M == user && user.client)
+ user.client.give_award(/datum/award/achievement/misc/selfouch, user)
+
user.do_attack_animation(M)
M.attacked_by(src, user, attackchain_flags, damage_multiplier)
diff --git a/code/controllers/subsystem/achievements.dm b/code/controllers/subsystem/achievements.dm
new file mode 100644
index 0000000000..554481b93a
--- /dev/null
+++ b/code/controllers/subsystem/achievements.dm
@@ -0,0 +1,74 @@
+SUBSYSTEM_DEF(achievements)
+ name = "Achievements"
+ flags = SS_NO_FIRE
+ init_order = INIT_ORDER_ACHIEVEMENTS
+ var/achievements_enabled = FALSE
+
+ ///List of achievements
+ var/list/datum/award/achievement/achievements = list()
+ ///List of scores
+ var/list/datum/award/score/scores = list()
+ ///List of all awards
+ var/list/datum/award/awards = list()
+
+/datum/controller/subsystem/achievements/Initialize(timeofday)
+ if(!SSdbcore.Connect())
+ return ..()
+ achievements_enabled = TRUE
+
+ for(var/T in subtypesof(/datum/award/achievement))
+ var/instance = new T
+ achievements[T] = instance
+ awards[T] = instance
+
+ for(var/T in subtypesof(/datum/award/score))
+ var/instance = new T
+ scores[T] = instance
+ awards[T] = instance
+
+ update_metadata()
+
+ for(var/i in GLOB.clients)
+ var/client/C = i
+ if(!C.player_details.achievements.initialized)
+ C.player_details.achievements.InitializeData()
+
+ return ..()
+
+/datum/controller/subsystem/achievements/Shutdown()
+ save_achievements_to_db()
+
+/datum/controller/subsystem/achievements/proc/save_achievements_to_db()
+ var/list/cheevos_to_save = list()
+ for(var/ckey in GLOB.player_details)
+ var/datum/player_details/PD = GLOB.player_details[ckey]
+ if(!PD || !PD.achievements)
+ continue
+ cheevos_to_save += PD.achievements.get_changed_data()
+ if(!length(cheevos_to_save))
+ return
+ SSdbcore.MassInsert(format_table_name("achievements"),cheevos_to_save,duplicate_key = TRUE)
+
+//Update the metadata if any are behind
+/datum/controller/subsystem/achievements/proc/update_metadata()
+ var/list/current_metadata = list()
+ //select metadata here
+ var/datum/DBQuery/Q = SSdbcore.NewQuery("SELECT achievement_key,achievement_version FROM [format_table_name("achievement_metadata")]")
+ if(!Q.Execute(async = TRUE))
+ qdel(Q)
+ return
+ else
+ while(Q.NextRow())
+ current_metadata[Q.item[1]] = text2num(Q.item[2])
+ qdel(Q)
+
+ var/list/to_update = list()
+ for(var/T in awards)
+ var/datum/award/A = awards[T]
+ if(!A.database_id)
+ continue
+ if(!current_metadata[A.database_id] || current_metadata[A.database_id] < A.achievement_version)
+ to_update += list(A.get_metadata_row())
+
+ if(to_update.len)
+ SSdbcore.MassInsert(format_table_name("achievement_metadata"),to_update,duplicate_key = TRUE)
diff --git a/code/controllers/subsystem/medals.dm b/code/controllers/subsystem/medals.dm
deleted file mode 100644
index 36be23973c..0000000000
--- a/code/controllers/subsystem/medals.dm
+++ /dev/null
@@ -1,87 +0,0 @@
-SUBSYSTEM_DEF(medals)
- name = "Medals"
- flags = SS_NO_FIRE
- var/hub_enabled = FALSE
-
-/datum/controller/subsystem/medals/Initialize(timeofday)
- if(CONFIG_GET(string/medal_hub_address) && CONFIG_GET(string/medal_hub_password))
- hub_enabled = TRUE
- return ..()
-
-/datum/controller/subsystem/medals/proc/UnlockMedal(medal, client/player)
- set waitfor = FALSE
- if(!medal || !hub_enabled)
- return
- if(isnull(world.SetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
- hub_enabled = FALSE
- log_game("MEDAL ERROR: Could not contact hub to award medal:[medal] player:[player.key]")
- message_admins("Error! Failed to contact hub to award [medal] medal to [player.key]!")
- return
- to_chat(player, "Achievement unlocked: [medal]!")
-
-
-/datum/controller/subsystem/medals/proc/SetScore(score, client/player, increment, force)
- set waitfor = FALSE
- if(!score || !hub_enabled)
- return
-
- var/list/oldscore = GetScore(score, player, TRUE)
- if(increment)
- if(!oldscore[score])
- oldscore[score] = 1
- else
- oldscore[score] = (text2num(oldscore[score]) + 1)
- else
- oldscore[score] = force
-
- var/newscoreparam = list2params(oldscore)
-
- if(isnull(world.SetScores(player.ckey, newscoreparam, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
- hub_enabled = FALSE
- log_game("SCORE ERROR: Could not contact hub to set score. Score:[score] player:[player.key]")
- message_admins("Error! Failed to contact hub to set [score] score for [player.key]!")
-
-/datum/controller/subsystem/medals/proc/GetScore(score, client/player, returnlist)
- if(!score || !hub_enabled)
- return
-
- var/scoreget = world.GetScores(player.ckey, score, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
- if(isnull(scoreget))
- hub_enabled = FALSE
- log_game("SCORE ERROR: Could not contact hub to get score. Score:[score] player:[player.key]")
- message_admins("Error! Failed to contact hub to get score: [score] for [player.key]!")
- return
- . = params2list(scoreget)
- if(!returnlist)
- return .[score]
-
-/datum/controller/subsystem/medals/proc/CheckMedal(medal, client/player)
- if(!medal || !hub_enabled)
- return
-
- if(isnull(world.GetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
- hub_enabled = FALSE
- log_game("MEDAL ERROR: Could not contact hub to get medal:[medal] player: [player.key]")
- message_admins("Error! Failed to contact hub to get [medal] medal for [player.key]!")
- return
- to_chat(player, "[medal] is unlocked")
-
-/datum/controller/subsystem/medals/proc/LockMedal(medal, client/player)
- if(!player || !medal || !hub_enabled)
- return
- var/result = world.ClearMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
- switch(result)
- if(null)
- hub_enabled = FALSE
- log_game("MEDAL ERROR: Could not contact hub to clear medal:[medal] player:[player.key]")
- message_admins("Error! Failed to contact hub to clear [medal] medal for [player.key]!")
- if(TRUE)
- message_admins("Medal: [medal] removed for [player.key]")
- if(FALSE)
- message_admins("Medal: [medal] was not found for [player.key]. Unable to clear.")
-
-
-/datum/controller/subsystem/medals/proc/ClearScore(client/player)
- if(isnull(world.SetScores(player.ckey, "", CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
- log_game("MEDAL ERROR: Could not contact hub to clear scores for [player.key]!")
- message_admins("Error! Failed to contact hub to clear scores for [player.key]!")
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index b74f1d46d3..66aa12ac1a 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -65,6 +65,8 @@ SUBSYSTEM_DEF(shuttle)
var/datum/turf_reservation/preview_reservation
+ var/shuttle_loading
+
/datum/controller/subsystem/shuttle/Initialize(timeofday)
ordernum = rand(1, 9000)
@@ -664,7 +666,7 @@ SUBSYSTEM_DEF(shuttle)
emergencyNoRecall = TRUE
endvote_passed = TRUE
-/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port)
+/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port, replace = FALSE)
// Check for an existing preview
if(preview_shuttle && (loading_template != preview_template))
preview_shuttle.jumpToNullSpace()
@@ -673,8 +675,8 @@ SUBSYSTEM_DEF(shuttle)
QDEL_NULL(preview_reservation)
if(!preview_shuttle)
- if(load_template(loading_template))
- preview_shuttle.linkup(loading_template, destination_port)
+ load_template(loading_template)
+ preview_shuttle.linkup(loading_template, destination_port)
preview_template = loading_template
// get the existing shuttle information, if any
@@ -684,7 +686,7 @@ SUBSYSTEM_DEF(shuttle)
if(istype(destination_port))
D = destination_port
- else if(existing_shuttle)
+ else if(existing_shuttle && replace)
timer = existing_shuttle.timer
mode = existing_shuttle.mode
D = existing_shuttle.get_docked()
@@ -703,11 +705,12 @@ SUBSYSTEM_DEF(shuttle)
WARNING("Template shuttle [preview_shuttle] cannot dock at [D] ([result]).")
return
- if(existing_shuttle)
+ if(existing_shuttle && replace)
existing_shuttle.jumpToNullSpace()
var/list/force_memory = preview_shuttle.movement_force
preview_shuttle.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0)
+ preview_shuttle.mode = SHUTTLE_PREARRIVAL//No idle shuttle moving. Transit dock get removed if shuttle moves too long.
preview_shuttle.initiate_docking(D)
preview_shuttle.movement_force = force_memory
@@ -718,7 +721,7 @@ SUBSYSTEM_DEF(shuttle)
preview_shuttle.timer = timer
preview_shuttle.mode = mode
- preview_shuttle.register()
+ preview_shuttle.register(replace)
// TODO indicate to the user that success happened, rather than just
// blanking the modification tab
@@ -848,7 +851,8 @@ SUBSYSTEM_DEF(shuttle)
return data
/datum/controller/subsystem/shuttle/ui_act(action, params)
- if(..())
+ . = ..()
+ if(.)
return
var/mob/user = usr
@@ -891,22 +895,10 @@ SUBSYSTEM_DEF(shuttle)
SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[M.name]")
break
- if("preview")
- if(S)
- . = TRUE
- unload_preview()
- load_template(S)
- if(preview_shuttle)
- preview_template = S
- user.forceMove(get_turf(preview_shuttle))
if("load")
- if(existing_shuttle == backup_shuttle)
- // TODO make the load button disabled
- WARNING("The shuttle that the selected shuttle will replace \
- is the backup shuttle. Backup shuttle is required to be \
- intact for round sanity.")
- else if(S)
+ if(S && !shuttle_loading)
. = TRUE
+ shuttle_loading = TRUE
// If successful, returns the mobile docking port
var/obj/docking_port/mobile/mdp = action_load(S)
if(mdp)
@@ -914,3 +906,38 @@ SUBSYSTEM_DEF(shuttle)
message_admins("[key_name_admin(usr)] loaded [mdp] with the shuttle manipulator.")
log_admin("[key_name(usr)] loaded [mdp] with the shuttle manipulator.")
SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[mdp.name]")
+ shuttle_loading = FALSE
+
+ if("preview")
+ //if(preview_shuttle && (loading_template != preview_template))
+ if(S && !shuttle_loading)
+ . = TRUE
+ shuttle_loading = TRUE
+ unload_preview()
+ load_template(S)
+ if(preview_shuttle)
+ preview_template = S
+ user.forceMove(get_turf(preview_shuttle))
+ shuttle_loading = FALSE
+
+ if("replace")
+ if(existing_shuttle == backup_shuttle)
+ // TODO make the load button disabled
+ WARNING("The shuttle that the selected shuttle will replace \
+ is the backup shuttle. Backup shuttle is required to be \
+ intact for round sanity.")
+ else if(S && !shuttle_loading)
+ . = TRUE
+ shuttle_loading = TRUE
+ // If successful, returns the mobile docking port
+ var/obj/docking_port/mobile/mdp = action_load(S, replace = TRUE)
+ if(mdp)
+ user.forceMove(get_turf(mdp))
+ message_admins("[key_name_admin(usr)] load/replaced [mdp] with the shuttle manipulator.")
+ log_admin("[key_name(usr)] load/replaced [mdp] with the shuttle manipulator.")
+ SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[mdp.name]")
+ shuttle_loading = FALSE
+ if(emergency == mdp) //you just changed the emergency shuttle, there are events in game + captains that can change your snowflake choice.
+ var/set_purchase = alert(usr, "Do you want to also disable shuttle purchases/random events that would change the shuttle?", "Butthurt Admin Prevention", "Yes, disable purchases/events", "No, I want to possibly get owned")
+ if(set_purchase == "Yes, disable purchases/events")
+ SSshuttle.shuttle_purchased = SHUTTLEPURCHASE_FORCED
diff --git a/code/datums/achievements/_achievement_data.dm b/code/datums/achievements/_achievement_data.dm
new file mode 100644
index 0000000000..2c904b8b9c
--- /dev/null
+++ b/code/datums/achievements/_achievement_data.dm
@@ -0,0 +1,148 @@
+///Datum that handles
+/datum/achievement_data
+ ///Ckey of this achievement data's owner
+ var/owner_ckey
+ ///Up to date list of all achievements and their info.
+ var/data = list()
+ ///Original status of achievement.
+ var/original_cached_data = list()
+ ///Have we done our set-up yet?
+ var/initialized = FALSE
+
+/datum/achievement_data/New(ckey)
+ owner_ckey = ckey
+ if(SSachievements.initialized && !initialized)
+ InitializeData()
+
+/datum/achievement_data/proc/InitializeData()
+ initialized = TRUE
+ load_all_achievements() //So we know which achievements we have unlocked so far.
+
+///Gets list of changed rows in MassInsert format
+/datum/achievement_data/proc/get_changed_data()
+ . = list()
+ for(var/T in data)
+ var/datum/award/A = SSachievements.awards[T]
+ if(data[T] != original_cached_data[T])//If our data from before is not the same as now, save it to db.
+ var/deets = A.get_changed_rows(owner_ckey,data[T])
+ if(deets)
+ . += list(deets)
+
+/datum/achievement_data/proc/load_all_achievements()
+ set waitfor = FALSE
+
+ var/list/kv = list()
+ var/datum/DBQuery/Query = SSdbcore.NewQuery(
+ "SELECT achievement_key,value FROM [format_table_name("achievements")] WHERE ckey = [owner_ckey]",
+ )
+ if(!Query.Execute())
+ qdel(Query)
+ return
+ while(Query.NextRow())
+ var/key = Query.item[1]
+ var/value = text2num(Query.item[2])
+ kv[key] = value
+ qdel(Query)
+
+ for(var/T in subtypesof(/datum/award))
+ var/datum/award/A = SSachievements.awards[T]
+ if(!A || !A.name) //Skip abstract achievements types
+ continue
+ if(!data[T])
+ data[T] = A.parse_value(kv[A.database_id])
+ original_cached_data[T] = data[T]
+
+///Updates local cache with db data for the given achievement type if it wasn't loaded yet.
+/datum/achievement_data/proc/get_data(achievement_type)
+ var/datum/award/A = SSachievements.awards[achievement_type]
+ if(!A.name)
+ return FALSE
+ if(!data[achievement_type])
+ data[achievement_type] = A.load(owner_ckey)
+ original_cached_data[achievement_type] = data[achievement_type]
+
+///Unlocks an achievement of a specific type. achievement type is a typepath to the award, user is the mob getting the award, and value is an optional value to be used for defining a score to add to the leaderboard
+/datum/achievement_data/proc/unlock(achievement_type, mob/user, value = 1)
+ set waitfor = FALSE
+
+ if(!SSachievements.achievements_enabled)
+ return
+ var/datum/award/A = SSachievements.awards[achievement_type]
+ get_data(achievement_type) //Get the current status first if necessary
+ if(istype(A, /datum/award/achievement))
+ if(data[achievement_type]) //You already unlocked it so don't bother running the unlock proc
+ return
+ data[achievement_type] = TRUE
+ A.on_unlock(user) //Only on default achievement, as scores keep going up.
+ else if(istype(A, /datum/award/score))
+ data[achievement_type] += value
+
+///Getter for the status/score of an achievement
+/datum/achievement_data/proc/get_achievement_status(achievement_type)
+ return data[achievement_type]
+
+///Resets an achievement to default values.
+/datum/achievement_data/proc/reset(achievement_type)
+ if(!SSachievements.achievements_enabled)
+ return
+ var/datum/award/A = SSachievements.awards[achievement_type]
+ get_data(achievement_type)
+ if(istype(A, /datum/award/achievement))
+ data[achievement_type] = FALSE
+ else if(istype(A, /datum/award/score))
+ data[achievement_type] = 0
+
+/datum/achievement_data/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/simple/achievements),
+ )
+
+/datum/achievement_data/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/achievement_data/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Achievements")
+ ui.open()
+
+/datum/achievement_data/ui_data(mob/user)
+ var/ret_data = list() // screw standards (qustinnus you must rename src.data ok)
+ ret_data["categories"] = list("Bosses", "Misc", "Mafia", "Scores")
+ ret_data["achievements"] = list()
+ ret_data["user_key"] = user.ckey
+
+ var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements)
+ //This should be split into static data later
+ for(var/achievement_type in SSachievements.awards)
+ if(!SSachievements.awards[achievement_type].name) //No name? we a subtype.
+ continue
+ if(isnull(data[achievement_type])) //We're still loading
+ continue
+ var/list/this = list(
+ "name" = SSachievements.awards[achievement_type].name,
+ "desc" = SSachievements.awards[achievement_type].desc,
+ "category" = SSachievements.awards[achievement_type].category,
+ "icon_class" = assets.icon_class_name(SSachievements.awards[achievement_type].icon),
+ "value" = data[achievement_type],
+ "score" = ispath(achievement_type,/datum/award/score)
+ )
+ ret_data["achievements"] += list(this)
+
+ return ret_data
+
+/datum/achievement_data/ui_static_data(mob/user)
+ . = ..()
+ .["highscore"] = list()
+ for(var/score in SSachievements.scores)
+ var/datum/award/score/S = SSachievements.scores[score]
+ if(!S.name || !S.track_high_scores || !S.high_scores.len)
+ continue
+ .["highscore"] += list(list("name" = S.name,"scores" = S.high_scores))
+
+/client/verb/checkachievements()
+ set category = "OOC"
+ set name = "Check achievements"
+ set desc = "See all of your achievements!"
+
+ player_details.achievements.ui_interact(usr)
diff --git a/code/datums/achievements/_awards.dm b/code/datums/achievements/_awards.dm
new file mode 100644
index 0000000000..63bc7f6ce7
--- /dev/null
+++ b/code/datums/achievements/_awards.dm
@@ -0,0 +1,115 @@
+/datum/award
+ ///Name of the achievement, If null it won't show up in the achievement browser. (Handy for inheritance trees)
+ var/name
+ var/desc = "You did it."
+ ///Found in UI_Icons/Achievements
+ var/icon = "default"
+ var/category = "Normal"
+
+ ///What ID do we use in db, limited to 32 characters
+ var/database_id
+ //Bump this up if you're changing outdated table identifier and/or achievement type
+ var/achievement_version = 2
+
+ //Value returned on db connection failure, in case we want to differ 0 and nonexistent later on
+ var/default_value = FALSE
+
+///This proc loads the achievement data from the hub.
+/datum/award/proc/load(key)
+ if(!SSdbcore.Connect())
+ return default_value
+ if(!key || !database_id || !name)
+ return default_value
+ var/raw_value = get_raw_value(key)
+ return parse_value(raw_value)
+
+///This saves the changed data to the hub.
+/datum/award/proc/get_changed_rows(key, value)
+ if(!database_id || !key || !name)
+ return
+ return list(
+ "ckey" = key,
+ "achievement_key" = database_id,
+ "value" = value,
+ )
+
+/datum/award/proc/get_metadata_row()
+ return list(
+ "achievement_key" = database_id,
+ "achievement_version" = achievement_version,
+ "achievement_type" = "award",
+ "achievement_name" = name,
+ "achievement_description" = desc,
+ )
+
+///Get raw numerical achievement value from the database
+/datum/award/proc/get_raw_value(key)
+ var/datum/DBQuery/Q = SSdbcore.NewQuery(
+ "SELECT value FROM [format_table_name("achievements")] WHERE ckey = [key] AND achievement_key = [database_id]"
+ )
+ if(!Q.Execute(async = TRUE))
+ qdel(Q)
+ return 0
+ var/result = 0
+ if(Q.NextRow())
+ result = text2num(Q.item[1])
+ qdel(Q)
+ return result
+
+//Should return sanitized value for achievement cache
+/datum/award/proc/parse_value(raw_value)
+ return default_value
+
+///Can be overriden for achievement specific events
+/datum/award/proc/on_unlock(mob/user)
+ return
+
+///Achievements are one-off awards for usually doing cool things.
+/datum/award/achievement
+ desc = "Achievement for epic people"
+
+/datum/award/achievement/get_metadata_row()
+ . = ..()
+ .["achievement_type"] = "achievement"
+
+/datum/award/achievement/parse_value(raw_value)
+ return raw_value > 0
+
+/datum/award/achievement/on_unlock(mob/user)
+ . = ..()
+ to_chat(user, "Achievement unlocked: [name]!")
+
+///Scores are for leaderboarded things, such as killcount of a specific boss
+/datum/award/score
+ desc = "you did it sooo many times."
+ category = "Scores"
+ default_value = 0
+
+ var/track_high_scores = TRUE
+ var/list/high_scores = list()
+
+/datum/award/score/New()
+ . = ..()
+ if(track_high_scores)
+ LoadHighScores()
+
+/datum/award/score/get_metadata_row()
+ . = ..()
+ .["achievement_type"] = "score"
+
+/datum/award/score/proc/LoadHighScores()
+ var/datum/DBQuery/Q = SSdbcore.NewQuery(
+ "SELECT ckey,value FROM [format_table_name("achievements")] WHERE achievement_key = [database_id] ORDER BY value DESC LIMIT 50"
+ )
+ if(!Q.Execute(async = TRUE))
+ qdel(Q)
+ return
+ else
+ while(Q.NextRow())
+ var/key = Q.item[1]
+ var/score = text2num(Q.item[2])
+ high_scores[key] = score
+ qdel(Q)
+
+/datum/award/score/parse_value(raw_value)
+ return isnum(raw_value) ? raw_value : 0
diff --git a/code/datums/achievements/boss_achievements.dm b/code/datums/achievements/boss_achievements.dm
new file mode 100644
index 0000000000..6625bbb439
--- /dev/null
+++ b/code/datums/achievements/boss_achievements.dm
@@ -0,0 +1,130 @@
+/datum/award/achievement/boss
+ category = "Bosses"
+ icon = "baseboss"
+
+/datum/award/achievement/boss/tendril_exterminator
+ name = "Tendril Exterminator"
+ desc = "Watch your step"
+ database_id = BOSS_MEDAL_TENDRIL
+ icon = "tendril"
+
+/datum/award/achievement/boss/boss_killer
+ name = "Boss Killer"
+ desc = "You've come a long ways from asking how to switch hands."
+ database_id = "Boss Killer"
+ icon = "firstboss"
+
+/datum/award/achievement/boss/blood_miner_kill
+ name = "Blood-Drunk Miner Killer"
+ desc = "I guess he couldn't handle his drink that well."
+ database_id = BOSS_MEDAL_MINER
+ icon = "miner"
+
+/datum/award/achievement/boss/demonic_miner_kill
+ name = "Demonic-Frost Miner Killer"
+ desc = "Definitely harder than the Blood-Drunk Miner."
+ database_id = BOSS_MEDAL_FROSTMINER
+
+/datum/award/achievement/boss/bubblegum_kill
+ name = "Bubblegum Killer"
+ desc = "I guess he wasn't made of candy after all"
+ database_id = BOSS_MEDAL_BUBBLEGUM
+ icon = "bbgum"
+
+/datum/award/achievement/boss/colossus_kill
+ name = "Colossus Killer"
+ desc = "The bigger they are... the better the loot"
+ database_id = BOSS_MEDAL_COLOSSUS
+ icon = "colossus"
+
+/datum/award/achievement/boss/drake_kill
+ name = "Drake Killer"
+ desc = "Now I can wear Rune Platebodies!"
+ database_id = BOSS_MEDAL_DRAKE
+ icon = "drake"
+
+/datum/award/achievement/boss/hierophant_kill
+ name = "Hierophant Killer"
+ desc = "Hierophant, but not triumphant."
+ database_id = BOSS_MEDAL_HIEROPHANT
+ icon = "hierophant"
+
+/datum/award/achievement/boss/legion_kill
+ name = "Legion Killer"
+ desc = "We were many..now we are none."
+ database_id = BOSS_MEDAL_LEGION
+ icon = "legion"
+
+/datum/award/achievement/boss/swarmer_beacon_kill
+ name = "Swarm Beacon Killer"
+ desc = "GET THEM OFF OF ME!"
+ database_id = BOSS_MEDAL_SWARMERS
+ icon = "swarmer"
+
+/datum/award/achievement/boss/wendigo_kill
+ name = "Wendigo Killer"
+ desc = "You've now ruined years of mythical storytelling."
+ database_id = BOSS_MEDAL_WENDIGO
+
+/datum/award/achievement/boss/blood_miner_crusher
+ name = "Blood-Drunk Miner Crusher"
+ desc = "I guess he couldn't handle his drink that well."
+ database_id = BOSS_MEDAL_MINER_CRUSHER
+ icon = "miner"
+
+/datum/award/achievement/boss/demonic_miner_crusher
+ name = "Demonic-Frost Miner Crusher"
+ desc = "Definitely harder than the Blood-Drunk Miner."
+ database_id = BOSS_MEDAL_FROSTMINER_CRUSHER
+
+/datum/award/achievement/boss/bubblegum_crusher
+ name = "Bubblegum Crusher"
+ desc = "I guess he wasn't made of candy after all"
+ database_id = BOSS_MEDAL_BUBBLEGUM_CRUSHER
+ icon = "bbgum"
+
+/datum/award/achievement/boss/colossus_crusher
+ name = "Colossus Crusher"
+ desc = "The bigger they are... the better the loot"
+ database_id = BOSS_MEDAL_COLOSSUS_CRUSHER
+ icon = "colossus"
+
+/datum/award/achievement/boss/drake_crusher
+ name = "Drake Crusher"
+ desc = "Now I can wear Rune Platebodies!"
+ database_id = BOSS_MEDAL_DRAKE_CRUSHER
+ icon = "drake"
+
+/datum/award/achievement/boss/hierophant_crusher
+ name = "Hierophant Crusher"
+ desc = "Hierophant, but not triumphant."
+ database_id = BOSS_MEDAL_HIEROPHANT_CRUSHER
+ icon = "hierophant"
+
+/datum/award/achievement/boss/legion_crusher
+ name = "Legion Crusher"
+ desc = "We were many... now we are none."
+ database_id = BOSS_MEDAL_LEGION_CRUSHER
+
+/datum/award/achievement/boss/swarmer_beacon_crusher
+ name = "Swarm Beacon Crusher"
+ desc = "GET THEM OFF OF ME!"
+ database_id = BOSS_MEDAL_SWARMERS_CRUSHER
+
+/datum/award/achievement/boss/wendigo_crusher
+ name = "Wendigo Crusher"
+ desc = "You've now ruined years of mythical storytelling."
+ database_id = BOSS_MEDAL_WENDIGO_CRUSHER
+
+//should be removed soon
+/datum/award/achievement/boss/king_goat_kill
+ name = "King Goat Killer"
+ desc = "The king is dead, long live the king!"
+ database_id = BOSS_MEDAL_KINGGOAT
+ icon = "goatboss"
+
+/datum/award/achievement/boss/king_goat_crusher
+ name = "King Goat Crusher"
+ desc = "The king is dead, long live the king!"
+ database_id = BOSS_MEDAL_KINGGOAT_CRUSHER
+ icon = "goatboss"
diff --git a/code/datums/achievements/boss_scores.dm b/code/datums/achievements/boss_scores.dm
new file mode 100644
index 0000000000..fdb9efa7c7
--- /dev/null
+++ b/code/datums/achievements/boss_scores.dm
@@ -0,0 +1,54 @@
+/datum/award/score/tendril_score
+ name = "Tendril Score"
+ desc = "Watch your step"
+ database_id = TENDRIL_CLEAR_SCORE
+
+/datum/award/score/boss_score
+ name = "Bosses Killed"
+ desc = "You've killed HOW many?"
+ database_id = BOSS_SCORE
+
+/datum/award/score/blood_miner_score
+ name = "Blood-Drunk Miners Killed"
+ desc = "You've killed HOW many?"
+ database_id = MINER_SCORE
+
+/datum/award/score/demonic_miner_score
+ name = "Demonic-Frost Miners Killed"
+ desc = "You've killed HOW many?"
+ database_id = FROST_MINER_SCORE
+
+/datum/award/score/bubblegum_score
+ name = "Bubblegums Killed"
+ desc = "You've killed HOW many?"
+ database_id = BUBBLEGUM_SCORE
+
+/datum/award/score/colussus_score
+ name = "Colossus Killed"
+ desc = "You've killed HOW many?"
+ database_id = COLOSSUS_SCORE
+
+/datum/award/score/drake_score
+ name = "Drakes Killed"
+ desc = "You've killed HOW many?"
+ database_id = DRAKE_SCORE
+
+/datum/award/score/hierophant_score
+ name = "Hierophants Killed"
+ desc = "You've killed HOW many?"
+ database_id = HIEROPHANT_SCORE
+
+/datum/award/score/legion_score
+ name = "Legions Killed"
+ desc = "You've killed HOW many?"
+ database_id = LEGION_SCORE
+
+/datum/award/score/swarmer_beacon_score
+ name = "Swarmer Beacons Killed"
+ desc = "You've killed HOW many?"
+ database_id = SWARMER_BEACON_SCORE
+
+/datum/award/score/wendigo_score
+ name = "Wendigos Killed"
+ desc = "You've killed HOW many?"
+ database_id = WENDIGO_SCORE
diff --git a/code/datums/achievements/hardcore_random.dm b/code/datums/achievements/hardcore_random.dm
new file mode 100644
index 0000000000..c5e2692552
--- /dev/null
+++ b/code/datums/achievements/hardcore_random.dm
@@ -0,0 +1,4 @@
+/datum/award/score/hardcore_random
+ name = "Hardcore random points"
+ desc = "Well, I might be a blind, deaf, crippled guy, but hey, at least I'm alive."
+ database_id = HARDCORE_RANDOM_SCORE
diff --git a/code/datums/achievements/mafia_achievements.dm b/code/datums/achievements/mafia_achievements.dm
new file mode 100644
index 0000000000..414917413a
--- /dev/null
+++ b/code/datums/achievements/mafia_achievements.dm
@@ -0,0 +1,97 @@
+/datum/award/achievement/mafia
+ category = "Mafia"
+ icon = "basemafia"
+
+///ALL THE ACHIEVEMENTS FOR WINNING A ROUND AS A ROLE///
+
+/datum/award/achievement/mafia/assistant
+ name = "Assistant Victory"
+ desc = "If you got killed instead of someone more important, you just flexed the true strength of your \"\"\"\"role\"\"\"\"."
+ database_id = MAFIA_MEDAL_ASSISTANT
+ icon = "assistant"
+
+/datum/award/achievement/mafia/detective
+ name = "Detective Victory"
+ desc = "If you did this with a Medical Doctor in the game, i'm not really that impressed."
+ database_id = MAFIA_MEDAL_DETECTIVE
+ icon = "detective"
+
+/datum/award/achievement/mafia/psychologist
+ name = "Psychologist Victory"
+ desc = "You learned how to not reveal someone random night one! Or... maybe you're just a lucky bastard."
+ database_id = MAFIA_MEDAL_PSYCHOLOGIST
+ icon = "psychologist"
+
+/datum/award/achievement/mafia/chaplain
+ name = "Chaplain Victory"
+ desc = "Useless... until the one night the thoughtfeeder confidently claims themselves as detective. Mafia's true bullshit detector."
+ database_id = MAFIA_MEDAL_CHAPLAIN
+ icon = "chaplain"
+
+/datum/award/achievement/mafia/md
+ name = "Medical Doctor Victory"
+ desc = "Congratulations on learning how to not talk!"
+ database_id = MAFIA_MEDAL_MD
+ icon = "md"
+
+/datum/award/achievement/mafia/lawyer
+ name = "Lawyer Victory"
+ desc = "Oh don't mind me, i'm just the worst rol- Oops, I just instantly ended the game."
+ database_id = MAFIA_MEDAL_LAWYER
+ icon = "lawyer"
+
+/datum/award/achievement/mafia/hop
+ name = "Head of Personnel Victory"
+ desc = "King of Assistants, waster of a single mafia's night, thrower of games."
+ database_id = MAFIA_MEDAL_HOP
+ icon = "hop"
+
+/datum/award/achievement/mafia/changeling
+ name = "Changeling Victory"
+ desc = "I think the changelings are metacomming."
+ database_id = MAFIA_MEDAL_CHANGELING
+ icon = "changeling"
+
+/datum/award/achievement/mafia/thoughtfeeder
+ name = "Thoughtfeeder Victory"
+ desc = "Clown's best friend. And Obsessed. And fugitive? Whose side are you on?!"
+ database_id = MAFIA_MEDAL_THOUGHTFEEDER
+ icon = "thoughtfeeder"
+
+/datum/award/achievement/mafia/traitor
+ name = "Traitor Victory"
+ desc = "Guys, we still have two more changelings to ki-!! TRAITOR VICTORY !!"
+ database_id = MAFIA_MEDAL_TRAITOR
+ icon = "traitor"
+
+/datum/award/achievement/mafia/nightmare
+ name = "Nightmare Victory"
+ desc = "DID YOUR LIGHT FLICKER?!"
+ database_id = MAFIA_MEDAL_NIGHTMARE
+ icon = "nightmare"
+
+/datum/award/achievement/mafia/fugitive
+ name = "Fugitive Victory"
+ desc = "I'm just the description on an achievement, but if you end up having to choose between town and changelings, go changelings."
+ database_id = MAFIA_MEDAL_FUGITIVE
+ icon = "fugitive"
+
+/datum/award/achievement/mafia/obsessed
+ name = "Obsessed Victory"
+ desc = "You got your target lynched, so instead of being spiteful and annoying, you're just smug and annoying."
+ database_id = MAFIA_MEDAL_OBSESSED
+ icon = "obsessed"
+
+/datum/award/achievement/mafia/clown
+ name = "Clown Victory"
+ desc = "Did you know this works on traitors, despite their immunity? If you hit the jackpot and manage to kill one, they'll salt into the next dimension. Clown tips!"
+ database_id = MAFIA_MEDAL_CLOWN
+ icon = "clown"
+
+///ALL THE ACHIEVEMENTS FOR MISC MAFIA ODDITIES///
+
+/datum/award/achievement/mafia/universally_hated
+ name = "Universally Hated"
+ desc = "Managed to get more than 12 votes when put up on trial, jesus christ."
+ database_id = MAFIA_MEDAL_HATED
+ icon = "hated"
diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm
new file mode 100644
index 0000000000..a99a25ec77
--- /dev/null
+++ b/code/datums/achievements/misc_achievements.dm
@@ -0,0 +1,155 @@
+/datum/award/achievement/misc
+ category = "Misc"
+ icon = "basemisc"
+
+/datum/award/achievement/misc/meteor_examine
+ name = "Your Life Before Your Eyes"
+ desc = "Take a close look at hurtling space debris"
+ database_id = MEDAL_METEOR
+ icon = "meteors"
+
+/datum/award/achievement/misc/pulse
+ name = "Jackpot"
+ desc = "Win a pulse rifle from an arcade machine"
+ database_id = MEDAL_PULSE
+ icon = "jackpot"
+
+/datum/award/achievement/misc/time_waste
+ name = "Time waster"
+ desc = "Speak no evil, hear no evil, see just errors"
+ database_id = MEDAL_TIMEWASTE
+ icon = "timewaste"
+
+/datum/award/achievement/misc/feat_of_strength
+ name = "Feat of Strength"
+ desc = "If the rod is immovable, is it passing you or are you passing it?"
+ database_id = MEDAL_RODSUPLEX
+ icon = "featofstrength"
+
+/datum/award/achievement/misc/round_and_full
+ name = "Round and Full"
+ desc = "Well at least you aren't down the river, I hear they eat people there."
+ database_id = MEDAL_CLOWNCARKING
+ icon = "clownking"
+
+/datum/award/achievement/misc/the_best_driver
+ name = "The Best Driver"
+ desc = "100 honks later"
+ database_id = MEDAL_THANKSALOT
+ icon = "clownthanks"
+
+/datum/award/achievement/misc/helbitaljanken
+ name = "Helbitaljanken"
+ desc = "You janked hard"
+ database_id = MEDAL_HELBITALJANKEN
+ icon = "helbital"
+
+/datum/award/achievement/misc/getting_an_upgrade
+ name = "Getting an upgrade"
+ desc = "Make your first unique material item!"
+ database_id = MEDAL_MATERIALCRAFT
+
+/datum/award/achievement/misc/rocket_holdup
+ name = "Disk, Please!"
+ desc = "Is the man currently pointing a loaded rocket launcher at your head point blank really dumb enough to pull the trigger? Do you really want to find out?"
+ database_id = MEDAL_DISKPLEASE
+
+/datum/award/achievement/misc/gamer
+ name = "My Watchlist Status is Not Important"
+ desc = "You may be under the impression that violent video games are a harmless pastime, but the security and medical personnel swarming your location with batons and knockout gas look like they disagree."
+ database_id = MEDAL_GAMER
+
+/datum/award/achievement/misc/vendor_squish
+ name = "I Was a Teenage Anarchist"
+ desc = "You were doing a great job sticking it to the system until that vending machine decided to fight back."
+ database_id = MEDAL_VENDORSQUISH
+
+/datum/award/achievement/misc/swirlie
+ name = "A Bowl-d New World"
+ desc = "There's a lot of grisly ways to kick it on the Spinward Periphery, but drowning to death in a toilet probably wasn't what you had in mind. Probably."
+ database_id = MEDAL_SWIRLIE
+
+/datum/award/achievement/misc/selfouch
+ name = "How Do I Switch Hands???"
+ desc = "If you saw someone casually club themselves upside the head with a toolbox anywhere in the galaxy but here, you'd probably be pretty concerned for them."
+ database_id = MEDAL_SELFOUCH
+
+/datum/award/achievement/misc/sandman
+ name = "Mister Sandman"
+ desc = "Mechanically speaking, there's no real benefit to being unconscious during surgery. Weird how insistent this doctor is about using the N2O anyway though, huh?"
+ database_id = MEDAL_SANDMAN
+
+/datum/award/achievement/misc/cleanboss
+ name = "One Lean, Mean, Cleaning Machine"
+ desc = "How does it feel to know that your workplace values a mop bucket on wheels more than you?" // i can do better than this give me time
+ database_id = MEDAL_CLEANBOSS
+
+/datum/award/achievement/misc/rule8
+ name = "Rule 8"
+ desc = "Call an admin this is ILLEGAL!!"
+ database_id = MEDAL_RULE8
+ icon = "rule8"
+
+/datum/award/achievement/misc/speed_round
+ name = "Long shift"
+ desc = "Well, that didn't take long."
+ database_id = MEDAL_LONGSHIFT
+ icon = "longshift"
+
+/datum/award/achievement/misc/snail
+ name = "KKKiiilll mmmeee"
+ desc = "You were a little too ambitious, but hey, I guess you're still alive?"
+ database_id = MEDAL_SNAIL
+ icon = "snail"
+
+/datum/award/achievement/misc/lookoutsir
+ name = "Look Out, Sir!"
+ desc = "Either awarded for making the ultimate sacrifice for your comrades, or a really dumb attempt at grenade jumping."
+ database_id = MEDAL_LOOKOUTSIR
+
+/datum/award/achievement/misc/gottem
+ name = "HA, GOTTEM"
+ desc = "Made you look!"
+ database_id = MEDAL_GOTTEM
+
+/datum/award/achievement/misc/ascension
+ name = "Ascension"
+ desc = "Caedite eos. Novit enim Dominus qui sunt eius."
+ database_id = MEDAL_ASCENSION
+ icon = "ascension"
+
+/datum/award/achievement/misc/frenching
+ name = "Frenching"
+ desc = "Just a taste, for science!"
+ database_id = MEDAL_FRENCHING
+ icon = "frenching"
+
+/datum/award/achievement/misc/ash_ascension
+ name = "Nightwatcher's Eyes"
+ desc = "You've risen above the flames, became one with the ashes. You've been reborn as one with the Nightwatcher."
+ database_id = MEDAL_ASH_ASCENSION
+ icon = "ashascend"
+
+/datum/award/achievement/misc/flesh_ascension
+ name = "Vortex of Arms"
+ desc = "You've became something more, something greater. A piece of the emperor resides within you, and you within him."
+ database_id = MEDAL_FLESH_ASCENSION
+ icon = "fleshascend"
+
+/datum/award/achievement/misc/rust_ascension
+ name = "Hills of Rust"
+ desc = "You've summoned a piece of the Hill of rust, and so the Hills welcome you."
+ database_id = MEDAL_RUST_ASCENSION
+ icon = "rustascend"
+
+/datum/award/achievement/misc/void_ascension
+ name = "All that perish"
+ desc = "Place of a diffrent being, diffrent time. Everything ends there... but maybe it is just the beginning?"
+ database_id = MEDAL_VOID_ASCENSION
+ icon = "voidascend"
+
+/datum/award/achievement/misc/toolbox_soul
+ name = "SOUL'd Out"
+ desc = "My eternal soul was destroyed to make a toolbox look funny and all I got was this achievement..."
+ database_id = MEDAL_TOOLBOX_SOUL
+ icon = "toolbox_soul"
diff --git a/code/datums/achievements/skill_achievements.dm b/code/datums/achievements/skill_achievements.dm
new file mode 100644
index 0000000000..7da936c61f
--- /dev/null
+++ b/code/datums/achievements/skill_achievements.dm
@@ -0,0 +1,10 @@
+/datum/award/achievement/skill
+ category = "Skills"
+ icon = "baseskill"
+
+/datum/award/achievement/skill/legendary_miner
+ name = "Legendary miner"
+ desc = "No mere rock can stop me!"
+ database_id = MEDAL_LEGENDARY_MINER
+ icon = "mining"
+
diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm
index 6404be94c4..f88b9e1869 100644
--- a/code/datums/components/pellet_cloud.dm
+++ b/code/datums/components/pellet_cloud.dm
@@ -275,6 +275,11 @@
else
target.visible_message("[target] is hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]!", null, null, COMBAT_MESSAGE_RANGE, target)
to_chat(target, "You're hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]!")
+
+ for(var/M in purple_hearts)
+ var/mob/living/martyr = M
+ if(martyr.stat == DEAD && martyr.client)
+ martyr.client.give_award(/datum/award/achievement/misc/lookoutsir, martyr)
UnregisterSignal(parent, COMSIG_PARENT_PREQDELETED)
if(queued_delete)
qdel(parent)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 189640a1c6..363bcbc29e 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -882,6 +882,9 @@
VV_DROPDOWN_OPTION(VV_HK_ADD_REAGENT, "Add Reagent")
VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EMP, "EMP Pulse")
VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion")
+ // VV_DROPDOWN_OPTION(VV_HK_RADIATE, "Radiate")
+ VV_DROPDOWN_OPTION(VV_HK_EDIT_FILTERS, "Edit Filters")
+ // VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller")
/atom/vv_do_topic(list/href_list)
. = ..()
@@ -925,6 +928,9 @@
var/newname = input(usr, "What do you want to rename this to?", "Automatic Rename") as null|text
if(newname)
vv_auto_rename(newname)
+ if(href_list[VV_HK_EDIT_FILTERS] && check_rights(R_VAREDIT))
+ var/client/C = usr.client
+ C?.open_filter_editor(src)
/atom/vv_get_header()
. = ..()
@@ -1145,7 +1151,6 @@
victim.log_message(message, LOG_ATTACK, color="blue")
-// Filter stuff
/atom/proc/add_filter(name,priority,list/params)
LAZYINITLIST(filter_data)
var/list/p = params.Copy()
@@ -1161,16 +1166,50 @@
var/list/arguments = data.Copy()
arguments -= "priority"
filters += filter(arglist(arguments))
+ UNSETEMPTY(filter_data)
+
+/atom/proc/transition_filter(name, time, list/new_params, easing, loop)
+ var/filter = get_filter(name)
+ if(!filter)
+ return
+
+ var/list/old_filter_data = filter_data[name]
+
+ var/list/params = old_filter_data.Copy()
+ for(var/thing in new_params)
+ params[thing] = new_params[thing]
+
+ animate(filter, new_params, time = time, easing = easing, loop = loop)
+ for(var/param in params)
+ filter_data[name][param] = params[param]
+
+/atom/proc/change_filter_priority(name, new_priority)
+ if(!filter_data || !filter_data[name])
+ return
+
+ filter_data[name]["priority"] = new_priority
+ update_filters()
+
+/obj/item/update_filters()
+ . = ..()
+ for(var/X in actions)
+ var/datum/action/A = X
+ A.UpdateButtonIcon()
/atom/proc/get_filter(name)
if(filter_data && filter_data[name])
return filters[filter_data.Find(name)]
-/atom/proc/remove_filter(name)
- if(filter_data && filter_data[name])
- filter_data -= name
- update_filters()
- return TRUE
+/atom/proc/remove_filter(name_or_names)
+ if(!filter_data)
+ return
+
+ var/list/names = islist(name_or_names) ? name_or_names : list(name_or_names)
+
+ for(var/name in names)
+ if(filter_data[name])
+ filter_data -= name
+ update_filters()
/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1)
. |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels)
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index 5cfec2376a..19e6768d71 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -1,4 +1,6 @@
#define DEFAULT_METEOR_LIFETIME 1800
+#define MAP_EDGE_PAD 5
+
GLOBAL_VAR_INIT(meteor_wave_delay, 625) //minimum wait between waves in tenths of seconds
//set to at least 100 unless you want evarr ruining every round
@@ -30,7 +32,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/turf/pickedgoal
var/max_i = 10//number of tries to spawn meteor.
while(!isspaceturf(pickedstart))
- var/startSide = dir || pick(GLOB.cardinals)
+ var/startSide = (dir ? dir : pick(GLOB.cardinals))
var/startZ = pick(SSmapping.levels_by_trait(ZTRAIT_STATION))
pickedstart = spaceDebrisStartLoc(startSide, startZ)
pickedgoal = spaceDebrisFinishLoc(startSide, startZ)
@@ -46,17 +48,17 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/startx
switch(startSide)
if(NORTH)
- starty = world.maxy-(TRANSITIONEDGE+2)
- startx = rand((TRANSITIONEDGE+2), world.maxx-(TRANSITIONEDGE+2))
+ starty = world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD)
+ startx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD))
if(EAST)
- starty = rand((TRANSITIONEDGE+2),world.maxy-(TRANSITIONEDGE+2))
- startx = world.maxx-(TRANSITIONEDGE+2)
+ starty = rand((TRANSITIONEDGE + MAP_EDGE_PAD),world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD))
+ startx = world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD)
if(SOUTH)
- starty = (TRANSITIONEDGE+2)
- startx = rand((TRANSITIONEDGE+2), world.maxx-(TRANSITIONEDGE+2))
+ starty = (TRANSITIONEDGE + MAP_EDGE_PAD)
+ startx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD))
if(WEST)
- starty = rand((TRANSITIONEDGE+2), world.maxy-(TRANSITIONEDGE+2))
- startx = (TRANSITIONEDGE+2)
+ starty = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD))
+ startx = (TRANSITIONEDGE + MAP_EDGE_PAD)
. = locate(startx, starty, Z)
/proc/spaceDebrisFinishLoc(startSide, Z)
@@ -64,17 +66,17 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/endx
switch(startSide)
if(NORTH)
- endy = (TRANSITIONEDGE+1)
- endx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1))
+ endy = (TRANSITIONEDGE + MAP_EDGE_PAD)
+ endx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD))
if(EAST)
- endy = rand((TRANSITIONEDGE+1), world.maxy-(TRANSITIONEDGE+1))
- endx = (TRANSITIONEDGE+1)
+ endy = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD))
+ endx = (TRANSITIONEDGE + MAP_EDGE_PAD)
if(SOUTH)
- endy = world.maxy-(TRANSITIONEDGE+1)
- endx = rand((TRANSITIONEDGE+1), world.maxx-(TRANSITIONEDGE+1))
+ endy = world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD)
+ endx = rand((TRANSITIONEDGE + MAP_EDGE_PAD), world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD))
if(WEST)
- endy = rand((TRANSITIONEDGE+1),world.maxy-(TRANSITIONEDGE+1))
- endx = world.maxx-(TRANSITIONEDGE+1)
+ endy = rand((TRANSITIONEDGE + MAP_EDGE_PAD),world.maxy-(TRANSITIONEDGE + MAP_EDGE_PAD))
+ endx = world.maxx-(TRANSITIONEDGE + MAP_EDGE_PAD)
. = locate(endx, endy, Z)
///////////////////////
@@ -82,7 +84,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
//////////////////////
/obj/effect/meteor
- name = "the concept of meteor"
+ name = "\proper the concept of meteor"
desc = "You should probably run instead of gawking at this."
icon = 'icons/obj/meteor.dmi'
icon_state = "small"
@@ -92,7 +94,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/hitpwr = 2 //Level of ex_act to be called on hit.
var/dest
pass_flags = PASSTABLE
- var/heavy = 0
+ var/heavy = FALSE
var/meteorsound = 'sound/effects/meteorimpact.ogg'
var/z_original
var/threat = 0 // used for determining which meteors are most interesting
@@ -108,12 +110,12 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
. = ..() //process movement...
+ var/turf/T = get_turf(loc)
if(.)//.. if did move, ram the turf we get in
- var/turf/T = get_turf(loc)
ram_turf(T)
- if(prob(10) && !isspaceturf(T) && !istype(T, /turf/closed/mineral) && !istype(T, /turf/open/floor/plating/asteroid))//randomly takes a 'hit' from ramming
- get_hit()
+ if(prob(10) && !isspaceturf(T) && !istype(T, /turf/closed/mineral) && !istype(T, /turf/open/floor/plating/asteroid))//randomly takes a 'hit' from ramming, and ignore spare ruin aseroids
+ get_hit()
/obj/effect/meteor/Destroy()
if (timerid)
@@ -135,24 +137,25 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
/obj/effect/meteor/Bump(atom/A)
if(A)
ram_turf(get_turf(A))
- playsound(src.loc, meteorsound, 40, 1)
- if(!istype(A, /turf/closed/mineral) && !istype(A, /turf/open/floor/plating/asteroid))
+ playsound(src.loc, meteorsound, 40, TRUE)
+ if(!istype(A, /turf/closed/mineral) && !istype(A, /turf/open/floor/plating/asteroid)) // ignore localstation ruins
get_hit()
/obj/effect/meteor/proc/ram_turf(turf/T)
//first bust whatever is in the turf
- for(var/atom/A in T)
- if(A != src)
- if(isliving(A))
- A.visible_message("[src] slams into [A].", "[src] slams into you!.")
- A.ex_act(hitpwr)
+ for(var/thing in T)
+ if(thing == src)
+ continue
+ if(isliving(thing))
+ var/mob/living/living_thing = thing
+ living_thing.visible_message("[src] slams into [living_thing].", "[src] slams into you!.")
+ A.ex_act(hitpwr)
//then, ram the turf if it still exists
if(T)
T.ex_act(hitpwr)
-
//process getting 'hit' by colliding with a dense object
//or randomly when ramming turfs
/obj/effect/meteor/proc/get_hit()
@@ -162,13 +165,10 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
meteor_effect()
qdel(src)
-/obj/effect/meteor/ex_act()
- return
-
/obj/effect/meteor/examine(mob/user)
+ . = ..()
if(!(flags_1 & ADMIN_SPAWNED_1) && isliving(user))
- SSmedals.UnlockMedal(MEDAL_METEOR, user.client)
- return ..()
+ user.client.give_award(/datum/award/achievement/misc/meteor_examine, user)
/obj/effect/meteor/attackby(obj/item/I, mob/user, params)
if(I.tool_behaviour == TOOL_MINING)
@@ -232,7 +232,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
name = "big meteor"
icon_state = "large"
hits = 6
- heavy = 1
+ heavy = TRUE
dropamt = 4
threat = 10
@@ -245,7 +245,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
name = "flaming meteor"
icon_state = "flaming"
hits = 5
- heavy = 1
+ heavy = TRUE
meteorsound = 'sound/effects/bamf.ogg'
meteordrop = list(/obj/item/stack/ore/plasma)
threat = 20
@@ -258,7 +258,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
/obj/effect/meteor/irradiated
name = "glowing meteor"
icon_state = "glowing"
- heavy = 1
+ heavy = TRUE
meteordrop = list(/obj/item/stack/ore/uranium)
threat = 15
@@ -275,7 +275,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
icon_state = "meateor"
desc = "Just... don't think too hard about where this thing came from."
hits = 2
- heavy = 1
+ heavy = TRUE
meteorsound = 'sound/effects/blobattack.ogg'
meteordrop = list(/obj/item/reagent_containers/food/snacks/meat/slab/human, /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant, /obj/item/organ/heart, /obj/item/organ/lungs, /obj/item/organ/tongue, /obj/item/organ/appendix/)
var/meteorgibs = /obj/effect/gibspawner/generic
@@ -327,7 +327,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
desc = "Your life briefly passes before your eyes the moment you lay them on this monstrosity."
hits = 30
hitpwr = 1
- heavy = 1
+ heavy = TRUE
meteorsound = 'sound/effects/bamf.ogg'
meteordrop = list(/obj/item/stack/ore/plasma)
threat = 50
@@ -358,7 +358,7 @@ GLOBAL_LIST_INIT(meteorsSPOOKY, list(/obj/effect/meteor/pumpkin))
icon = 'icons/obj/meteor_spooky.dmi'
icon_state = "pumpkin"
hits = 10
- heavy = 1
+ heavy = TRUE
dropamt = 1
meteordrop = list(/obj/item/clothing/head/hardhat/pumpkinhead, /obj/item/reagent_containers/food/snacks/grown/pumpkin)
threat = 100
@@ -368,3 +368,4 @@ GLOBAL_LIST_INIT(meteorsSPOOKY, list(/obj/effect/meteor/pumpkin))
meteorsound = pick('sound/hallucinations/im_here1.ogg','sound/hallucinations/im_here2.ogg')
//////////////////////////
#undef DEFAULT_METEOR_LIFETIME
+#undef MAP_EDGE_PAD
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index 1f0687151d..d8a5f7d2c7 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -1,6 +1,6 @@
-#define AUTOLATHE_MAIN_MENU 1
-#define AUTOLATHE_CATEGORY_MENU 2
-#define AUTOLATHE_SEARCH_MENU 3
+#define AUTOLATHE_MAIN_MENU 1
+#define AUTOLATHE_CATEGORY_MENU 2
+#define AUTOLATHE_SEARCH_MENU 3
/obj/machinery/autolathe
name = "autolathe"
@@ -17,7 +17,7 @@
var/list/L = list()
var/list/LL = list()
var/hacked = FALSE
- var/disabled = 0
+ var/disabled = FALSE
var/shocked = FALSE
var/hack_wire
var/disable_wire
@@ -27,13 +27,13 @@
var/prod_coeff = 1
var/datum/design/being_built
+ var/datum/techweb/stored_research
var/list/datum/design/matching_designs
var/selected_category
var/screen = 1
var/base_price = 25
var/hacked_price = 50
- var/datum/techweb/specialized/autounlocking/stored_research = /datum/techweb/specialized/autounlocking/autolathe
var/list/categories = list(
"Tools",
"Electronics",
@@ -46,19 +46,13 @@
"Dinnerware",
"Imported"
)
- var/list/allowed_materials
-
- /// Base print speed
- var/base_print_speed = 10
/obj/machinery/autolathe/Initialize()
- var/list/mats = allowed_materials
- if(!mats)
- mats = SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID]
- AddComponent(/datum/component/material_container, mats, _show_on_examine=TRUE, _after_insert=CALLBACK(src, .proc/AfterMaterialInsert))
+ AddComponent(/datum/component/material_container, SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID], 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert))
. = ..()
+
wires = new /datum/wires/autolathe(src)
- stored_research = new stored_research
+ stored_research = new /datum/techweb/specialized/autounlocking/autolathe
matching_designs = list()
/obj/machinery/autolathe/Destroy()
@@ -83,7 +77,7 @@
if(AUTOLATHE_SEARCH_MENU)
dat = search_win(user)
- var/datum/browser/popup = new(user, name, name, 400, 500)
+ var/datum/browser/popup = new(user, "autolathe", name, 400, 500)
popup.set_content(dat)
popup.open()
@@ -114,9 +108,9 @@
return TRUE
if(istype(O, /obj/item/disk/design_disk))
- user.visible_message("[user] begins to load \the [O] in \the [src]...",
- "You begin to load a design from \the [O]...",
- "You hear the chatter of a floppy drive.")
+ user.visible_message("[user] begins to load \the [O] in \the [src]...",
+ "You begin to load a design from \the [O]...",
+ "You hear the chatter of a floppy drive.")
busy = TRUE
var/obj/item/disk/design_disk/D = O
if(do_after(user, 14.4, target = src))
@@ -128,14 +122,16 @@
return ..()
-/obj/machinery/autolathe/proc/AfterMaterialInsert(obj/item/item_inserted, id_inserted, amount_inserted)
+
+/obj/machinery/autolathe/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted)
if(istype(item_inserted, /obj/item/stack/ore/bluespace_crystal))
use_power(MINERAL_MATERIAL_AMOUNT / 10)
- else if(item_inserted.custom_materials?.len && item_inserted.custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)])
+ else if(custom_materials && custom_materials.len && custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)])
flick("autolathe_r",src)//plays glass insertion animation by default otherwise
else
flick("autolathe_o",src)//plays metal insertion animation
+
use_power(min(1000, amount_inserted / 100))
updateUsrDialog()
@@ -187,7 +183,7 @@
if(materials.materials[i] > 0)
list_to_show += i
- used_material = input("Choose [used_material]", "Custom Material") as null|anything in list_to_show
+ used_material = input("Choose [used_material]", "Custom Material") as null|anything in sortList(list_to_show, /proc/cmp_typepaths_asc)
if(!used_material)
return //Didn't pick any material, so you can't build shit either.
custom_materials[used_material] += amount_needed
@@ -198,8 +194,8 @@
busy = TRUE
use_power(power)
icon_state = "autolathe_n"
- var/time = is_stack ? 10 : base_print_speed * coeff * multiplier
- addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack), time)
+ var/time = is_stack ? 32 : (32 * coeff * multiplier) ** 0.8
+ addtimer(CALLBACK(src, .proc/make_item, power, materials_used, custom_materials, multiplier, coeff, is_stack, usr), time)
else
to_chat(usr, "Not enough materials for this operation.")
@@ -218,10 +214,11 @@
return
-/obj/machinery/autolathe/proc/make_item(power, var/list/materials_used, var/list/picked_materials, multiplier, coeff, is_stack)
+/obj/machinery/autolathe/proc/make_item(power, list/materials_used, list/picked_materials, multiplier, coeff, is_stack, mob/user)
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
var/atom/A = drop_location()
use_power(power)
+
materials.use_materials(materials_used)
if(is_stack)
@@ -235,6 +232,11 @@
if(length(picked_materials))
new_item.set_custom_materials(picked_materials, 1 / multiplier) //Ensure we get the non multiplied amount
+ for(var/x in picked_materials)
+ var/datum/material/M = x
+ if(!istype(M, /datum/material/glass) && !istype(M, /datum/material/iron))
+ user.client.give_award(/datum/award/achievement/misc/getting_an_upgrade, user)
+
icon_state = "autolathe"
busy = FALSE
@@ -246,12 +248,10 @@
T += MB.rating*75000
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
materials.max_amount = T
- var/manips = 0
- var/total_manip_rating = 0
+ T=1.2
for(var/obj/item/stock_parts/manipulator/M in component_parts)
- total_manip_rating += M.rating
- manips++
- prod_coeff = STANDARD_PART_LEVEL_LATHE_COEFFICIENT(total_manip_rating / (manips? manips : 1))
+ T -= M.rating*0.2
+ prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4
/obj/machinery/autolathe/examine(mob/user)
. += ..()
@@ -376,6 +376,7 @@
return materials.has_materials(required_materials)
+
/obj/machinery/autolathe/proc/get_design_cost(datum/design/D)
var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff)
var/dat
@@ -416,9 +417,7 @@
hacked = state
for(var/id in SSresearch.techweb_designs)
var/datum/design/D = SSresearch.techweb_design_by_id(id)
- if(D.build_type & stored_research.design_autounlock_skip_types)
- continue
- if((D.build_type & stored_research.design_autounlock_buildtypes) && ("hacked" in D.category))
+ if((D.build_type & AUTOLATHE) && ("hacked" in D.category))
if(hacked)
stored_research.add_design(D)
else
@@ -428,19 +427,24 @@
. = ..()
adjust_hacked(TRUE)
+//Called when the object is constructed by an autolathe
+//Has a reference to the autolathe so you can do !!FUN!! things with hacked lathes
+/obj/item/proc/autolathe_crafted(obj/machinery/autolathe/A)
+ return
+
/obj/machinery/autolathe/secure
name = "secured autolathe"
desc = "It produces items using metal and glass. This model was reprogrammed without some of the more hazardous designs."
circuit = /obj/item/circuitboard/machine/autolathe/secure
- stored_research = /datum/techweb/specialized/autounlocking/autolathe/public
- base_print_speed = 20
+
+/obj/machinery/autolathe/secure/Initialize()
+ . = ..()
+ stored_research = new /datum/techweb/specialized/autounlocking/autolathe/public
/obj/machinery/autolathe/toy
name = "autoylathe"
desc = "It produces toys using plastic, metal and glass."
circuit = /obj/item/circuitboard/machine/autolathe/toy
-
- stored_research = /datum/techweb/specialized/autounlocking/autolathe/toy
categories = list(
"Toys",
"Figurines",
@@ -453,12 +457,8 @@
"Misc",
"Imported"
)
- allowed_materials = list(
- /datum/material/iron,
- /datum/material/glass,
- /datum/material/plastic
- )
/obj/machinery/autolathe/toy/hacked/Initialize()
. = ..()
adjust_hacked(TRUE)
+ stored_research = new /datum/techweb/specialized/autounlocking/autolathe/toy
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 077571b931..8085d69f49 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -3,14 +3,7 @@
#define ARCADE_WEIGHT_RARE 1
#define ARCADE_RATIO_PLUSH 0.20 // average 1 out of 6 wins is a plush.
-/obj/machinery/computer/arcade
- name = "random arcade"
- desc = "random arcade machine"
- icon_state = "arcade"
- icon_keyboard = null
- icon_screen = "invaders"
- clockwork = TRUE //it'd look weird
- var/list/prizes = list(
+GLOBAL_LIST_INIT(arcade_prize_pool, list(
/obj/item/toy/balloon = ARCADE_WEIGHT_USELESS,
/obj/item/toy/beach_ball = ARCADE_WEIGHT_USELESS,
/obj/item/toy/cattoy = ARCADE_WEIGHT_USELESS,
@@ -70,9 +63,16 @@
/obj/item/clothing/mask/fakemoustache/italian = ARCADE_WEIGHT_RARE,
/obj/item/clothing/suit/hooded/wintercoat/ratvar/fake = ARCADE_WEIGHT_TRICK,
/obj/item/clothing/suit/hooded/wintercoat/narsie/fake = ARCADE_WEIGHT_TRICK
- )
+))
+/obj/machinery/computer/arcade
+ name = "random arcade"
+ desc = "random arcade machine"
+ icon_state = "arcade"
+ icon_keyboard = "no_keyboard"
+ icon_screen = "invaders"
light_color = LIGHT_COLOR_GREEN
+ var/list/prize_override
/obj/machinery/computer/arcade/proc/Reset()
return
@@ -96,39 +96,51 @@
prizes[/obj/item/toy/plush/random] = counterlist_sum(prizes) * ARCADE_RATIO_PLUSH
Reset()
-/obj/machinery/computer/arcade/proc/prizevend(mob/user, list/rarity_classes)
- SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "arcade", /datum/mood_event/arcade)
+/obj/machinery/computer/arcade/proc/prizevend(mob/user, prizes = 1)
+ // if(user.mind?.get_skill_level(/datum/skill/gaming) >= SKILL_LEVEL_LEGENDARY && HAS_TRAIT(user, TRAIT_GAMERGOD))
+ // visible_message("[user] inputs an intense cheat code!",\
+ // "You hear a flurry of buttons being pressed.")
+ // say("CODE ACTIVATED: EXTRA PRIZES.")
+ // prizes *= 2
+ for(var/i = 0, i < prizes, i++)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "arcade", /datum/mood_event/arcade)
+ if(prob(0.0001)) //1 in a million
+ new /obj/item/gun/energy/pulse/prize(src)
+ visible_message("[src] dispenses.. woah, a gun! Way past cool.", "You hear a chime and a shot.")
+ user.client.give_award(/datum/award/achievement/misc/pulse, user)
+ return
- if(prob(1) && prob(1) && prob(1)) //Proper 1 in a million
- new /obj/item/gun/energy/pulse/prize(src)
- SSmedals.UnlockMedal(MEDAL_PULSE, usr.client)
+ var/prizeselect
+ if(prize_override)
+ prizeselect = pickweight(prize_override)
+ else
+ prizeselect = pickweight(GLOB.arcade_prize_pool)
+ var/atom/movable/the_prize = new prizeselect(get_turf(src))
+ playsound(src, 'sound/machines/machine_vend.ogg', 50, TRUE, extrarange = -3)
+ visible_message("[src] dispenses [the_prize]!", "You hear a chime and a clunk.")
- if(!contents.len)
- var/list/toy_raffle
- if(rarity_classes)
- for(var/A in prizes)
- if(prizes[A] in rarity_classes)
- LAZYSET(toy_raffle, A, prizes[A])
- if(!toy_raffle)
- toy_raffle = prizes
- var/prizeselect = pickweight(toy_raffle)
- new prizeselect(src)
-
- var/atom/movable/prize = pick(contents)
- visible_message("[src] dispenses [prize]!", "You hear a chime and a clunk.")
-
- prize.forceMove(get_turf(src))
/obj/machinery/computer/arcade/emp_act(severity)
. = ..()
+ var/override = FALSE
+ if(prize_override)
+ override = TRUE
if(stat & (NOPOWER|BROKEN) || . & EMP_PROTECT_SELF)
return
var/empprize = null
- var/num_of_prizes = rand(round(severity/50),round(severity/100))
+ var/num_of_prizes = 0
+ switch(severity)
+ if(1)
+ num_of_prizes = rand(1,4)
+ if(2)
+ num_of_prizes = rand(0,2)
for(var/i = num_of_prizes; i > 0; i--)
- empprize = pickweight(prizes)
+ if(override)
+ empprize = pickweight(prize_override)
+ else
+ empprize = pickweight(GLOB.arcade_prize_pool)
new empprize(loc)
explosion(loc, -1, 0, 1+num_of_prizes, flame_range = 1+num_of_prizes)
diff --git a/code/game/machinery/computer/arcade/battle.dm b/code/game/machinery/computer/arcade/battle.dm
index 96f224f4cf..8240a22290 100644
--- a/code/game/machinery/computer/arcade/battle.dm
+++ b/code/game/machinery/computer/arcade/battle.dm
@@ -1,130 +1,399 @@
// ** BATTLE ** //
-
-
/obj/machinery/computer/arcade/battle
name = "arcade machine"
desc = "Does not support Pinball."
icon_state = "arcade"
circuit = /obj/item/circuitboard/computer/arcade/battle
- var/enemy_name = "Space Villain"
- var/temp = "Winners don't use space drugs" //Temporary message, for attack messages, etc
- var/player_hp = 30 //Player health/attack points
- var/player_mp = 10
- var/enemy_hp = 45 //Enemy health/attack points
- var/enemy_mp = 20
- var/gameover = FALSE
- var/blocked = FALSE //Player cannot attack/heal while set
- var/turtle = 0
- var/turn_speed = 5 //Measured in deciseconds.
+ var/enemy_name = "Space Villain"
+ ///Enemy health/attack points
+ var/enemy_hp = 100
+ var/enemy_mp = 40
+ ///Temporary message, for attack messages, etc
+ var/temp = "
Winners don't use space drugs"
+ ///the list of passive skill the enemy currently has. the actual passives are added in the enemy_setup() proc
+ var/list/enemy_passive
+ ///if all the enemy's weakpoints have been triggered becomes TRUE
+ var/finishing_move = FALSE
+ ///linked to passives, when it's equal or above the max_passive finishing move will become TRUE
+ var/pissed_off = 0
+ ///the number of passives the enemy will start with
+ var/max_passive = 3
+ ///weapon wielded by the enemy, the shotgun doesn't count.
+ var/chosen_weapon
+
+ ///Player health
+ var/player_hp = 85
+ ///player magic points
+ var/player_mp = 20
+ ///used to remember the last three move of the player before this turn.
+ var/list/last_three_move
+ ///if the enemy or player died. restart the game when TRUE
+ var/gameover = FALSE
+ ///the player cannot make any move while this is set to TRUE. should only TRUE during enemy turns.
+ var/blocked = FALSE
+ ///used to clear the enemy_action proc timer when the game is restarted
+ var/timer_id
+ ///weapon used by the enemy, pure fluff.for certain actions
+ var/list/weapons
+ ///unique to the emag mode, acts as a time limit where the player dies when it reaches 0.
+ var/bomb_cooldown = 19
+
+///creates the enemy base stats for a new round along with the enemy passives
+/obj/machinery/computer/arcade/battle/proc/enemy_setup(player_skill)
+ player_hp = 85
+ player_mp = 20
+ enemy_hp = 100
+ enemy_mp = 40
+ gameover = FALSE
+ blocked = FALSE
+ finishing_move = FALSE
+ pissed_off = 0
+ last_three_move = null
+
+ enemy_passive = list("short_temper" = TRUE, "poisonous" = TRUE, "smart" = TRUE, "shotgun" = TRUE, "magical" = TRUE, "chonker" = TRUE)
+ for(var/i = LAZYLEN(enemy_passive); i > max_passive; i--) //we'll remove passives from the list until we have the number of passive we want
+ var/picked_passive = pick(enemy_passive)
+ LAZYREMOVE(enemy_passive, picked_passive)
+
+ if(LAZYACCESS(enemy_passive, "chonker"))
+ enemy_hp += 20
+
+ if(LAZYACCESS(enemy_passive, "shotgun"))
+ chosen_weapon = "shotgun"
+ else if(weapons)
+ chosen_weapon = pick(weapons)
+ else
+ chosen_weapon = "null gun" //if the weapons list is somehow empty, shouldn't happen but runtimes are sneaky bastards.
+
+ if(player_skill)
+ player_hp += player_skill * 2
/obj/machinery/computer/arcade/battle/Reset()
+ max_passive = 3
var/name_action
var/name_part1
var/name_part2
- name_action = pick("Defeat ", "Annihilate ", "Save ", "Strike ", "Stop ", "Destroy ", "Robust ", "Romance ", "Pwn ", "Own ", "Ban ")
+ if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
+ name_action = pick_list(ARCADE_FILE, "rpg_action_halloween")
+ name_part1 = pick_list(ARCADE_FILE, "rpg_adjective_halloween")
+ name_part2 = pick_list(ARCADE_FILE, "rpg_enemy_halloween")
+ weapons = strings(ARCADE_FILE, "rpg_weapon_halloween")
+ else if(SSevents.holidays && SSevents.holidays[CHRISTMAS])
+ name_action = pick_list(ARCADE_FILE, "rpg_action_xmas")
+ name_part1 = pick_list(ARCADE_FILE, "rpg_adjective_xmas")
+ name_part2 = pick_list(ARCADE_FILE, "rpg_enemy_xmas")
+ weapons = strings(ARCADE_FILE, "rpg_weapon_xmas")
+ else if(SSevents.holidays && SSevents.holidays[VALENTINES])
+ name_action = pick_list(ARCADE_FILE, "rpg_action_valentines")
+ name_part1 = pick_list(ARCADE_FILE, "rpg_adjective_valentines")
+ name_part2 = pick_list(ARCADE_FILE, "rpg_enemy_valentines")
+ weapons = strings(ARCADE_FILE, "rpg_weapon_valentines")
+ else
+ name_action = pick_list(ARCADE_FILE, "rpg_action")
+ name_part1 = pick_list(ARCADE_FILE, "rpg_adjective")
+ name_part2 = pick_list(ARCADE_FILE, "rpg_enemy")
+ weapons = strings(ARCADE_FILE, "rpg_weapon")
- name_part1 = pick("the Automatic ", "Farmer ", "Lord ", "Professor ", "the Cuban ", "the Evil ", "the Dread King ", "the Space ", "Lord ", "the Great ", "Duke ", "General ")
- name_part2 = pick("Melonoid", "Murdertron", "Sorcerer", "Ruin", "Jeff", "Ectoplasm", "Crushulon", "Uhangoid", "Vhakoid", "Peteoid", "slime", "Griefer", "ERPer", "Lizard Man", "Unicorn", "Bloopers")
+ enemy_name = ("The " + name_part1 + " " + name_part2)
+ name = (name_action + " " + enemy_name)
- enemy_name = replacetext((name_part1 + name_part2), "the ", "")
- name = (name_action + name_part1 + name_part2)
+ enemy_setup(0) //in the case it's reset we assume the player skill is 0 because the VOID isn't a gamer
/obj/machinery/computer/arcade/battle/ui_interact(mob/user)
. = ..()
+ screen_setup(user)
+
+///sets up the main screen for the user
+/obj/machinery/computer/arcade/battle/proc/screen_setup(mob/user)
var/dat = "Close"
dat += "[enemy_name]
"
- dat += "
[temp]
"
+ dat += "[temp]"
dat += "
Health: [player_hp] | Magic: [player_mp] | Enemy Health: [enemy_hp]"
if (gameover)
dat += "New Game"
else
- dat += "Attack | "
- dat += "Heal | "
- dat += "Recharge Power"
+ dat += "Light attack"
+ dat += "Defend"
+ dat += "Counter attack"
+ dat += "Power attack"
dat += ""
- var/datum/browser/popup = new(user, "arcade", "Space Villain 2000")
- popup.set_content(dat)
- popup.open()
+ if(user.client) //mainly here to avoid a runtime when the player gets gibbed when losing the emag mode.
+ var/datum/browser/popup = new(user, "arcade", "Space Villain 2000")
+ popup.set_content(dat)
+ popup.open()
/obj/machinery/computer/arcade/battle/Topic(href, href_list)
if(..())
return
+ var/gamerSkill = 0
+ // if(usr?.mind)
+ // gamerSkill = usr.mind.get_skill_level(/datum/skill/gaming)
if (!blocked && !gameover)
+ var/attackamt = rand(5,7) + rand(0, gamerSkill)
+
+ if(finishing_move) //time to bonk that fucker,cuban pete will sometime survive a finishing move.
+ attackamt *= 100
+
+ //light attack suck absolute ass but it doesn't cost any MP so it's pretty good to finish an enemy off
if (href_list["attack"])
- blocked = TRUE
- var/attackamt = rand(2,6)
- temp = "You attack for [attackamt] damage!"
- playsound(loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3)
- updateUsrDialog()
- if(turtle > 0)
- turtle--
-
- sleep(turn_speed)
+ temp = "
you do quick jab for [attackamt] of damage!
"
enemy_hp -= attackamt
- arcade_action(usr)
+ arcade_action(usr,"attack",attackamt)
- else if (href_list["heal"])
- blocked = TRUE
- var/pointamt = rand(1,3)
- var/healamt = rand(6,8)
- temp = "You use [pointamt] magic to heal for [healamt] damage!"
- playsound(loc, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3)
- updateUsrDialog()
- turtle++
+ //defend lets you gain back MP and take less damage from non magical attack.
+ else if(href_list["defend"])
+ temp = "
you take a defensive stance and gain back 10 mp!
"
+ player_mp += 10
+ arcade_action(usr,"defend",attackamt)
+ playsound(src, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3)
- sleep(turn_speed)
- player_mp -= pointamt
- player_hp += healamt
- blocked = TRUE
- updateUsrDialog()
- arcade_action(usr)
+ //mainly used to counter short temper and their absurd damage, will deal twice the damage the player took of a non magical attack.
+ else if(href_list["counter_attack"] && player_mp >= 10)
+ temp = "
you prepare yourself to counter the next attack!
"
+ player_mp -= 10
+ arcade_action(usr,"counter_attack",attackamt)
+ playsound(src, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3)
- else if (href_list["charge"])
- blocked = TRUE
- var/chargeamt = rand(4,7)
- temp = "You regain [chargeamt] points"
- playsound(loc, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3)
- player_mp += chargeamt
- if(turtle > 0)
- turtle--
+ else if(href_list["counter_attack"] && player_mp < 10)
+ temp = "
you don't have the mp necessary to counter attack and defend yourself instead
"
+ player_mp += 10
+ arcade_action(usr,"defend",attackamt)
+ playsound(src, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3)
- updateUsrDialog()
- sleep(turn_speed)
- arcade_action(usr)
+ //power attack deals twice the amount of damage but is really expensive MP wise, mainly used with combos to get weakpoints.
+ else if (href_list["power_attack"] && player_mp >= 20)
+ temp = "
You attack [enemy_name] with all your might for [attackamt * 2] damage!
"
+ enemy_hp -= attackamt * 2
+ player_mp -= 20
+ arcade_action(usr,"power_attack",attackamt)
+
+ else if(href_list["power_attack"] && player_mp < 20)
+ temp = "
You don't have the mp necessary for a power attack and settle for a light attack!
"
+ enemy_hp -= attackamt
+ arcade_action(usr,"attack",attackamt)
if (href_list["close"])
usr.unset_machine()
usr << browse(null, "window=arcade")
else if (href_list["newgame"]) //Reset everything
- temp = "New Round"
- player_hp = initial(player_hp)
- player_mp = initial(player_mp)
- enemy_hp = initial(enemy_hp)
- enemy_mp = initial(enemy_mp)
- gameover = FALSE
- turtle = 0
+ temp = "
New Round"
if(obj_flags & EMAGGED)
Reset()
obj_flags &= ~EMAGGED
+ enemy_setup(gamerSkill)
+ screen_setup(usr)
+
+
add_fingerprint(usr)
updateUsrDialog()
return
-/obj/machinery/computer/arcade/battle/proc/arcade_action(mob/user)
- if ((enemy_mp <= 0) || (enemy_hp <= 0))
+///happens after a player action and before the enemy turn. the enemy turn will be cancelled if there's a gameover.
+/obj/machinery/computer/arcade/battle/proc/arcade_action(mob/user,player_stance,attackamt)
+ screen_setup(user)
+ blocked = TRUE
+ if(player_stance == "attack" || player_stance == "power_attack")
+ if(attackamt > 40)
+ playsound(src, 'sound/arcade/boom.ogg', 50, TRUE, extrarange = -3)
+ else
+ playsound(src, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3)
+
+ timer_id = addtimer(CALLBACK(src, .proc/enemy_action,player_stance,user),1 SECONDS,TIMER_STOPPABLE)
+ gameover_check(user)
+
+///the enemy turn, the enemy's action entirely depend on their current passive and a teensy tiny bit of randomness
+/obj/machinery/computer/arcade/battle/proc/enemy_action(player_stance,mob/user)
+ var/list/list_temp = list()
+
+ switch(LAZYLEN(last_three_move)) //we keep the last three action of the player in a list here
+ if(0 to 2)
+ LAZYADD(last_three_move, player_stance)
+ if(3)
+ for(var/i in 1 to 2)
+ last_three_move[i] = last_three_move[i + 1]
+ last_three_move[3] = player_stance
+
+ if(4 to INFINITY)
+ last_three_move = null //this shouldn't even happen but we empty the list if it somehow goes above 3
+
+ var/enemy_stance
+ var/attack_amount = rand(8,10) //making the attack amount not vary too much so that it's easier to see if the enemy has a shotgun
+
+ if(player_stance == "defend")
+ attack_amount -= 5
+
+ //if emagged, cuban pete will set up a bomb acting up as a timer. when it reaches 0 the player fucking dies
+ if(obj_flags & EMAGGED)
+ switch(bomb_cooldown--)
+ if(18)
+ list_temp += "
[enemy_name] takes two valve tank and links them together, what's he planning?"
+ if(15)
+ list_temp += "
[enemy_name] adds a remote control to the tan- ho god is that a bomb?"
+ if(12)
+ list_temp += "
[enemy_name] throws the bomb next to you, you'r too scared to pick it up. "
+ if(6)
+ list_temp += "
[enemy_name]'s hand brushes the remote linked to the bomb, your heart skipped a beat. "
+ if(2)
+ list_temp += "
[enemy_name] is going to press the button! It's now or never! "
+ if(0)
+ player_hp -= attack_amount * 1000 //hey it's a maxcap we might as well go all in
+
+ //yeah I used the shotgun as a passive, you know why? because the shotgun gives +5 attack which is pretty good
+ if(LAZYACCESS(enemy_passive, "shotgun"))
+ if(weakpoint_check("shotgun","defend","defend","power_attack"))
+ list_temp += "
You manage to disarm [enemy_name] with a surprise power attack and shoot him with his shotgun until it runs out of ammo! "
+ enemy_hp -= 10
+ chosen_weapon = "empty shotgun"
+ else
+ attack_amount += 5
+
+ //heccing chonker passive, only gives more HP at the start of a new game but has one of the hardest weakpoint to trigger.
+ if(LAZYACCESS(enemy_passive, "chonker"))
+ if(weakpoint_check("chonker","power_attack","power_attack","power_attack"))
+ list_temp += "
After a lot of power attacks you manage to tip over [enemy_name] as they fall over their enormous weight "
+ enemy_hp -= 30
+
+ //smart passive trait, mainly works in tandem with other traits, makes the enemy unable to be counter_attacked
+ if(LAZYACCESS(enemy_passive, "smart"))
+ if(weakpoint_check("smart","defend","defend","attack"))
+ list_temp += "
[enemy_name] is confused by your illogical strategy! "
+ attack_amount -= 5
+
+ else if(attack_amount >= player_hp)
+ player_hp -= attack_amount
+ list_temp += "
[enemy_name] figures out you are really close to death and finishes you off with their [chosen_weapon]!"
+ enemy_stance = "attack"
+
+ else if(player_stance == "counter_attack")
+ list_temp += "
[enemy_name] is not taking your bait. "
+ if(LAZYACCESS(enemy_passive, "short_temper"))
+ list_temp += "However controlling their hatred of you still takes a toll on their mental and physical health!"
+ enemy_hp -= 5
+ enemy_mp -= 5
+ enemy_stance = "defensive"
+
+ //short temper passive trait, gets easily baited into being counter attacked but will bypass your counter when low on HP
+ if(LAZYACCESS(enemy_passive, "short_temper"))
+ if(weakpoint_check("short_temper","counter_attack","counter_attack","counter_attack"))
+ list_temp += "
[enemy_name] is getting frustrated at all your counter attacks and throws a tantrum!"
+ enemy_hp -= attack_amount
+
+ else if(player_stance == "counter_attack")
+ if(!(LAZYACCESS(enemy_passive, "smart")) && enemy_hp > 30)
+ list_temp += "
[enemy_name] took the bait and allowed you to counter attack for [attack_amount * 2] damage!"
+ player_hp -= attack_amount
+ enemy_hp -= attack_amount * 2
+ enemy_stance = "attack"
+
+ else if(enemy_hp <= 30) //will break through the counter when low enough on HP even when smart.
+ list_temp += "
[enemy_name] is getting tired of your tricks and breaks through your counter with their [chosen_weapon]!"
+ player_hp -= attack_amount
+ enemy_stance = "attack"
+
+ else if(!enemy_stance)
+ var/added_temp
+
+ if(rand())
+ added_temp = "you for [attack_amount + 5] damage!"
+ player_hp -= attack_amount + 5
+ enemy_stance = "attack"
+ else
+ added_temp = "the wall, breaking their skull in the process and losing [attack_amount] hp!" //[enemy_name] you have a literal dent in your skull
+ enemy_hp -= attack_amount
+ enemy_stance = "attack"
+
+ list_temp += "
[enemy_name] grits their teeth and charge right into [added_temp]"
+
+ //in the case none of the previous passive triggered, Mainly here to set an enemy stance for passives that needs it like the magical passive.
+ if(!enemy_stance)
+ enemy_stance = pick("attack","defensive")
+ if(enemy_stance == "attack")
+ player_hp -= attack_amount
+ list_temp += "
[enemy_name] attacks you for [attack_amount] points of damage with their [chosen_weapon]"
+ if(player_stance == "counter_attack")
+ enemy_hp -= attack_amount * 2
+ list_temp += "
You counter [enemy_name]'s attack and deal [attack_amount * 2] points of damage!"
+
+ if(enemy_stance == "defensive" && enemy_mp < 15)
+ list_temp += "
[enemy_name] take some time to get some mp back! "
+ enemy_mp += attack_amount
+
+ else if (enemy_stance == "defensive" && enemy_mp >= 15 && !(LAZYACCESS(enemy_passive, "magical")))
+ list_temp += "
[enemy_name] quickly heal themselves for 5 hp! "
+ enemy_mp -= 15
+ enemy_hp += 5
+
+ //magical passive trait, recharges MP nearly every turn it's not blasting you with magic.
+ if(LAZYACCESS(enemy_passive, "magical"))
+ if(player_mp >= 50)
+ list_temp += "
the huge amount of magical energy you have acumulated throws [enemy_name] off balance!"
+ enemy_mp = 0
+ LAZYREMOVE(enemy_passive, "magical")
+ pissed_off++
+
+ else if(LAZYACCESS(enemy_passive, "smart") && player_stance == "counter_attack" && enemy_mp >= 20)
+ list_temp += "
[enemy_name] blasts you with magic from afar for 10 points of damage before you can counter!"
+ player_hp -= 10
+ enemy_mp -= 20
+
+ else if(enemy_hp >= 20 && enemy_mp >= 40 && enemy_stance == "defensive")
+ list_temp += "
[enemy_name] Blasts you with magic from afar!"
+ enemy_mp -= 40
+ player_hp -= 30
+ enemy_stance = "attack"
+
+ else if(enemy_hp < 20 && enemy_mp >= 20 && enemy_stance == "defensive") //it's a pretty expensive spell so they can't spam it that much
+ list_temp += "
[enemy_name] heal themselves with magic and gain back 20 hp!"
+ enemy_hp += 20
+ enemy_mp -= 30
+ else
+ list_temp += "
[enemy_name]'s magical nature lets them get some mp back!"
+ enemy_mp += attack_amount
+
+ //poisonous passive trait, while it's less damage added than the shotgun it acts up even when the enemy doesn't attack at all.
+ if(LAZYACCESS(enemy_passive, "poisonous"))
+ if(weakpoint_check("poisonous","attack","attack","attack"))
+ list_temp += "
your flurry of attack throws back the poisonnous gas at [enemy_name] and makes them choke on it! "
+ enemy_hp -= 5
+ else
+ list_temp += "
the stinky breath of [enemy_name] hurts you for 3 hp! "
+ player_hp -= 3
+
+ //if all passive's weakpoint have been triggered, set finishing_move to TRUE
+ if(pissed_off >= max_passive && !finishing_move)
+ list_temp += "
You have weakened [enemy_name] enough for them to show their weak point, you will do 10 times as much damage with your next attack! "
+ finishing_move = TRUE
+
+ playsound(src, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3)
+
+ temp = list_temp.Join()
+ gameover_check(user)
+ screen_setup(user)
+ blocked = FALSE
+
+
+/obj/machinery/computer/arcade/battle/proc/gameover_check(mob/user)
+ var/xp_gained = 0
+ if(enemy_hp <= 0)
if(!gameover)
+ if(timer_id)
+ deltimer(timer_id)
+ timer_id = null
+ if(player_hp <= 0)
+ player_hp = 1 //let's just pretend the enemy didn't kill you so not both the player and enemy look dead.
gameover = TRUE
- temp = "[enemy_name] has fallen! Rejoice!"
- playsound(loc, 'sound/arcade/win.ogg', 50, TRUE, extrarange = -3)
+ blocked = FALSE
+ temp = "
[enemy_name] has fallen! Rejoice!"
+ playsound(loc, 'sound/arcade/win.ogg', 50, TRUE)
if(obj_flags & EMAGGED)
new /obj/effect/spawner/newbomb/timer/syndicate(loc)
@@ -133,78 +402,76 @@
log_game("[key_name(usr)] has outbombed Cuban Pete and been awarded a bomb.")
Reset()
obj_flags &= ~EMAGGED
+ xp_gained += 100
else
prizevend(user)
+ xp_gained += 50
SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("win", (obj_flags & EMAGGED ? "emagged":"normal")))
-
- else if ((obj_flags & EMAGGED) && (turtle >= 4))
- var/boomamt = rand(5,10)
- temp = "[enemy_name] throws a bomb, exploding you for [boomamt] damage!"
- playsound(loc, 'sound/arcade/boom.ogg', 50, TRUE, extrarange = -3)
- player_hp -= boomamt
-
- else if ((enemy_mp <= 5) && (prob(70)))
- var/stealamt = rand(2,3)
- temp = "[enemy_name] steals [stealamt] of your power!"
- playsound(loc, 'sound/arcade/steal.ogg', 50, TRUE, extrarange = -3)
- player_mp -= stealamt
- updateUsrDialog()
-
- if (player_mp <= 0)
- gameover = TRUE
- sleep(turn_speed)
- temp = "You have been drained! GAME OVER"
- playsound(loc, 'sound/arcade/lose.ogg', 50, TRUE, extrarange = -3)
- if(obj_flags & EMAGGED)
- usr.gib()
- SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("loss", "mana", (obj_flags & EMAGGED ? "emagged":"normal")))
-
- else if ((enemy_hp <= 10) && (enemy_mp > 4))
- temp = "[enemy_name] heals for 4 health!"
- playsound(loc, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3)
- enemy_hp += 4
- enemy_mp -= 4
-
- else
- var/attackamt = rand(3,6)
- temp = "[enemy_name] attacks for [attackamt] damage!"
- playsound(loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3)
- player_hp -= attackamt
-
- if ((player_mp <= 0) || (player_hp <= 0))
+ else if(player_hp <= 0)
+ if(timer_id)
+ deltimer(timer_id)
+ timer_id = null
gameover = TRUE
- temp = "You have been crushed! GAME OVER"
- playsound(loc, 'sound/arcade/lose.ogg', 50, TRUE, extrarange = -3)
+ temp = "
You have been crushed! GAME OVER"
+ playsound(loc, 'sound/arcade/lose.ogg', 50, TRUE)
+ xp_gained += 10//pity points
if(obj_flags & EMAGGED)
- usr.gib()
+ var/mob/living/living_user = user
+ if (istype(living_user))
+ living_user.gib()
SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("loss", "hp", (obj_flags & EMAGGED ? "emagged":"normal")))
- blocked = FALSE
- return
+ // if(gameover)
+ // user?.mind?.adjust_experience(/datum/skill/gaming, xp_gained+1)//always gain at least 1 point of XP
+
+
+///used to check if the last three move of the player are the one we want in the right order and if the passive's weakpoint has been triggered yet
+/obj/machinery/computer/arcade/battle/proc/weakpoint_check(passive,first_move,second_move,third_move)
+ if(LAZYLEN(last_three_move) < 3)
+ return FALSE
+
+ if(last_three_move[1] == first_move && last_three_move[2] == second_move && last_three_move[3] == third_move && LAZYACCESS(enemy_passive, passive))
+ LAZYREMOVE(enemy_passive, passive)
+ pissed_off++
+ return TRUE
+ else
+ return FALSE
+
+
+/obj/machinery/computer/arcade/battle/Destroy()
+ enemy_passive = null
+ weapons = null
+ last_three_move = null
+ return ..() //well boys we did it, lists are no more
/obj/machinery/computer/arcade/battle/examine_more(mob/user)
- to_chat(user, "Scribbled on the side of the Arcade Machine you notice some writing...\
- \nmagical -> >=50 power\
- \nsmart -> defend, defend, light attack\
- \nshotgun -> defend, defend, power attack\
- \nshort temper -> counter, counter, counter\
- \npoisonous -> light attack, light attack, light attack\
- \nchonker -> power attack, power attack, power attack")
- return ..()
+ var/list/msg = list("You notice some writing scribbled on the side of [src]...")
+ msg += "\tsmart -> defend, defend, light attack"
+ msg += "\tshotgun -> defend, defend, power attack"
+ msg += "\tshort temper -> counter, counter, counter"
+ msg += "\tpoisonous -> light attack, light attack, light attack"
+ msg += "\tchonker -> power attack, power attack, power attack"
+ return msg
/obj/machinery/computer/arcade/battle/emag_act(mob/user)
. = ..()
if(obj_flags & EMAGGED)
return
+
to_chat(user, "A mesmerizing Rhumba beat starts playing from the arcade machine's speakers!")
- temp = "If you die in the game, you die for real!"
- player_hp = 30
- player_mp = 10
- enemy_hp = 45
- enemy_mp = 20
+ temp = "
If you die in the game, you die for real!"
+ max_passive = 6
+ bomb_cooldown = 18
+ var/gamerSkill = 0
+ // if(usr?.mind)
+ // gamerSkill = usr.mind.get_skill_level(/datum/skill/gaming)
+ enemy_setup(gamerSkill)
+ enemy_hp += 100 //extra HP just to make cuban pete even more bullshit
+ player_hp += 30 //the player will also get a few extra HP in order to have a fucking chance
+
+ screen_setup(user)
gameover = FALSE
- blocked = FALSE
obj_flags |= EMAGGED
diff --git a/code/game/machinery/computer/arcade/minesweeper.dm b/code/game/machinery/computer/arcade/minesweeper.dm
index 3f7ef778b0..a7369348dc 100644
--- a/code/game/machinery/computer/arcade/minesweeper.dm
+++ b/code/game/machinery/computer/arcade/minesweeper.dm
@@ -268,7 +268,7 @@
visible_message("[src] dispenses [itemname]!", "You hear a chime and a clunk.")
DISABLE_BITFIELD(obj_flags, EMAGGED)
else
- var/dope_prizes = (area >= 480) ? list(ARCADE_WEIGHT_RARE) : (area >= 256) ? list(ARCADE_WEIGHT_RARE, ARCADE_WEIGHT_TRICK) : null
+ var/dope_prizes = (area >= 480) ? 6 : (area >= 256) ? 4 : 2
prizevend(user, dope_prizes)
if(game_status == MINESWEEPER_GAME_WON)
diff --git a/code/game/machinery/computer/arcade/misc_arcade.dm b/code/game/machinery/computer/arcade/misc_arcade.dm
index 50633192ce..5c887e3726 100644
--- a/code/game/machinery/computer/arcade/misc_arcade.dm
+++ b/code/game/machinery/computer/arcade/misc_arcade.dm
@@ -8,7 +8,7 @@
icon_state = "arcade"
circuit = /obj/item/circuitboard/computer/arcade/amputation
-/obj/machinery/computer/arcade/amputation/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
+/obj/machinery/computer/arcade/amputation/on_attack_hand(mob/user)
if(!iscarbon(user))
return
var/mob/living/carbon/c_user = user
@@ -24,8 +24,13 @@
var/obj/item/bodypart/chopchop = c_user.get_bodypart(which_hand)
chopchop.dismember()
qdel(chopchop)
- playsound(loc, 'sound/arcade/win.ogg', 50, TRUE, extrarange = -3)
- for(var/i=1; i<=rand(3,5); i++)
- prizevend(user)
+ // user.mind?.adjust_experience(/datum/skill/gaming, 100)
+ playsound(loc, 'sound/arcade/win.ogg', 50, TRUE)
+ prizevend(user, rand(3,5))
else
to_chat(c_user, "You (wisely) decide against putting your hand in the machine.")
+
+/obj/machinery/computer/arcade/amputation/festive //dispenses wrapped gifts instead of arcade prizes, also known as the ancap christmas tree
+ name = "Mediborg's Festive Amputation Adventure"
+ desc = "A picture of a blood-soaked medical cyborg wearing a Santa hat flashes on the screen. The mediborg has a speech bubble that says, \"Put your hand in the machine if you aren't a coward!\""
+ prize_override = list(/obj/item/a_gift/anything = 1)
diff --git a/code/game/machinery/computer/arcade/orion_trail.dm b/code/game/machinery/computer/arcade/orion_trail.dm
index 441010906c..08ce7d92e5 100644
--- a/code/game/machinery/computer/arcade/orion_trail.dm
+++ b/code/game/machinery/computer/arcade/orion_trail.dm
@@ -1,5 +1,3 @@
-
-
// *** THE ORION TRAIL ** //
#define ORION_TRAIL_WINTURN 9
@@ -15,6 +13,8 @@
#define ORION_TRAIL_COLLISION "Collision"
#define ORION_TRAIL_SPACEPORT "Spaceport"
#define ORION_TRAIL_BLACKHOLE "BlackHole"
+#define ORION_TRAIL_OLDSHIP "Old Ship"
+#define ORION_TRAIL_SEARCH "Old Ship Search"
#define ORION_STATUS_START 1
#define ORION_STATUS_NORMAL 2
@@ -44,7 +44,8 @@
ORION_TRAIL_LING = 3,
ORION_TRAIL_MALFUNCTION = 2,
ORION_TRAIL_COLLISION = 1,
- ORION_TRAIL_SPACEPORT = 2
+ ORION_TRAIL_SPACEPORT = 2,
+ ORION_TRAIL_OLDSHIP = 2
)
var/list/stops = list()
var/list/stopblurbs = list()
@@ -55,13 +56,27 @@
var/gameStatus = ORION_STATUS_START
var/canContinueEvent = 0
+ var/obj/item/radio/Radio
+ var/list/gamers = list()
+ var/killed_crew = 0
+
+
+/obj/machinery/computer/arcade/orion_trail/Initialize()
+ . = ..()
+ Radio = new /obj/item/radio(src)
+ Radio.listening = 0
+
+/obj/machinery/computer/arcade/orion_trail/Destroy()
+ QDEL_NULL(Radio)
+ return ..()
+
/obj/machinery/computer/arcade/orion_trail/kobayashi
name = "Kobayashi Maru control computer"
desc = "A test for cadets"
icon = 'icons/obj/machines/particle_accelerator.dmi'
icon_state = "control_boxp"
events = list("Raiders" = 3, "Interstellar Flux" = 1, "Illness" = 3, "Breakdown" = 2, "Malfunction" = 2, "Collision" = 1, "Spaceport" = 2)
- prizes = list(/obj/item/paper/fluff/holodeck/trek_diploma = 1)
+ prize_override = list(/obj/item/paper/fluff/holodeck/trek_diploma = 1)
settlers = list("Kirk","Worf","Gene")
/obj/machinery/computer/arcade/orion_trail/Reset()
@@ -96,14 +111,52 @@
event = null
gameStatus = ORION_STATUS_NORMAL
lings_aboard = 0
+ killed_crew = 0
//spaceport junk
spaceport_raided = 0
spaceport_freebie = 0
last_spaceport_action = ""
-/obj/machinery/computer/arcade/orion_trail/ui_interact(mob/user)
+/obj/machinery/computer/arcade/orion_trail/proc/report_player(mob/gamer)
+ if(gamers[gamer] == -2)
+ return // enough harassing them
+
+ if(gamers[gamer] == -1)
+ say("WARNING: Continued antisocial behavior detected: Dispensing self-help literature.")
+ new /obj/item/paper/pamphlet/violent_video_games(drop_location())
+ gamers[gamer]--
+ return
+
+ if(!(gamer in gamers))
+ gamers[gamer] = 0
+
+ gamers[gamer]++ // How many times the player has 'prestiged' (massacred their crew)
+
+ if(gamers[gamer] > 2 && prob(20 * gamers[gamer]))
+
+ Radio.set_frequency(FREQ_SECURITY)
+ Radio.talk_into(src, "SECURITY ALERT: Crewmember [gamer] recorded displaying antisocial tendencies in [get_area(src)]. Please watch for violent behavior.", FREQ_SECURITY)
+
+ Radio.set_frequency(FREQ_MEDICAL)
+ Radio.talk_into(src, "PSYCH ALERT: Crewmember [gamer] recorded displaying antisocial tendencies in [get_area(src)]. Please schedule psych evaluation.", FREQ_MEDICAL)
+
+ gamers[gamer] = -1
+
+ gamer.client.give_award(/datum/award/achievement/misc/gamer, gamer) // PSYCH REPORT NOTE: patient kept rambling about how they did it for an "achievement", recommend continued holding for observation
+ // gamer.mind?.adjust_experience(/datum/skill/gaming, 50) // cheevos make u better
+
+ if(!isnull(GLOB.data_core.general))
+ for(var/datum/data/record/R in GLOB.data_core.general)
+ if(R.fields["name"] == gamer.name)
+ R.fields["m_stat"] = "*Unstable*"
+ return
+
+/obj/machinery/computer/arcade/orion_trail/ui_interact(mob/_user)
. = ..()
+ if (!isliving(_user))
+ return
+ var/mob/living/user = _user
if(fuel <= 0 || food <=0 || settlers.len == 0)
gameStatus = ORION_STATUS_GAMEOVER
event = null
@@ -136,6 +189,8 @@
desc = "Learn how our ancestors got to Orion, and have fun in the process!"
dat += "
May They Rest In Peace
"
+ // user?.mind?.adjust_experience(/datum/skill/gaming, 10)//learning from your mistakes is the first rule of roguelikes
+
else if(event)
dat = eventdat
else if(gameStatus == ORION_STATUS_NORMAL)
@@ -174,20 +229,32 @@
return
busy = TRUE
+ var/gamerSkillLevel = 0
+ var/gamerSkill = 0
+ var/gamerSkillRands = 0
+
+ // if(usr?.mind)
+ // gamerSkillLevel = usr.mind.get_skill_level(/datum/skill/gaming)
+ // gamerSkill = usr.mind.get_skill_modifier(/datum/skill/gaming, SKILL_PROBS_MODIFIER)
+ // gamerSkillRands = usr.mind.get_skill_modifier(/datum/skill/gaming, SKILL_RANDS_MODIFIER)
+
+
+ var/xp_gained = 0
if (href_list["continue"]) //Continue your travels
if(gameStatus == ORION_STATUS_NORMAL && !event && turns != 7)
if(turns >= ORION_TRAIL_WINTURN)
win(usr)
+ xp_gained += 34
else
food -= (alive+lings_aboard)*2
fuel -= 5
- if(turns == 2 && prob(30))
+ if(turns == 2 && prob(30-gamerSkill))
event = ORION_TRAIL_COLLISION
event()
- else if(prob(75))
+ else if(prob(75-gamerSkill))
event = pickweight(events)
if(lings_aboard)
- if(event == ORION_TRAIL_LING || prob(55))
+ if(event == ORION_TRAIL_LING || prob(55-gamerSkill))
event = ORION_TRAIL_LING_ATTACK
event()
turns += 1
@@ -195,15 +262,18 @@
var/mob/living/carbon/M = usr //for some vars
switch(event)
if(ORION_TRAIL_RAIDERS)
- if(prob(50))
+ if(prob(50-gamerSkill))
to_chat(usr, "You hear battle shouts. The tramping of boots on cold metal. Screams of agony. The rush of venting air. Are you going insane?")
M.hallucination += 30
else
to_chat(usr, "Something strikes you from behind! It hurts like hell and feel like a blunt weapon, but nothing is there...")
M.take_bodypart_damage(30)
- playsound(loc, 'sound/weapons/genhit2.ogg', 100, 1)
+ playsound(loc, 'sound/weapons/genhit2.ogg', 100, TRUE)
if(ORION_TRAIL_ILLNESS)
- var/severity = rand(1,3) //pray to RNGesus. PRAY, PIGS
+ var/maxSeverity = 3
+ // if(gamerSkillLevel >= SKILL_LEVEL_EXPERT)
+ // maxSeverity = 2 //part of gitting gud is rng mitigation
+ var/severity = rand(1,maxSeverity) //pray to RNGesus. PRAY, PIGS
if(severity == 1)
to_chat(M, "You suddenly feel slightly nauseated." )
if(severity == 2)
@@ -215,16 +285,16 @@
sleep(30)
M.vomit(10, distance = 5)
if(ORION_TRAIL_FLUX)
- if(prob(75))
+ if(prob(75-gamerSkill))
M.DefaultCombatKnockdown(60)
say("A sudden gust of powerful wind slams [M] into the floor!")
M.take_bodypart_damage(25)
- playsound(loc, 'sound/weapons/genhit.ogg', 100, 1)
+ playsound(loc, 'sound/weapons/genhit.ogg', 100, TRUE)
else
to_chat(M, "A violent gale blows past you, and you barely manage to stay standing!")
if(ORION_TRAIL_COLLISION) //by far the most damaging event
- if(prob(90))
- playsound(loc, 'sound/effects/bang.ogg', 100, 1)
+ if(prob(90-gamerSkill))
+ playsound(loc, 'sound/effects/bang.ogg', 100, TRUE)
var/turf/open/floor/F
for(F in orange(1, src))
F.ScrapeAway()
@@ -232,15 +302,15 @@
if(hull)
sleep(10)
say("A new floor suddenly appears around [src]. What the hell?")
- playsound(loc, 'sound/weapons/genhit.ogg', 100, 1)
+ playsound(loc, 'sound/weapons/genhit.ogg', 100, TRUE)
var/turf/open/space/T
for(T in orange(1, src))
T.PlaceOnTop(/turf/open/floor/plating)
else
say("Something slams into the floor around [src] - luckily, it didn't get through!")
- playsound(loc, 'sound/effects/bang.ogg', 50, 1)
+ playsound(loc, 'sound/effects/bang.ogg', 50, TRUE)
if(ORION_TRAIL_MALFUNCTION)
- playsound(loc, 'sound/effects/empulse.ogg', 50, 1)
+ playsound(loc, 'sound/effects/empulse.ogg', 50, TRUE)
visible_message("[src] malfunctions, randomizing in-game stats!")
var/oldfood = food
var/oldfuel = fuel
@@ -254,7 +324,7 @@
audible_message("[src] lets out a somehow ominous chime.")
food = oldfood
fuel = oldfuel
- playsound(loc, 'sound/machines/chime.ogg', 50, 1)
+ playsound(loc, 'sound/machines/chime.ogg', 50, TRUE)
else if(href_list["newgame"]) //Reset everything
if(gameStatus == ORION_STATUS_START)
@@ -266,6 +336,10 @@
food = 80
fuel = 60
settlers = list("Harry","Larry","Bob")
+ else if(href_list["search"]) //search old ship
+ if(event == ORION_TRAIL_OLDSHIP)
+ event = ORION_TRAIL_SEARCH
+ event()
else if(href_list["slow"]) //slow down
if(event == ORION_TRAIL_FLUX)
food -= (alive+lings_aboard)*2
@@ -302,11 +376,11 @@
event = null
else if(href_list["blackhole"]) //keep speed past a black hole
if(turns == 7)
- if(prob(75))
+ if(prob(75-gamerSkill))
event = ORION_TRAIL_BLACKHOLE
event()
if(obj_flags & EMAGGED)
- playsound(loc, 'sound/effects/supermatter.ogg', 100, 1)
+ playsound(loc, 'sound/effects/supermatter.ogg', 100, TRUE)
say("A miniature black hole suddenly appears in front of [src], devouring [usr] alive!")
if(isliving(usr))
var/mob/living/L = usr
@@ -328,22 +402,29 @@
else if(href_list["killcrew"]) //shoot a crewmember
if(gameStatus == ORION_STATUS_NORMAL || event == ORION_TRAIL_LING)
var/sheriff = remove_crewmember() //I shot the sheriff
- playsound(loc,'sound/weapons/gunshot.ogg', 100, 1)
+ playsound(loc,'sound/weapons/gun/pistol/shot.ogg', 100, TRUE)
+ killed_crew++
+
+ var/mob/living/user = usr
if(settlers.len == 0 || alive == 0)
say("The last crewmember [sheriff], shot themselves, GAME OVER!")
if(obj_flags & EMAGGED)
- usr.death(0)
- obj_flags &= EMAGGED
+ user.death(FALSE)
gameStatus = ORION_STATUS_GAMEOVER
event = null
+
+ if(killed_crew >= 4)
+ xp_gained -= 15//no cheating by spamming game overs
+ report_player(usr)
else if(obj_flags & EMAGGED)
if(usr.name == sheriff)
say("The crew of the ship chose to kill [usr.name]!")
- usr.death(0)
+ user.death(FALSE)
if(event == ORION_TRAIL_LING) //only ends the ORION_TRAIL_LING event, since you can do this action in multiple places
event = null
+ killed_crew-- // the kill was valid
//Spaceport specific interactions
//they get a header because most of them don't reset event (because it's a shop, you leave when you want to)
@@ -356,6 +437,7 @@
fuel -= 10
food -= 10
event()
+ killed_crew-- // I mean not really but you know
else if(href_list["sellcrew"]) //sell a crewmember
if(gameStatus == ORION_STATUS_MARKET)
@@ -377,15 +459,16 @@
else if(href_list["raid_spaceport"])
if(gameStatus == ORION_STATUS_MARKET)
if(!spaceport_raided)
- var/success = min(15 * alive,100) //default crew (4) have a 60% chance
+ var/success = min(15 * alive + gamerSkill,100) //default crew (4) have a 60% chance
spaceport_raided = 1
var/FU = 0
var/FO = 0
if(prob(success))
- FU = rand(5,15)
- FO = rand(5,15)
+ FU = rand(5 + gamerSkillRands,15 + gamerSkillRands)
+ FO = rand(5 + gamerSkillRands,15 + gamerSkillRands)
last_spaceport_action = "You successfully raided the spaceport! You gained [FU] Fuel and [FO] Food! (+[FU]FU,+[FO]FO)"
+ xp_gained += 10
else
FU = rand(-5,-15)
FO = rand(-5,-15)
@@ -444,7 +527,7 @@
add_fingerprint(usr)
updateUsrDialog()
busy = FALSE
- return
+ // usr?.mind?.adjust_experience(/datum/skill/gaming, xp_gained+1)
/obj/machinery/computer/arcade/orion_trail/proc/event()
@@ -686,8 +769,279 @@
eventdat += "Depart Spaceport
"
+/obj/machinery/computer/arcade/orion_trail/proc/event()
+ eventdat = "[event]
"
+ canContinueEvent = 0
+ switch(event)
+ if(ORION_TRAIL_RAIDERS)
+ eventdat += "Raiders have come aboard your ship!"
+ if(prob(50))
+ var/sfood = rand(1,10)
+ var/sfuel = rand(1,10)
+ food -= sfood
+ fuel -= sfuel
+ eventdat += "
They have stolen [sfood] Food and [sfuel] Fuel."
+ else if(prob(10))
+ var/deadname = remove_crewmember()
+ eventdat += "
[deadname] tried to fight back, but was killed."
+ else
+ eventdat += "
Fortunately, you fended them off without any trouble."
+ eventdat += "Continue
"
+ eventdat += "Close
"
+ canContinueEvent = 1
+
+ if(ORION_TRAIL_FLUX)
+ eventdat += "This region of space is highly turbulent.
If we go slowly we may avoid more damage, but if we keep our speed we won't waste supplies."
+ eventdat += "
What will you do?"
+ eventdat += "Slow Down Continue
"
+ eventdat += "Close
"
+
+ if(ORION_TRAIL_OLDSHIP)
+ eventdat += "
Your crew spots an old ship floating through space. It might have some supplies, but then again it looks rather unsafe."
+ eventdat += "Search itLeave it
Close
"
+ canContinueEvent = 1
+
+ if(ORION_TRAIL_SEARCH)
+ switch(rand(100))
+ if(0 to 15)
+ var/rescued = add_crewmember()
+ var/oldfood = rand(1,7)
+ var/oldfuel = rand(4,10)
+ food += oldfood
+ fuel += oldfuel
+ eventdat += "
As you look through it you find some supplies and a living person!"
+ eventdat += "
[rescued] was rescued from the abandoned ship!"
+ eventdat += "
You found [oldfood] Food and [oldfuel] Fuel."
+ if(15 to 35)
+ var/lfuel = rand(4,7)
+ var/deadname = remove_crewmember()
+ fuel -= lfuel
+ eventdat += "
[deadname] was lost deep in the wreckage, and your own vessel lost [lfuel] Fuel maneuvering to the the abandoned ship."
+ if(35 to 65)
+ var/oldfood = rand(5,11)
+ food += oldfood
+ engine++
+ eventdat += "
You found [oldfood] Food and some parts amongst the wreck."
+ else
+ eventdat += "
As you look through the wreck you cannot find much of use."
+ eventdat += "Continue
"
+ eventdat += "Close
"
+ canContinueEvent = 1
+
+ if(ORION_TRAIL_ILLNESS)
+ eventdat += "A deadly illness has been contracted!"
+ var/deadname = remove_crewmember()
+ eventdat += "
[deadname] was killed by the disease."
+ eventdat += "Continue
"
+ eventdat += "Close
"
+ canContinueEvent = 1
+
+ if(ORION_TRAIL_BREAKDOWN)
+ eventdat += "Oh no! The engine has broken down!"
+ eventdat += "
You can repair it with an engine part, or you can make repairs for 3 days."
+ if(engine >= 1)
+ eventdat += "Use PartWait
"
+ else
+ eventdat += "Wait
"
+ eventdat += "Close
"
+
+ if(ORION_TRAIL_MALFUNCTION)
+ eventdat += "The ship's systems are malfunctioning!"
+ eventdat += "
You can replace the broken electronics with spares, or you can spend 3 days troubleshooting the AI."
+ if(electronics >= 1)
+ eventdat += "Use PartWait
"
+ else
+ eventdat += "Wait
"
+ eventdat += "Close
"
+
+ if(ORION_TRAIL_COLLISION)
+ eventdat += "Something hit us! Looks like there's some hull damage."
+ if(prob(25))
+ var/sfood = rand(5,15)
+ var/sfuel = rand(5,15)
+ food -= sfood
+ fuel -= sfuel
+ eventdat += "
[sfood] Food and [sfuel] Fuel was vented out into space."
+ if(prob(10))
+ var/deadname = remove_crewmember()
+ eventdat += "
[deadname] was killed by rapid depressurization."
+ eventdat += "
You can repair the damage with hull plates, or you can spend the next 3 days welding scrap together."
+ if(hull >= 1)
+ eventdat += "Use PartWait
"
+ else
+ eventdat += "Wait
"
+ eventdat += "Close
"
+
+ if(ORION_TRAIL_BLACKHOLE)
+ eventdat += "You were swept away into the black hole."
+ eventdat += "Oh...
"
+ eventdat += "Close
"
+ settlers = list()
+
+ if(ORION_TRAIL_LING)
+ eventdat += "Strange reports warn of changelings infiltrating crews on trips to Orion..."
+ if(settlers.len <= 2)
+ eventdat += "
Your crew's chance of reaching Orion is so slim the changelings likely avoided your ship..."
+ eventdat += "Continue
"
+ eventdat += "Close
"
+ if(prob(10)) // "likely", I didn't say it was guaranteed!
+ lings_aboard = min(++lings_aboard,2)
+ else
+ if(lings_aboard) //less likely to stack lings
+ if(prob(20))
+ lings_aboard = min(++lings_aboard,2)
+ else if(prob(70))
+ lings_aboard = min(++lings_aboard,2)
+
+ eventdat += "Kill a Crewmember
"
+ eventdat += "Risk it
"
+ eventdat += "Close
"
+ canContinueEvent = 1
+
+ if(ORION_TRAIL_LING_ATTACK)
+ if(lings_aboard <= 0) //shouldn't trigger, but hey.
+ eventdat += "Haha, fooled you, there are no changelings on board!"
+ eventdat += "
(You should report this to a coder :S)"
+ else
+ var/ling1 = remove_crewmember()
+ var/ling2 = ""
+ if(lings_aboard >= 2)
+ ling2 = remove_crewmember()
+
+ eventdat += "Changelings among your crew suddenly burst from hiding and attack!"
+ if(ling2)
+ eventdat += "
[ling1] and [ling2]'s arms twist and contort into grotesque blades!"
+ else
+ eventdat += "
[ling1]'s arm twists and contorts into a grotesque blade!"
+
+ var/chance2attack = alive*20
+ if(prob(chance2attack))
+ var/chancetokill = 30*lings_aboard-(5*alive) //eg: 30*2-(10) = 50%, 2 lings, 2 crew is 50% chance
+ if(prob(chancetokill))
+ var/deadguy = remove_crewmember()
+ var/murder_text = pick("The changeling[ling2 ? "s" : ""] bring[ling2 ? "" : "s"] down [deadguy] and disembowel[ling2 ? "" : "s"] them in a spray of gore!", \
+ "[ling2 ? pick(ling1, ling2) : ling1] corners [deadguy] and impales them through the stomach!", \
+ "[ling2 ? pick(ling1, ling2) : ling1] decapitates [deadguy] in a single cleaving arc!")
+ eventdat += "
[murder_text]"
+ else
+ eventdat += "
You valiantly fight off the changeling[ling2 ? "s":""]!"
+ if(ling2)
+ food += 30
+ lings_aboard = max(0,lings_aboard-2)
+ else
+ food += 15
+ lings_aboard = max(0,--lings_aboard)
+ eventdat += "
Well, it's perfectly good food...\
+
You cut the changeling[ling2 ? "s" : ""] into meat, gaining [ling2 ? "30" : "15"] Food!"
+ else
+ eventdat += "
[pick("Sensing unfavorable odds", "After a failed attack", "Suddenly breaking nerve")], \
+ the changeling[ling2 ? "s":""] vanish[ling2 ? "" : "es"] into space through the airlocks! You're safe... for now."
+ if(ling2)
+ lings_aboard = max(0,lings_aboard-2)
+ else
+ lings_aboard = max(0,--lings_aboard)
+
+ eventdat += "Continue
"
+ eventdat += "Close
"
+ canContinueEvent = 1
+
+
+ if(ORION_TRAIL_SPACEPORT)
+ gameStatus = ORION_STATUS_MARKET
+ if(spaceport_raided)
+ eventdat += "The spaceport is on high alert! You've been barred from docking by the local authorities after your failed raid."
+ if(last_spaceport_action)
+ eventdat += "
Last Spaceport Action: [last_spaceport_action]"
+ eventdat += "Depart Spaceport
"
+ eventdat += "Close
"
+ else
+ eventdat += "Your jump into the sector yields a spaceport - a lucky find!"
+ eventdat += "
This spaceport is home to travellers who failed to reach Orion, but managed to find a different home..."
+ eventdat += "
Trading terms: FU = Fuel, FO = Food"
+ if(last_spaceport_action)
+ eventdat += "
Last action: [last_spaceport_action]"
+ eventdat += "Crew:
"
+ eventdat += english_list(settlers)
+ eventdat += "
Food: [food] | Fuel: [fuel]"
+ eventdat += "
Engine Parts: [engine] | Hull Panels: [hull] | Electronics: [electronics]"
+
+
+ //If your crew is pathetic you can get freebies (provided you haven't already gotten one from this port)
+ if(!spaceport_freebie && (fuel < 20 || food < 20))
+ spaceport_freebie++
+ var/FU = 10
+ var/FO = 10
+ var/freecrew = 0
+ if(prob(30))
+ FU = 25
+ FO = 25
+
+ if(prob(10))
+ add_crewmember()
+ freecrew++
+
+ eventdat += "
The traders of the spaceport take pity on you, and generously give you some free supplies! (+[FU]FU, +[FO]FO)"
+ if(freecrew)
+ eventdat += "
You also gain a new crewmember!"
+
+ fuel += FU
+ food += FO
+
+ //CREW INTERACTIONS
+ eventdat += "Crew Management:
"
+
+ //Buy crew
+ if(food >= 10 && fuel >= 10)
+ eventdat += "Hire a New Crewmember (-10FU, -10FO)
"
+ else
+ eventdat += "You cannot afford a new crewmember.
"
+
+ //Sell crew
+ if(settlers.len > 1)
+ eventdat += "Sell Crew for Fuel and Food (+7FU, +7FO)
"
+ else
+ eventdat += "You have no other crew to sell.
"
+
+ //BUY/SELL STUFF
+ eventdat += "Spare Parts:
"
+
+ //Engine parts
+ if(fuel > 5)
+ eventdat += "Buy Engine Parts (-5FU)
"
+ else
+ eventdat += "You cannot afford engine parts."
+
+ //Hull plates
+ if(fuel > 5)
+ eventdat += "
Buy Hull Plates (-5FU)
"
+ else
+ eventdat += "You cannot afford hull plates."
+
+ //Electronics
+ if(fuel > 5)
+ eventdat += "
Buy Spare Electronics (-5FU)
"
+ else
+ eventdat += "You cannot afford spare electronics."
+
+ //Trade
+ if(fuel > 5)
+ eventdat += "
Trade Fuel for Food (-5FU,+5FO)
"
+ else
+ eventdat += "You don't have 5FU to trade.
5)
+ eventdat += "Trade Food for Fuel (+5FU,-5FO)
"
+ else
+ eventdat += "You don't have 5FO to trade.
!! Raid Spaceport !!
"
+
+ eventdat += "Depart Spaceport
"
+
+
//Add Random/Specific crewmember
-/obj/machinery/computer/arcade/orion_trail/proc/add_crewmember(var/specific = "")
+/obj/machinery/computer/arcade/orion_trail/proc/add_crewmember(specific = "")
var/newcrew = ""
if(specific)
newcrew = specific
@@ -703,7 +1057,7 @@
//Remove Random/Specific crewmember
-/obj/machinery/computer/arcade/orion_trail/proc/remove_crewmember(var/specific = "", var/dont_remove = "")
+/obj/machinery/computer/arcade/orion_trail/proc/remove_crewmember(specific = "", dont_remove = "")
var/list/safe2remove = settlers
var/removed = ""
if(dont_remove)
@@ -779,14 +1133,14 @@
to_chat(user, "You flip the switch on the underside of [src].")
active = 1
visible_message("[src] softly beeps and whirs to life!")
- playsound(loc, 'sound/machines/defib_SaftyOn.ogg', 25, 1)
+ playsound(loc, 'sound/machines/defib_SaftyOn.ogg', 25, TRUE)
say("This is ship ID #[rand(1,1000)] to Orion Port Authority. We're coming in for landing, over.")
sleep(20)
visible_message("[src] begins to vibrate...")
say("Uh, Port? Having some issues with our reactor, could you check it out? Over.")
sleep(30)
say("Oh, God! Code Eight! CODE EIGHT! IT'S GONNA BL-")
- playsound(loc, 'sound/machines/buzz-sigh.ogg', 25, 1)
+ playsound(loc, 'sound/machines/buzz-sigh.ogg', 25, TRUE)
sleep(3.6)
visible_message("[src] explodes!")
explosion(loc, 2,4,8, flame_range = 16)
diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm
index 6e270b6374..eafa03ca61 100644
--- a/code/game/objects/items/his_grace.dm
+++ b/code/game/objects/items/his_grace.dm
@@ -12,7 +12,7 @@
lefthand_file = 'icons/mob/inhands/equipment/toolbox_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/toolbox_righthand.dmi'
icon = 'icons/obj/items_and_weapons.dmi'
- w_class = WEIGHT_CLASS_BULKY
+ w_class = WEIGHT_CLASS_GIGANTIC
force = 12
total_mass = TOTAL_MASS_NORMAL_ITEM // average toolbox
attack_verb = list("robusted")
@@ -70,19 +70,19 @@
else
. += "[src] is latched closed."
-/obj/item/his_grace/relaymove(mob/living/user) //Allows changelings, etc. to climb out of Him after they revive, provided He isn't active
+/obj/item/his_grace/relaymove(mob/living/user, direction) //Allows changelings, etc. to climb out of Him after they revive, provided He isn't active
if(!awakened)
user.forceMove(get_turf(src))
user.visible_message("[user] scrambles out of [src]!", "You climb out of [src]!")
-/obj/item/his_grace/process()
+/obj/item/his_grace/process(delta_time)
if(!bloodthirst)
drowse()
return
if(bloodthirst < HIS_GRACE_CONSUME_OWNER && !ascended)
- adjust_bloodthirst(1 + FLOOR(LAZYLEN(contents) * 0.5, 1)) //Maybe adjust this?
+ adjust_bloodthirst((1 + FLOOR(LAZYLEN(contents) * 0.5, 1)) * delta_time) //Maybe adjust this?
else
- adjust_bloodthirst(1) //don't cool off rapidly once we're at the point where His Grace consumes all.
+ adjust_bloodthirst(1 * delta_time) //don't cool off rapidly once we're at the point where His Grace consumes all.
var/mob/living/master = get_atom_on_turf(src, /mob/living)
if(istype(master) && (src in master.held_items))
switch(bloodthirst)
@@ -94,7 +94,7 @@
REMOVE_TRAIT(src, TRAIT_NODROP, HIS_GRACE_TRAIT)
master.DefaultCombatKnockdown(60)
master.adjustBruteLoss(master.maxHealth)
- playsound(master, 'sound/effects/splat.ogg', 100, 0)
+ playsound(master, 'sound/effects/splat.ogg', 100, FALSE)
else
master.apply_status_effect(STATUS_EFFECT_HISGRACE)
return
@@ -115,8 +115,8 @@
if(!L.stat)
L.visible_message("[src] lunges at [L]!", "[src] lunges at you!")
do_attack_animation(L, null, src)
- playsound(L, 'sound/weapons/smash.ogg', 50, 1)
- playsound(L, 'sound/misc/desceration-01.ogg', 50, 1)
+ playsound(L, 'sound/weapons/smash.ogg', 50, TRUE)
+ playsound(L, 'sound/misc/desceration-01.ogg', 50, TRUE)
L.adjustBruteLoss(force)
adjust_bloodthirst(-5) //Don't stop attacking they're right there!
else
@@ -137,6 +137,8 @@
move_gracefully()
/obj/item/his_grace/proc/move_gracefully()
+ SIGNAL_HANDLER
+
if(!awakened)
return
var/static/list/transforms
@@ -161,7 +163,7 @@
return
var/turf/T = get_turf(src)
T.visible_message("[src] slowly stops rattling and falls still, His latch snapping shut.")
- playsound(loc, 'sound/weapons/batonextend.ogg', 100, 1)
+ playsound(loc, 'sound/weapons/batonextend.ogg', 100, TRUE)
name = initial(name)
desc = initial(desc)
icon_state = initial(icon_state)
@@ -178,8 +180,8 @@
var/victims = 0
meal.visible_message("[src] swings open and devours [meal]!", "[src] consumes you!")
meal.adjustBruteLoss(200)
- playsound(meal, 'sound/misc/desceration-02.ogg', 75, 1)
- playsound(src, 'sound/items/eatfood.ogg', 100, 1)
+ playsound(meal, 'sound/misc/desceration-02.ogg', 75, TRUE)
+ playsound(src, 'sound/items/eatfood.ogg', 100, TRUE)
meal.forceMove(src)
force_bonus += HIS_GRACE_FORCE_BONUS
prev_bloodthirst = bloodthirst
@@ -253,3 +255,4 @@
if(istype(master))
master.visible_message("Gods will be watching.")
name = "[master]'s mythical toolbox of three powers"
+ master.client?.give_award(/datum/award/achievement/misc/ascension, master)
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 07900d6bbf..f20fbc21ee 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -24,9 +24,9 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
*/
/obj/item/banhammer/attack(mob/M, mob/user)
if(user.zone_selected == BODY_ZONE_HEAD)
- M.visible_message("[user] are stroking the head of [M] with a bangammer", "[user] are stroking the head with a bangammer", "you hear a bangammer stroking a head");
+ M.visible_message("[user] are stroking the head of [M] with a bangammer.", "[user] are stroking your head with a bangammer.", "You hear a bangammer stroking a head.") // see above comment
else
- M.visible_message("[M] has been banned FOR NO REISIN by [user]", "You have been banned FOR NO REISIN by [user]", "you hear a banhammer banning someone")
+ M.visible_message("[M] has been banned FOR NO REISIN by [user]!", "You have been banned FOR NO REISIN by [user]!", "You hear a banhammer banning someone.")
playsound(loc, 'sound/effects/adminhelp.ogg', 15) //keep it at 15% volume so people don't jump out of their skin too much
if(user.a_intent != INTENT_HELP)
return ..(M, user)
@@ -89,7 +89,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander //ALL COMMENTS MADE REGARDING THIS SWORD MUST BE MADE IN ALL CAPS
desc = "THERE CAN BE ONLY ONE, AND IT WILL BE YOU!!!\nActivate it in your hand to point to the nearest victim."
flags_1 = CONDUCT_1
- item_flags = DROPDEL
+ item_flags = DROPDEL //WOW BRO YOU LOST AN ARM, GUESS WHAT YOU DONT GET YOUR SWORD ANYMORE //I CANT BELIEVE SPOOKYDONUT WOULD BREAK THE REQUIREMENTS
slot_flags = null
block_chance = 0 //RNG WON'T HELP YOU NOW, PANSY
light_range = 3
@@ -130,8 +130,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/dropped(mob/living/user)
. = ..()
user.unignore_slowdown(HIGHLANDER)
- if(!QDELETED(src))
- qdel(src) //If this ever happens, it's because you lost an arm
/obj/item/claymore/highlander/examine(mob/user)
. = ..()
@@ -141,8 +139,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/attack(mob/living/target, mob/living/user)
. = ..()
- if(!QDELETED(target) && iscarbon(target) && target.stat == DEAD && target.mind && target.mind.special_role == "highlander")
- user.fully_heal() //STEAL THE LIFE OF OUR FALLEN FOES
+ if(!QDELETED(target) && target.stat == DEAD && target.mind && target.mind.special_role == "highlander")
+ user.fully_heal(admin_revive = FALSE) //STEAL THE LIFE OF OUR FALLEN FOES
add_notch(user)
target.visible_message("[target] crumbles to dust beneath [user]'s blows!", "As you fall, your body crumbles to dust!")
target.dust()
@@ -150,9 +148,13 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/attack_self(mob/living/user)
var/closest_victim
var/closest_distance = 255
- for(var/mob/living/carbon/human/H in GLOB.player_list - user)
- if(H.client && H.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance))
- closest_victim = H
+ for(var/mob/living/carbon/human/scot in GLOB.player_list - user)
+ if(scot.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance))
+ closest_victim = scot
+ for(var/mob/living/silicon/robot/siliscot in GLOB.player_list - user)
+ if(siliscot.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance))
+ closest_victim = siliscot
+
if(!closest_victim)
to_chat(user, "[src] thrums for a moment and falls dark. Perhaps there's nobody nearby.")
return
@@ -161,7 +163,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
if((attack_type & ATTACK_TYPE_PROJECTILE) && is_energy_reflectable_projectile(object))
return BLOCK_SUCCESS | BLOCK_SHOULD_REDIRECT | BLOCK_PHYSICAL_EXTERNAL | BLOCK_REDIRECTED
- return ..()
+ return ..() //YOU THINK YOUR PUNY LASERS CAN STOP ME?
/obj/item/claymore/highlander/proc/add_notch(mob/living/user) //DYNAMIC CLAYMORE PROGRESSION SYSTEM - THIS IS THE FUTURE
notches++
@@ -214,7 +216,22 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
remove_atom_colour(ADMIN_COLOUR_PRIORITY)
name = new_name
- playsound(user, 'sound/items/screwdriver2.ogg', 50, 1)
+ playsound(user, 'sound/items/screwdriver2.ogg', 50, TRUE)
+
+/obj/item/claymore/highlander/robot //BLOODTHIRSTY BORGS NOW COME IN PLAID
+ icon = 'icons/obj/items_cyborg.dmi'
+ icon_state = "claymore_cyborg"
+ var/mob/living/silicon/robot/robot
+
+/obj/item/claymore/highlander/robot/Initialize()
+ var/obj/item/robot_module/kiltkit = loc
+ robot = kiltkit.loc
+ if(!istype(robot))
+ qdel(src)
+ return ..()
+
+/obj/item/claymore/highlander/robot/process()
+ loc.layer = LARGE_MOB_LAYER
/obj/item/katana
name = "katana"
@@ -227,7 +244,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK
force = 40
throwforce = 10
- w_class = WEIGHT_CLASS_BULKY
+ w_class = WEIGHT_CLASS_HUGE
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
block_chance = 50
@@ -417,7 +434,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
desc = "A misnomer of sorts, this is effectively a blunt katana made from steelwood, a dense organic wood derived from steelcaps. Why steelwood? Druids can use it. Duh."
icon_state = "bokken_steel"
item_state = "bokken_steel"
- force = 12
+ force = 12
stamina_damage_increment = 3
/obj/item/melee/bokken/waki
@@ -427,7 +444,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
item_state = "wakibokken"
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_NORMAL
- force = 6
+ force = 6
stamina_damage_increment = 4
block_parry_data = /datum/block_parry_data/bokken/waki
default_parry_data = /datum/block_parry_data/bokken/waki
@@ -442,7 +459,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
parry_time_perfect_leeway = 1
parry_imperfect_falloff_percent = 7.5
parry_efficiency_to_counterattack = 120
- parry_efficiency_considered_successful = 65
+ parry_efficiency_considered_successful = 65
parry_efficiency_perfect = 120
parry_efficiency_perfect_override = list(
TEXT_ATTACK_TYPE_PROJECTILE = 30,
@@ -455,10 +472,10 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/datum/block_parry_data/bokken/waki/quick_parry //For the parry spammer in you
parry_stamina_cost = 2 // Slam that parry button
parry_time_active = 2.5
- parry_time_perfect = 1
+ parry_time_perfect = 1
parry_time_perfect_leeway = 1
parry_failed_stagger_duration = 1 SECONDS
- parry_failed_clickcd_duration = 1 SECONDS
+ parry_failed_clickcd_duration = 1 SECONDS
/datum/block_parry_data/bokken/waki/quick_parry/proj
parry_efficiency_perfect_override = list()
@@ -468,7 +485,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
desc = "A misnomer of sorts, this is effectively a blunt wakizashi made from steelwood, a dense organic wood derived from steelcaps. Why steelwood? Druids can use it. Duh."
icon_state = "wakibokken_steel"
item_state = "wakibokken_steel"
- force = 8
+ force = 8
stamina_damage_increment = 2
/obj/item/melee/bokken/debug
@@ -575,7 +592,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
user.put_in_hands(S)
to_chat(user, "You fasten the glass shard to the top of the rod with the cable.")
- else if(istype(I, /obj/item/assembly/igniter) && !HAS_TRAIT(I, TRAIT_NODROP))
+ else if(istype(I, /obj/item/assembly/igniter) && !(HAS_TRAIT(I, TRAIT_NODROP)))
var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod
remove_item_from_storage(user)
@@ -598,13 +615,13 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
force = 2
- throwforce = 10 //This is never used on mobs since this has a 100% embed chance.
+ throwforce = 10 //10 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = 18 damage on hit due to guaranteed embedding
throw_speed = 4
embedding = list("pain_mult" = 4, "embed_chance" = 100, "fall_chance" = 0, "embed_chance_turf_mod" = 15)
armour_penetration = 40
w_class = WEIGHT_CLASS_SMALL
- sharpness = SHARP_EDGED
+ sharpness = SHARP_POINTY
custom_materials = list(/datum/material/iron=500, /datum/material/glass=500)
resistance_flags = FIRE_PROOF
@@ -639,27 +656,23 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
attack_verb = list("stubbed", "poked")
resistance_flags = FIRE_PROOF
var/extended = 0
- var/extended_force = 20
- var/extended_throwforce = 23
- var/extended_icon_state = "switchblade_ext"
- var/retracted_icon_state = "switchblade"
/obj/item/switchblade/attack_self(mob/user)
extended = !extended
- playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1)
+ playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, TRUE)
if(extended)
- force = extended_force
+ force = 20
w_class = WEIGHT_CLASS_NORMAL
- throwforce = extended_throwforce
- icon_state = extended_icon_state
+ throwforce = 23
+ icon_state = "switchblade_ext"
attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = SHARP_EDGED
else
- force = initial(force)
+ force = 3
w_class = WEIGHT_CLASS_SMALL
- throwforce = initial(throwforce)
- icon_state = retracted_icon_state
+ throwforce = 5
+ icon_state = "switchblade"
attack_verb = list("stubbed", "poked")
hitsound = 'sound/weapons/genhit.ogg'
sharpness = SHARP_NONE
@@ -750,6 +763,10 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
user.visible_message("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the astral plane!")
return (OXYLOSS)
+// /obj/item/ectoplasm/angelic
+// icon = 'icons/obj/wizard.dmi'
+// icon_state = "angelplasm"
+
/obj/item/mounted_chainsaw
name = "mounted chainsaw"
desc = "A chainsaw that has replaced your arm."
@@ -758,7 +775,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi'
item_flags = ABSTRACT | DROPDEL
- w_class = WEIGHT_CLASS_BULKY
+ w_class = WEIGHT_CLASS_HUGE
force = 24
throwforce = 0
throw_range = 0
@@ -801,7 +818,13 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/statuebust/Initialize()
. = ..()
AddElement(/datum/element/art, impressiveness)
- addtimer(CALLBACK(src, /datum.proc/_AddElement, list(/datum/element/beauty, 1000)), 0)
+ // AddComponent(/datum/component/beauty, 1000)
+
+// /obj/item/statuebust/hippocratic
+// name = "hippocrates bust"
+// desc = "A bust of the famous Greek physician Hippocrates of Kos, often referred to as the father of western medicine."
+// icon_state = "hippocratic"
+// impressiveness = 50
/obj/item/tailclub
name = "tail club"
@@ -825,8 +848,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
icon_state = "catwhip"
/obj/item/melee/skateboard
- name = "improvised skateboard"
- desc = "A skateboard. It can be placed on its wheels and ridden, or used as a strong weapon."
+ name = "skateboard"
+ desc = "A skateboard. It can be placed on its wheels and ridden, or used as a radical weapon."
icon_state = "skateboard"
item_state = "skateboard"
force = 12
@@ -839,17 +862,22 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/melee/skateboard/attack_self(mob/user)
if(!user.canUseTopic(src, TRUE, FALSE, TRUE))
return
- var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user))
+ var/obj/vehicle/ridden/scooter/skateboard/S = new board_item_type(get_turf(user))//this probably has fucky interactions with telekinesis but for the record it wasn't my fault
S.buckle_mob(user)
qdel(src)
+/obj/item/melee/skateboard/improvised
+ name = "improvised skateboard"
+ desc = "A jury-rigged skateboard. It can be placed on its wheels and ridden, or used as a radical weapon."
+ // board_item_type = /obj/vehicle/ridden/scooter/skateboard/improvised
+
/obj/item/melee/skateboard/pro
name = "skateboard"
- desc = "A RaDSTORMz brand professional skateboard. It looks sturdy and well made."
+ desc = "An EightO brand professional skateboard. It looks sturdy and well made."
icon_state = "skateboard2"
item_state = "skateboard2"
board_item_type = /obj/vehicle/ridden/scooter/skateboard/pro
- custom_premium_price = 500
+ custom_premium_price = PAYCHECK_HARD * 5
/obj/item/melee/skateboard/hoverboard
name = "hoverboard"
@@ -857,10 +885,10 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
icon_state = "hoverboard_red"
item_state = "hoverboard_red"
board_item_type = /obj/vehicle/ridden/scooter/skateboard/hoverboard
- custom_premium_price = 2015
+ custom_premium_price = PAYCHECK_COMMAND * 5.4 //If I can't make it a meme I'll make it RAD
/obj/item/melee/skateboard/hoverboard/admin
- name = "\improper Board Of Directors"
+ name = "Board Of Directors"
desc = "The engineering complexity of a spaceship concentrated inside of a board. Just as expensive, too."
icon_state = "hoverboard_nt"
item_state = "hoverboard_nt"
@@ -879,11 +907,19 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
throwforce = 12
attack_verb = list("beat", "smacked")
custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 3.5)
- w_class = WEIGHT_CLASS_BULKY
+ w_class = WEIGHT_CLASS_HUGE
var/homerun_ready = 0
var/homerun_able = 0
total_mass = 2.7 //a regular wooden major league baseball bat weighs somewhere between 2 to 3.4 pounds, according to google
+/obj/item/melee/baseball_bat/Initialize()
+ . = ..()
+ if(prob(1))
+ name = "cricket bat"
+ desc = "You've got red on you."
+ icon_state = "baseball_bat_brit"
+ item_state = "baseball_bat_brit"
+
/obj/item/melee/baseball_bat/chaplain
name = "blessed baseball bat"
desc = "There ain't a cult in the league that can withstand a swatter."
@@ -908,11 +944,11 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
..()
return
if(homerun_ready)
- to_chat(user, "You're already ready to do a home run!")
+ to_chat(user, "You're already ready to do a home run!")
..()
return
to_chat(user, "You begin gathering strength...")
- playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, 1)
+ playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, TRUE)
if(do_after(user, 90, target = src))
to_chat(user, "You gather power! Time for a home run!")
homerun_ready = 1
@@ -920,12 +956,14 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/melee/baseball_bat/attack(mob/living/target, mob/living/user)
. = ..()
+ if(HAS_TRAIT(user, TRAIT_PACIFISM))
+ return
var/atom/throw_target = get_edge_target_turf(target, user.dir)
if(homerun_ready)
user.visible_message("It's a home run!")
target.throw_at(throw_target, rand(8,10), 14, user)
target.ex_act(EXPLODE_HEAVY)
- playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, 1)
+ playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, TRUE)
homerun_ready = 0
return
else if(!target.anchored)
@@ -956,7 +994,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/melee/flyswatter
name = "flyswatter"
- desc = "Useful for killing insects of all sizes."
+ desc = "Useful for killing pests of all sizes."
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "flyswatter"
item_state = "flyswatter"
@@ -969,7 +1007,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
w_class = WEIGHT_CLASS_SMALL
//Things in this list will be instantly splatted. Flyman weakness is handled in the flyman species weakness proc.
var/list/strong_against
- var/list/spider_panic
/obj/item/melee/flyswatter/Initialize()
. = ..()
@@ -977,13 +1014,11 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/mob/living/simple_animal/hostile/poison/bees/,
/mob/living/simple_animal/butterfly,
/mob/living/simple_animal/cockroach,
- /obj/item/queen_bee
- ))
- spider_panic = typecacheof(list(
- /mob/living/simple_animal/banana_spider,
- /mob/living/simple_animal/hostile/poison/giant_spider,
+ /obj/item/queen_bee,
+ /obj/structure/spider/spiderling
))
+
/obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag)
. = ..()
if(proximity_flag)
@@ -993,11 +1028,6 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
if(istype(target, /mob/living/))
var/mob/living/bug = target
bug.death(1)
- if(is_type_in_typecache(target, spider_panic))
- to_chat(user, "You easily land a critical blow on the [target].")
- if(istype(target, /mob/living/))
- var/mob/living/bug = target
- bug.adjustBruteLoss(35) //What kinda mad man would go into melee with a spider?!
else
qdel(target)
@@ -1007,7 +1037,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
icon_state = "madeyoulook"
force = 0
throwforce = 0
- item_flags = DROPDEL | ABSTRACT
+ item_flags = DROPDEL | ABSTRACT // | HAND_ITEM
attack_verb = list("bopped")
/obj/item/circlegame/Initialize()
@@ -1030,6 +1060,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/// Stage 1: The mistake is made
/obj/item/circlegame/proc/ownerExamined(mob/living/owner, mob/living/sucker)
+ SIGNAL_HANDLER
+
if(!istype(sucker) || !in_range(owner, sucker))
return
addtimer(CALLBACK(src, .proc/waitASecond, owner, sucker), 4)
@@ -1076,6 +1108,10 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
to_chat(owner, "[sucker] looks down at your [src.name] before trying to avert [sucker.p_their()] eyes, but it's too late!")
to_chat(sucker, "[owner] sees the fear in your eyes as you try to look away from [owner.p_their()] [src.name]!")
+ owner.face_atom(sucker)
+ if(owner.client)
+ owner.client.give_award(/datum/award/achievement/misc/gottem, owner) // then everybody clapped
+
playsound(get_turf(owner), 'sound/effects/hit_punch.ogg', 50, TRUE, -1)
owner.do_attack_animation(sucker)
@@ -1103,7 +1139,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
item_state = "nothing"
force = 0
throwforce = 0
- item_flags = DROPDEL | ABSTRACT
+ item_flags = DROPDEL | ABSTRACT // | HAND_ITEM
attack_verb = list("slapped")
hitsound = 'sound/effects/snap.ogg'
@@ -1112,16 +1148,12 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
var/mob/living/carbon/human/L = M
if(L && L.dna && L.dna.species)
L.dna.species.stop_wagging_tail(M)
- if(user.a_intent != INTENT_HARM && ((user.zone_selected == BODY_ZONE_PRECISE_MOUTH) || (user.zone_selected == BODY_ZONE_PRECISE_EYES) || (user.zone_selected == BODY_ZONE_HEAD)))
- user.do_attack_animation(M)
- playsound(M, 'sound/weapons/slap.ogg', 50, 1, -1)
- user.visible_message("[user] slaps [M]!",
- "You slap [M]!",\
- "You hear a slap.")
- return
- else
- ..()
-
+ user.do_attack_animation(M)
+ playsound(M, 'sound/weapons/slap.ogg', 50, TRUE, -1)
+ user.visible_message("[user] slaps [M]!",
+ "You slap [M]!",\
+ "You hear a slap.")
+ return
/obj/item/proc/can_trigger_gun(mob/living/user)
if(!user.can_use_guns(src))
return FALSE
@@ -1138,6 +1170,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
force = 0
throwforce = 5
reach = 2
+ var/min_reach = 2
/obj/item/extendohand/acme
name = "\improper ACME Extendo-Hand"
@@ -1145,13 +1178,26 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/extendohand/attack(atom/M, mob/living/carbon/human/user)
var/dist = get_dist(M, user)
- if(dist < reach)
+ if(dist < min_reach)
to_chat(user, "[M] is too close to use [src] on.")
return
M.attack_hand(user)
-//HF blade
+// /obj/item/gohei
+// name = "gohei"
+// desc = "A wooden stick with white streamers at the end. Originally used by shrine maidens to purify things. Now used by the station's valued weeaboos."
+// force = 5
+// throwforce = 5
+// hitsound = "swing_hit"
+// attack_verb_continuous = list("whacks", "thwacks", "wallops", "socks")
+// attack_verb_simple = list("whack", "thwack", "wallop", "sock")
+// icon = 'icons/obj/items_and_weapons.dmi'
+// icon_state = "gohei"
+// inhand_icon_state = "gohei"
+// lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
+// righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
+//HF blade
/obj/item/vibro_weapon
icon_state = "hfrequency0"
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
@@ -1160,6 +1206,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
desc = "A potent weapon capable of cutting through nearly anything. Wielding it in two hands will allow you to deflect gunfire."
armour_penetration = 100
block_chance = 40
+ force = 20
throwforce = 20
throw_speed = 4
sharpness = SHARP_EDGED
@@ -1182,10 +1229,14 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/// triggered on wield of two handed item
/obj/item/vibro_weapon/proc/on_wield(obj/item/source, mob/user)
+ SIGNAL_HANDLER
+
wielded = TRUE
/// triggered on unwield of two handed item
/obj/item/vibro_weapon/proc/on_unwield(obj/item/source, mob/user)
+ SIGNAL_HANDLER
+
wielded = FALSE
/obj/item/vibro_weapon/update_icon_state()
diff --git a/code/game/objects/structures/lavaland/necropolis_tendril.dm b/code/game/objects/structures/lavaland/necropolis_tendril.dm
index edc4f0c91f..67341160de 100644
--- a/code/game/objects/structures/lavaland/necropolis_tendril.dm
+++ b/code/game/objects/structures/lavaland/necropolis_tendril.dm
@@ -52,12 +52,12 @@ GLOBAL_LIST_INIT(tendrils, list())
last_tendril = FALSE
if(last_tendril && !(flags_1 & ADMIN_SPAWNED_1))
- if(SSmedals.hub_enabled)
+ if(SSachievements.achievements_enabled)
for(var/mob/living/L in view(7,src))
if(L.stat || !L.client)
continue
- SSmedals.UnlockMedal("[BOSS_MEDAL_TENDRIL] [ALL_KILL_MEDAL]", L.client)
- SSmedals.SetScore(TENDRIL_CLEAR_SCORE, L.client, 1)
+ L.client.give_award(/datum/award/achievement/boss/tendril_exterminator, L)
+ L.client.give_award(/datum/award/score/tendril_score, L) //Progresses score by one
GLOB.tendrils -= src
QDEL_NULL(emitted_light)
QDEL_NULL(gps)
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 2214f9e4bb..23900e8913 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -47,14 +47,19 @@
if(open)
GM.visible_message("[user] starts to give [GM] a swirlie!", "[user] starts to give you a swirlie...")
swirlie = GM
- if(do_after(user, 30, 0, target = src))
- GM.visible_message("[user] gives [GM] a swirlie!", "[user] gives you a swirlie!", "You hear a toilet flushing.")
+ var/was_alive = (swirlie.stat != DEAD)
+ if(do_after(user, 3 SECONDS, target = src, timed_action_flags = IGNORE_HELD_ITEM))
+ GM.visible_message("[user] gives [GM] a swirlie!", "[user] gives you a swirlie!", "You hear a toilet flushing.")
if(iscarbon(GM))
var/mob/living/carbon/C = GM
if(!C.internal)
+ log_combat(user, C, "swirlied (oxy)")
C.adjustOxyLoss(5)
else
+ log_combat(user, GM, "swirlied (oxy)")
GM.adjustOxyLoss(5)
+ if(was_alive && swirlie.stat == DEAD && swirlie.client)
+ swirlie.client.give_award(/datum/award/achievement/misc/swirlie, swirlie) // just like space high school all over again!
swirlie = null
else
playsound(src.loc, 'sound/effects/bang.ogg', 25, 1)
diff --git a/code/game/turfs/simulated/minerals.dm b/code/game/turfs/simulated/minerals.dm
index 0f1ec6fa85..e75c7dde55 100644
--- a/code/game/turfs/simulated/minerals.dm
+++ b/code/game/turfs/simulated/minerals.dm
@@ -872,20 +872,18 @@
/turf/closed/mineral/strong/gets_drilled(mob/user)
if(!ishuman(user))
return // see attackby
- /*
var/mob/living/carbon/human/H = user
- if(!(H.mind.get_skill_level(/datum/skill/mining) >= SKILL_LEVEL_MASTER))
- return
- */
+ // if(!(H.mind?.get_skill_level(/datum/skill/mining) >= SKILL_LEVEL_MASTER))
+ // return
drop_ores()
-// H.client.give_award(/datum/award/achievement/skill/legendary_miner, H)
+ H.client.give_award(/datum/award/achievement/skill/legendary_miner, H)
var/flags = NONE
if(defer_change) // TODO: make the defer change var a var for any changeturf flag
flags = CHANGETURF_DEFER_CHANGE
ScrapeAway(flags=flags)
addtimer(CALLBACK(src, .proc/AfterChange), 1, TIMER_UNIQUE)
playsound(src, 'sound/effects/break_stone.ogg', 50, TRUE) //beautiful destruction
-// H.mind.adjust_experience(/datum/skill/mining, 100) //yay!
+ // H.mind?.adjust_experience(/datum/skill/mining, 100) //yay!
/turf/closed/mineral/strong/proc/drop_ores()
if(prob(10))
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index e2c12353f7..43feed2811 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -6,13 +6,21 @@ GLOBAL_PROTECT(admin_verbs_default)
return list(
/client/proc/deadmin, /*destroys our own admin datum so we can play as a regular player*/
/client/proc/cmd_admin_say, /*admin-only ooc chat*/
- /client/proc/dsay, /*talk in deadchat using our ckey/fakekey*/
- /client/proc/deadchat,
- /client/proc/investigate_show, /*various admintools for investigation. Such as a singulo grief-log*/
+ /client/proc/hide_verbs, /*hides all our adminverbs*/
+ /client/proc/hide_most_verbs, /*hides all our hideable adminverbs*/
/client/proc/debug_variables, /*allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify*/
- /client/proc/toggleprayers,
- /client/proc/toggleadminhelpsound,
- /client/proc/debugstatpanel,
+ /client/proc/dsay, /*talk in deadchat using our ckey/fakekey*/
+ /client/proc/investigate_show, /*various admintools for investigation. Such as a singulo grief-log*/
+ /client/proc/secrets,
+ /client/proc/toggle_hear_radio, /*allows admins to hide all radio output*/
+ /client/proc/reload_admins,
+ /client/proc/reestablish_db_connection, /*reattempt a connection to the database*/
+ /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/
+ /client/proc/cmd_admin_pm_panel, /*admin-pm list*/
+ /client/proc/stop_sounds,
+ /client/proc/mark_datum_mapview,
+ /client/proc/debugstatpanel
+ // /client/proc/fix_air /*resets air in designated radius to its default atmos composition*/
)
GLOBAL_LIST_INIT(admin_verbs_admin, world.AVerbsAdmin())
GLOBAL_PROTECT(admin_verbs_admin)
@@ -24,6 +32,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/datum/verbs/menu/Admin/verb/playerpanel,
/client/proc/game_panel, /*game panel, allows to change game-mode etc*/
/client/proc/check_ai_laws, /*shows AI and borg laws*/
+ // /client/proc/ghost_pool_protection, /*opens a menu for toggling ghost roles*/
/datum/admins/proc/toggleooc, /*toggles ooc on/off for everyone*/
/datum/admins/proc/toggleooclocal, /*toggles looc on/off for everyone*/
/datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/
@@ -35,15 +44,15 @@ GLOBAL_PROTECT(admin_verbs_admin)
/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/getserverlogs, /*for accessing server logs*/
- /client/proc/cmd_admin_subtle_message, /*send an message to somebody as a 'voice in their head'*/
- /client/proc/cmd_admin_headset_message, /*send an message to somebody through their headset as CentCom*/
+ /client/proc/getcurrentlogs, /*for accessing server logs for the current round*/
+ /client/proc/cmd_admin_subtle_message, /*send a message to somebody as a 'voice in their head'*/
+ /client/proc/cmd_admin_headset_message, /*send a message to somebody through their headset as CentCom*/
/client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/
/client/proc/cmd_admin_check_contents, /*displays the contents of an instance*/
/client/proc/centcom_podlauncher,/*Open a window to launch a Supplypod and configure it or it's contents*/
/client/proc/check_antagonists, /*shows all antags*/
/datum/admins/proc/access_news_network, /*allows access of newscasters*/
/client/proc/jumptocoord, /*we ghost and jump to a coordinate*/
- /client/proc/getcurrentlogs, /*for accessing server logs for the current round*/
/client/proc/Getmob, /*teleports a mob to our location*/
/client/proc/Getkey, /*teleports a mob with a certain ckey to our location*/
// /client/proc/sendmob, /*sends a mob somewhere*/ -Removed due to it needing two sorting procs to work, which were executed every time an admin right-clicked. ~Errorage
@@ -53,6 +62,8 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/jumptoturf, /*allows us to jump to a specific turf*/
/client/proc/admin_call_shuttle, /*allows us to call the emergency shuttle*/
/client/proc/admin_cancel_shuttle, /*allows us to cancel the emergency shuttle, sending it back to centcom*/
+ // /client/proc/admin_disable_shuttle, /*allows us to disable the emergency shuttle admin-wise so that it cannot be called*/
+ // /client/proc/admin_enable_shuttle, /*undoes the above*/
/client/proc/cmd_admin_direct_narrate, /*send text directly to a player with no padding. Useful for narratives and fluff-text*/
/client/proc/cmd_admin_world_narrate, /*sends text to all players with no padding*/
/client/proc/cmd_admin_local_narrate, /*sends text to all mobs within view of atom*/
@@ -64,26 +75,18 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/toggle_combo_hud, // toggle display of the combination pizza antag and taco sci/med/eng hud
/client/proc/toggle_AI_interact, /*toggle admin ability to interact with machines as an AI*/
/datum/admins/proc/open_shuttlepanel, /* Opens shuttle manipulator UI */
+ /client/proc/deadchat,
+ /client/proc/toggleprayers,
+ // /client/proc/toggle_prayer_sound,
+ // /client/proc/colorasay,
+ // /client/proc/resetasaycolor,
+ /client/proc/toggleadminhelpsound,
/client/proc/respawn_character,
- /client/proc/secrets,
- /client/proc/toggle_hear_radio, /*allows admins to hide all radio output*/
- /client/proc/reload_admins,
- /client/proc/reestablish_db_connection, /*reattempt a connection to the database*/
- /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/
- /client/proc/cmd_admin_pm_panel, /*admin-pm list*/
- /client/proc/panicbunker,
- /client/proc/addbunkerbypass,
- /client/proc/revokebunkerbypass,
- /client/proc/stop_sounds,
- /client/proc/mark_datum_mapview,
- /client/proc/hide_verbs, /*hides all our adminverbs*/
- /client/proc/hide_most_verbs, /*hides all our hideable adminverbs*/
- /datum/admins/proc/open_borgopanel,
- /client/proc/admin_cmd_respawn_return_to_lobby,
- /client/proc/admin_cmd_remove_ghost_respawn_timer
+ /client/proc/admin_cmd_respawn_return_to_lobby, //CIT
+ /client/proc/admin_cmd_remove_ghost_respawn_timer, //CIT
+ /datum/admins/proc/open_borgopanel
)
-
-GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/DB_ban_panel, /client/proc/stickybanpanel))
+GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/DB_ban_panel, /client/proc/stickybanpanel))
GLOBAL_PROTECT(admin_verbs_ban)
GLOBAL_LIST_INIT(admin_verbs_sounds, list(/client/proc/play_local_sound, /client/proc/play_sound, /client/proc/manual_play_web_sound, /client/proc/set_round_end_sound))
GLOBAL_PROTECT(admin_verbs_sounds)
@@ -110,13 +113,14 @@ GLOBAL_LIST_INIT(admin_verbs_fun, list(
/client/proc/show_tip,
/client/proc/smite,
/client/proc/admin_away,
- /client/proc/cmd_admin_toggle_fov,
+ /client/proc/cmd_admin_toggle_fov, //CIT CHANGE - FOV
/client/proc/roll_dices //CIT CHANGE - Adds dice verb
))
GLOBAL_PROTECT(admin_verbs_fun)
GLOBAL_LIST_INIT(admin_verbs_spawn, list(/datum/admins/proc/spawn_atom, /datum/admins/proc/podspawn_atom, /datum/admins/proc/spawn_cargo, /datum/admins/proc/spawn_objasmob, /client/proc/respawn_character))
GLOBAL_PROTECT(admin_verbs_spawn)
GLOBAL_LIST_INIT(admin_verbs_server, world.AVerbsServer())
+GLOBAL_PROTECT(admin_verbs_server)
/world/proc/AVerbsServer()
return list(
/datum/admins/proc/startnow,
@@ -126,17 +130,22 @@ GLOBAL_LIST_INIT(admin_verbs_server, world.AVerbsServer())
/datum/admins/proc/toggleaban,
/client/proc/everyone_random,
/datum/admins/proc/toggleAI,
- /datum/admins/proc/toggleMulticam,
- /datum/admins/proc/toggledynamicvote,
+ /datum/admins/proc/toggleMulticam, //CIT
+ /datum/admins/proc/toggledynamicvote, //CIT
/client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/
/client/proc/cmd_debug_del_all,
/client/proc/toggle_random_events,
/client/proc/forcerandomrotate,
/client/proc/adminchangemap,
- /client/proc/toggle_hub
+ /client/proc/panicbunker,
+ /client/proc/addbunkerbypass, //CIT
+ /client/proc/revokebunkerbypass, //CIT
+ // /client/proc/toggle_interviews,
+ /client/proc/toggle_hub,
+ /client/proc/toggle_cdn
)
-GLOBAL_PROTECT(admin_verbs_server)
GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug())
+GLOBAL_PROTECT(admin_verbs_debug)
/world/proc/AVerbsDebug()
return list(
/client/proc/restart_controller,
@@ -174,27 +183,37 @@ GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug())
/client/proc/cmd_display_init_log,
/client/proc/cmd_display_overlay_log,
/client/proc/reload_configuration,
+ // /client/proc/atmos_control,
+ // /client/proc/reload_cards,
+ // /client/proc/validate_cards,
+ // /client/proc/test_cardpack_distribution,
+ // /client/proc/print_cards,
+ #ifdef TESTING
+ /client/proc/check_missing_sprites,
+ #endif
/datum/admins/proc/create_or_modify_area,
#ifdef REFERENCE_TRACKING
/datum/admins/proc/view_refs,
/datum/admins/proc/view_del_failures,
#endif
- /client/proc/generate_wikichem_list, //DO NOT PRESS UNLESS YOU WANT SUPERLAG
+ // /client/proc/check_timer_sources,
+ /client/proc/toggle_cdn,
+ /client/proc/generate_wikichem_list //DO NOT PRESS UNLESS YOU WANT SUPERLAG
)
-GLOBAL_PROTECT(admin_verbs_debug)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
GLOBAL_PROTECT(admin_verbs_possess)
GLOBAL_LIST_INIT(admin_verbs_permissions, list(/client/proc/edit_admin_permissions))
GLOBAL_PROTECT(admin_verbs_permissions)
GLOBAL_LIST_INIT(admin_verbs_poll, list(/client/proc/create_poll))
+GLOBAL_PROTECT(admin_verbs_poll)
//verbs which can be hidden - needs work
-GLOBAL_PROTECT(admin_verbs_poll)
GLOBAL_LIST_INIT(admin_verbs_hideable, list(
/client/proc/set_ooc,
/client/proc/reset_ooc,
/client/proc/deadmin,
/datum/admins/proc/show_traitor_panel,
+ // /datum/admins/proc/show_skill_panel,
/datum/admins/proc/toggleenter,
/datum/admins/proc/toggleguests,
/datum/admins/proc/announce,
@@ -239,7 +258,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list(
/client/proc/Debug2,
/client/proc/reload_admins,
/client/proc/cmd_debug_make_powernets,
- /client/proc/startSinglo,
+ /client/proc/startSinglo, // tg removed this
/client/proc/cmd_debug_mob_lists,
/client/proc/cmd_debug_del_all,
/client/proc/enable_debug_verbs,
@@ -247,8 +266,9 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list(
/proc/release,
/client/proc/reload_admins,
/client/proc/panicbunker,
- /client/proc/addbunkerbypass,
- /client/proc/revokebunkerbypass,
+ /client/proc/addbunkerbypass, //CIT
+ /client/proc/revokebunkerbypass, //CIT
+ // /client/proc/toggle_interviews,
/client/proc/admin_change_sec_level,
/client/proc/toggle_nuke,
/client/proc/cmd_display_del_log,
@@ -322,7 +342,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
remove_verb(src, /client/proc/hide_most_verbs)
add_verb(src, /client/proc/show_verbs)
- to_chat(src, "Most of your adminverbs have been hidden.")
+ to_chat(src, "Most of your adminverbs have been hidden.", confidential = TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Hide Most Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return
@@ -333,7 +353,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
remove_admin_verbs()
add_verb(src, /client/proc/show_verbs)
- to_chat(src, "Almost all of your adminverbs have been hidden.")
+ to_chat(src, "Almost all of your adminverbs have been hidden.", confidential = TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Hide All Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return
@@ -344,7 +364,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
remove_verb(src, /client/proc/show_verbs)
add_admin_verbs()
- to_chat(src, "All of your adminverbs are now visible.")
+ to_chat(src, "All of your adminverbs are now visible.", confidential = TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -354,7 +374,8 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set category = "Admin.Game"
set name = "Aghost"
if(!holder)
- return FALSE
+ return
+ . = TRUE
if(isobserver(mob))
//re-enter
var/mob/dead/observer/ghost = mob
@@ -367,7 +388,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
ghost.reenter_corpse()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin Reenter") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
else if(isnewplayer(mob))
- to_chat(src, "Error: Aghost: Can't admin-ghost whilst in the lobby. Join or Observe first.")
+ to_chat(src, "Error: Aghost: Can't admin-ghost whilst in the lobby. Join or Observe first.", confidential = TRUE)
return FALSE
else
//ghostize
@@ -375,10 +396,10 @@ GLOBAL_PROTECT(admin_verbs_hideable)
message_admins("[key_name_admin(usr)] admin ghosted.")
var/mob/body = mob
body.ghostize(1, voluntary = TRUE)
+ init_verbs()
if(body && !body.key)
body.key = "@[key]" //Haaaaaaaack. But the people have spoken. If it breaks; blame adminbus
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin Ghost") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- return TRUE
/client/proc/invisimin()
set name = "Invisimin"
@@ -387,10 +408,10 @@ GLOBAL_PROTECT(admin_verbs_hideable)
if(holder && mob)
if(mob.invisibility == INVISIBILITY_OBSERVER)
mob.invisibility = initial(mob.invisibility)
- to_chat(mob, "Invisimin off. Invisibility reset.")
+ to_chat(mob, "Invisimin off. Invisibility reset.", confidential = TRUE)
else
mob.invisibility = INVISIBILITY_OBSERVER
- to_chat(mob, "Invisimin on. You are now as invisible as a ghost.")
+ to_chat(mob, "Invisimin on. You are now as invisible as a ghost.", confidential = TRUE)
/client/proc/check_antagonists()
set name = "Check Antagonists"
@@ -402,15 +423,21 @@ GLOBAL_PROTECT(admin_verbs_hideable)
message_admins("[key_name_admin(usr)] checked antagonists.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Antagonists") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/client/proc/unban_panel()
- set name = "Unban Panel"
- set category = "Admin"
- if(holder)
- if(CONFIG_GET(flag/ban_legacy_system))
- holder.unbanpanel()
- else
- holder.DB_ban_panel()
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Unban Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+// /client/proc/ban_panel()
+// set name = "Banning Panel"
+// set category = "Admin"
+// if(!check_rights(R_BAN))
+// return
+// holder.ban_panel()
+// SSblackbox.record_feedback("tally", "admin_verb", 1, "Banning Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+
+// /client/proc/unban_panel()
+// set name = "Unbanning Panel"
+// set category = "Admin"
+// if(!check_rights(R_BAN))
+// return
+// holder.unban_panel()
+// SSblackbox.record_feedback("tally", "admin_verb", 1, "Unbanning Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/game_panel()
set name = "Game Panel"
@@ -419,13 +446,13 @@ GLOBAL_PROTECT(admin_verbs_hideable)
holder.Game()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Game Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/client/proc/secrets()
- set name = "Secrets"
- set category = "Admin.Game"
- if (holder)
- holder.Secrets()
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Secrets Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
+// /client/proc/poll_panel()
+// set name = "Server Poll Management"
+// set category = "Admin"
+// if(!check_rights(R_POLL))
+// return
+// holder.poll_list_panel()
+// SSblackbox.record_feedback("tally", "admin_verb", 1, "Server Poll Management") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/findStealthKey(txt)
if(txt)
@@ -457,7 +484,10 @@ GLOBAL_PROTECT(admin_verbs_hideable)
if(isobserver(mob))
mob.invisibility = initial(mob.invisibility)
mob.alpha = initial(mob.alpha)
- mob.name = initial(mob.name)
+ if(mob.mind)
+ mob.name = mob.mind.name
+ else
+ mob.name = mob.real_name
mob.mouse_opacity = initial(mob.mouse_opacity)
else
var/new_key = ckeyEx(stripped_input(usr, "Enter your desired display name.", "Fake Key", key, 26))
@@ -485,7 +515,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
switch(choice)
if(null)
- return 0
+ return
if("Small Bomb (1, 2, 3, 3)")
explosion(epicenter, 1, 2, 3, 3, TRUE, TRUE)
if("Medium Bomb (2, 3, 4, 4)")
@@ -538,7 +568,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
if (isnull(ex_power))
return
var/range = round((2 * ex_power)**GLOB.DYN_EX_SCALE)
- to_chat(usr, "Estimated Explosive Range: (Devastation: [round(range*0.25)], Heavy: [round(range*0.5)], Light: [round(range)])")
+ to_chat(usr, "Estimated Explosive Range: (Devastation: [round(range*0.25)], Heavy: [round(range*0.5)], Light: [round(range)])", confidential = TRUE)
/client/proc/get_dynex_power()
set category = "Debug"
@@ -549,7 +579,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
if (isnull(ex_range))
return
var/power = (0.5 * ex_range)**(1/GLOB.DYN_EX_SCALE)
- to_chat(usr, "Estimated Explosive Power: [power]")
+ to_chat(usr, "Estimated Explosive Power: [power]", confidential = TRUE)
/client/proc/set_dynex_scale()
set category = "Debug"
@@ -563,6 +593,55 @@ GLOBAL_PROTECT(admin_verbs_hideable)
log_admin("[key_name(usr)] has modified Dynamic Explosion Scale: [ex_scale]")
message_admins("[key_name_admin(usr)] has modified Dynamic Explosion Scale: [ex_scale]")
+// /client/proc/atmos_control()
+// set name = "Atmos Control Panel"
+// set category = "Debug"
+// if(!check_rights(R_DEBUG))
+// return
+// SSair.ui_interact(mob)
+
+// /client/proc/reload_cards()
+// set name = "Reload Cards"
+// set category = "Debug"
+// if(!check_rights(R_DEBUG))
+// return
+// if(!SStrading_card_game.loaded)
+// message_admins("The card subsystem is not currently loaded")
+// return
+// reloadAllCardFiles(SStrading_card_game.card_files, SStrading_card_game.card_directory)
+
+// /client/proc/validate_cards()
+// set name = "Validate Cards"
+// set category = "Debug"
+// if(!check_rights(R_DEBUG))
+// return
+// if(!SStrading_card_game.loaded)
+// message_admins("The card subsystem is not currently loaded")
+// return
+// var/message = checkCardpacks(SStrading_card_game.card_packs)
+// message += checkCardDatums()
+// if(message)
+// message_admins(message)
+
+// /client/proc/test_cardpack_distribution()
+// set name = "Test Cardpack Distribution"
+// set category = "Debug"
+// if(!check_rights(R_DEBUG))
+// return
+// if(!SStrading_card_game.loaded)
+// message_admins("The card subsystem is not currently loaded")
+// return
+// var/pack = input("Which pack should we test?", "You fucked it didn't you") as null|anything in sortList(SStrading_card_game.card_packs)
+// var/batchCount = input("How many times should we open it?", "Don't worry, I understand") as null|num
+// var/batchSize = input("How many cards per batch?", "I hope you remember to check the validation") as null|num
+// var/guar = input("Should we use the pack's guaranteed rarity? If so, how many?", "We've all been there. Man you should have seen the old system") as null|num
+// checkCardDistribution(pack, batchSize, batchCount, guar)
+
+// /client/proc/print_cards()
+// set name = "Print Cards"
+// set category = "Debug"
+// printAllCards()
+
/client/proc/give_spell(mob/T in GLOB.mob_list)
set category = "Admin.Fun"
set name = "Give Spell"
@@ -572,13 +651,13 @@ GLOBAL_PROTECT(admin_verbs_hideable)
var/type_length = length_char("/obj/effect/proc_holder/spell") + 2
for(var/A in GLOB.spells)
spell_list[copytext_char("[A]", type_length)] = A
- var/obj/effect/proc_holder/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in spell_list
+ var/obj/effect/proc_holder/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in sortList(spell_list)
if(!S)
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].")
- message_admins("[key_name_admin(usr)] gave [key_name(T)] the spell [S].")
+ message_admins("[key_name_admin(usr)] gave [key_name_admin(T)] the spell [S].")
S = spell_list[S]
if(T.mind)
@@ -592,12 +671,12 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set name = "Remove Spell"
set desc = "Remove a spell from the selected mob."
- if(T && T.mind)
- var/obj/effect/proc_holder/spell/S = input("Choose the spell to remove", "NO ABRAKADABRA") as null|anything in T.mind.spell_list
+ if(T?.mind)
+ var/obj/effect/proc_holder/spell/S = input("Choose the spell to remove", "NO ABRAKADABRA") as null|anything in sortList(T.mind.spell_list)
if(S)
T.mind.RemoveSpell(S)
log_admin("[key_name(usr)] removed the spell [S] from [key_name(T)].")
- message_admins("[key_name_admin(usr)] removed the spell [S] from [key_name(T)].")
+ message_admins("[key_name_admin(usr)] removed the spell [S] from [key_name_admin(T)].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Remove Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/give_disease(mob/living/T in GLOB.mob_living_list)
@@ -605,15 +684,15 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set name = "Give Disease"
set desc = "Gives a Disease to a mob."
if(!istype(T))
- to_chat(src, "You can only give a disease to a mob of type /mob/living.")
+ to_chat(src, "You can only give a disease to a mob of type /mob/living.", confidential = TRUE)
return
- var/datum/disease/D = input("Choose the disease to give to that guy", "ACHOO") as null|anything in SSdisease.diseases
+ var/datum/disease/D = input("Choose the disease to give to that guy", "ACHOO") as null|anything in sortList(SSdisease.diseases, /proc/cmp_typepaths_asc)
if(!D)
return
T.ForceContractDisease(new D, FALSE, TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Disease") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
log_admin("[key_name(usr)] gave [key_name(T)] the disease [D].")
- message_admins("[key_name_admin(usr)] gave [key_name(T)] the disease [D].")
+ message_admins("[key_name_admin(usr)] gave [key_name_admin(T)] the disease [D].")
/client/proc/object_say(obj/O in world)
set category = "Admin.Events"
@@ -655,8 +734,8 @@ GLOBAL_PROTECT(admin_verbs_hideable)
holder.deactivate()
to_chat(src, "You are now a normal player.")
- log_admin("[src] deadmined themself.")
- message_admins("[src] deadmined themself.")
+ log_admin("[src] deadminned themselves.")
+ message_admins("[src] deadminned themselves.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Deadmin")
/client/proc/readmin()
@@ -679,7 +758,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
if (!holder)
return //This can happen if an admin attempts to vv themself into somebody elses's deadmin datum by getting ref via brute force
- to_chat(src, "You are now an admin.")
+ to_chat(src, "You are now an admin.", confidential = TRUE)
message_admins("[src] re-adminned themselves.")
log_admin("[src] re-adminned themselves.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Readmin")
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index 8f4a9742ea..1430d5d77a 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -28,6 +28,8 @@ GLOBAL_PROTECT(href_token)
var/deadmined
+ var/datum/filter_editor/filteriffic
+
/datum/admins/CanProcCall(procname)
. = ..()
if(!check_rights(R_SENSITIVE))
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index e0434a6fea..bd4b4cf0c7 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -2500,9 +2500,6 @@
break
return
- else if(href_list["secrets"])
- Secrets_topic(href_list["secrets"],href_list)
-
else if(href_list["ac_view_wanted"]) //Admin newscaster Topic() stuff be here
if(!check_rights(R_ADMIN))
return
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 5704448053..29ee35c117 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -790,11 +790,12 @@
if(!check_rights(R_DEBUG))
return
- SSmedals.hub_enabled = !SSmedals.hub_enabled
+ SSachievements.achievements_enabled = !SSachievements.achievements_enabled
- message_admins("[key_name_admin(src)] [SSmedals.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.")
+ message_admins("[key_name_admin(src)] [SSachievements.achievements_enabled ? "disabled" : "enabled"] the medal hub lockout.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Toggle Medal Disable") // If...
- log_admin("[key_name(src)] [SSmedals.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.")
+ log_admin("[key_name(src)] [SSachievements.achievements_enabled ? "disabled" : "enabled"] the medal hub lockout.")
+
/client/proc/view_runtimes()
set category = "Debug"
diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm
index 6f8e9e703f..358e7f1bec 100644
--- a/code/modules/admin/verbs/diagnostics.dm
+++ b/code/modules/admin/verbs/diagnostics.dm
@@ -63,3 +63,31 @@
load_admins()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Reload All Admins") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
message_admins("[key_name_admin(usr)] manually reloaded admins")
+
+/client/proc/toggle_cdn()
+ set name = "Toggle CDN"
+ set category = "Server"
+ var/static/admin_disabled_cdn_transport = null
+ if (alert(usr, "Are you sure you want to toggle the CDN asset transport?", "Confirm", "Yes", "No") != "Yes")
+ return
+ var/current_transport = CONFIG_GET(string/asset_transport)
+ if (!current_transport || current_transport == "simple")
+ if (admin_disabled_cdn_transport)
+ CONFIG_SET(string/asset_transport, admin_disabled_cdn_transport)
+ admin_disabled_cdn_transport = null
+ SSassets.OnConfigLoad()
+ message_admins("[key_name_admin(usr)] re-enabled the CDN asset transport")
+ log_admin("[key_name(usr)] re-enabled the CDN asset transport")
+ else
+ to_chat(usr, "The CDN is not enabled!")
+ if (alert(usr, "The CDN asset transport is not enabled! If you having issues with assets you can also try disabling filename mutations.", "The CDN asset transport is not enabled!", "Try disabling filename mutations", "Nevermind") == "Try disabling filename mutations")
+ SSassets.transport.dont_mutate_filenames = !SSassets.transport.dont_mutate_filenames
+ message_admins("[key_name_admin(usr)] [(SSassets.transport.dont_mutate_filenames ? "disabled" : "re-enabled")] asset filename transforms")
+ log_admin("[key_name(usr)] [(SSassets.transport.dont_mutate_filenames ? "disabled" : "re-enabled")] asset filename transforms")
+ else
+ admin_disabled_cdn_transport = current_transport
+ CONFIG_SET(string/asset_transport, "simple")
+ SSassets.OnConfigLoad()
+ SSassets.transport.dont_mutate_filenames = TRUE
+ message_admins("[key_name_admin(usr)] disabled the CDN asset transport")
+ log_admin("[key_name(usr)] disabled the CDN asset transport")
diff --git a/code/modules/admin/secrets.dm b/code/modules/admin/verbs/secrets.dm
similarity index 53%
rename from code/modules/admin/secrets.dm
rename to code/modules/admin/verbs/secrets.dm
index ffe5371619..342b8f0bfe 100644
--- a/code/modules/admin/secrets.dm
+++ b/code/modules/admin/verbs/secrets.dm
@@ -1,132 +1,176 @@
-/datum/admins/proc/Secrets()
- if(!check_rights(0))
+
+
+/client/proc/secrets() //Creates a verb for admins to open up the ui
+ set name = "Secrets"
+ set desc = "Abuse harder than you ever have before with this handy dandy semi-misc stuff menu"
+ set category = "Admin.Game"
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Secrets Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ var/datum/secrets_menu/tgui = new(usr)//create the datum
+ tgui.ui_interact(usr)//datum has a tgui component, here we open the window
+
+/datum/secrets_menu
+ var/client/holder //client of whoever is using this datum
+ var/is_debugger = FALSE
+ var/is_funmin = FALSE
+
+/datum/secrets_menu/New(user)//user can either be a client or a mob due to byondcode(tm)
+ if (istype(user, /client))
+ var/client/user_client = user
+ holder = user_client //if its a client, assign it to holder
+ else
+ var/mob/user_mob = user
+ holder = user_mob.client //if its a mob, assign the mob's client to holder
+
+ is_debugger = check_rights(R_DEBUG)
+ is_funmin = check_rights(R_FUN)
+
+/datum/secrets_menu/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/secrets_menu/ui_close()
+ qdel(src)
+
+/datum/secrets_menu/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Secrets")
+ ui.open()
+
+/datum/secrets_menu/ui_data(mob/user)
+ var/list/data = list()
+ data["is_debugger"] = is_debugger
+ data["is_funmin"] = is_funmin
+ return data
+
+/datum/secrets_menu/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ if((action != "admin_log" || action != "show_admins" || action != "mentor_log") && !check_rights(R_ADMIN))
return
-
- var/list/dat = list("The first rule of adminbuse is: you don't talk about the adminbuse.
")
-
- dat +={"
- General Secrets
-
- Admin Log
- Show Admin List
- Mentor Log
-
- "}
-
- if(check_rights(R_ADMIN,0))
- dat += {"
- Admin Secrets
-
- Cure all diseases currently in existence
- Bombing List
- Show current traitors and objectives
- Show last [length(GLOB.lastsignalers)] signalers
- Show last [length(GLOB.lawchanges)] law changes
- Show AI Laws
- Show Game Mode
- Show Crew Manifest
- List DNA (Blood)
- List Fingerprints
- Enable/Disable CTF
- Reset Thunderdome to default state
- Rename Station Name
- Reset Station Name
- Set Night Shift Mode
-
- Shuttles
-
- Move Ferry
- Toggle Arrivals Ferry
- Move Mining Shuttle
- Move Labor Shuttle
-
- "}
-
- if(check_rights(R_FUN,0))
- dat += {"
- Fun Secrets
-
- Trigger a Virus Outbreak
- Turn all humans into monkeys
- Chinese Cartoons
- Change the species of all humans
- Make all areas powered
- Make all areas unpowered
- Power all SMES
- Triple AI mode (needs to be used in the lobby)
- Everyone is the traitor
- AK-47s For Everyone!
- Summon Guns
- Summon Magic
- Summon Events (Toggle)
- There can only be one!
- There can only be one! (40-second delay)
- Make all players stupid
- Egalitarian Station Mode
- Anarcho-Capitalist Station Mode
- Break all lights
- Fix all lights
- The floor is lava! (DANGEROUS: extremely lame)
- Spawn a custom portal storm
-
- Change bomb cap
- Mass Purrbation
- Mass Remove Purrbation
- "}
-
- dat += "
"
-
- if(check_rights(R_DEBUG,0))
- dat += {"
- Security Level Elevated
-
- Change all maintenance doors to engie/brig access only
- Change all maintenance doors to brig access only
- Remove cap on security officers
-
- "}
-
- usr << browse(dat.Join(), "window=secrets")
- return
-
-
-
-
-
-/datum/admins/proc/Secrets_topic(item,href_list)
var/datum/round_event/E
- var/ok = 0
- switch(item)
+ var/ok = FALSE
+ switch(action)
+ //Generic Buttons anyone can use.
if("admin_log")
var/dat = "Admin Log
"
for(var/l in GLOB.admin_log)
dat += "[l]"
if(!GLOB.admin_log.len)
dat += "No-one has done anything this round!"
- usr << browse(dat, "window=admin_log")
-
- if("mentor_log")
- CitadelMentorLogSecret()
-
+ holder << browse(dat, "window=admin_log")
if("show_admins")
var/dat = "Current admins:
"
if(GLOB.admin_datums)
for(var/ckey in GLOB.admin_datums)
var/datum/admins/D = GLOB.admin_datums[ckey]
dat += "[ckey] - [D.rank.name]
"
- usr << browse(dat, "window=showadmins;size=600x500")
+ holder << browse(dat, "window=showadmins;size=600x500")
+ if("mentor_log")
+ var/dat = "Mentor Log
"
+ for(var/l in GLOB.mentorlog)
+ dat += "[l]"
- if("tdomereset")
- if(!check_rights(R_ADMIN))
+ if(!GLOB.mentorlog.len)
+ dat += "No mentors have done anything this round!"
+ usr << browse(dat, "window=mentor_log")
+
+ //Buttons for debug.
+ if("maint_access_engiebrig")
+ if(!is_debugger)
return
+ for(var/obj/machinery/door/airlock/maintenance/M in GLOB.machines)
+ M.check_access()
+ if (ACCESS_MAINT_TUNNELS in M.req_access)
+ M.req_access = list()
+ M.req_one_access = list(ACCESS_BRIG,ACCESS_ENGINE)
+ message_admins("[key_name_admin(holder)] made all maint doors engineering and brig access-only.")
+ if("maint_access_brig")
+ if(!is_debugger)
+ return
+ for(var/obj/machinery/door/airlock/maintenance/M in GLOB.machines)
+ M.check_access()
+ if (ACCESS_MAINT_TUNNELS in M.req_access)
+ M.req_access = list(ACCESS_BRIG)
+ message_admins("[key_name_admin(holder)] made all maint doors brig access-only.")
+ if("infinite_sec")
+ if(!is_debugger)
+ return
+ var/datum/job/J = SSjob.GetJob("Security Officer")
+ if(!J)
+ return
+ J.total_positions = -1
+ J.spawn_positions = -1
+ message_admins("[key_name_admin(holder)] has removed the cap on security officers.")
+ //Buttons for helpful stuff. This is where people land in the tgui
+ if("clear_virus")
+ var/choice = input("Are you sure you want to cure all disease?") in list("Yes", "Cancel")
+ if(choice == "Yes")
+ message_admins("[key_name_admin(holder)] has cured all diseases.")
+ for(var/thing in SSdisease.active_diseases)
+ var/datum/disease/D = thing
+ D.cure(0)
+ if("list_bombers")
+ var/dat = "Bombing List
"
+ for(var/l in GLOB.bombers)
+ dat += text("[l]
")
+ holder << browse(dat, "window=bombers")
+
+ if("list_signalers")
+ var/dat = "Showing last [length(GLOB.lastsignalers)] signalers.
"
+ for(var/sig in GLOB.lastsignalers)
+ dat += "[sig]
"
+ holder << browse(dat, "window=lastsignalers;size=800x500")
+ if("list_lawchanges")
+ var/dat = "Showing last [length(GLOB.lawchanges)] law changes.
"
+ for(var/sig in GLOB.lawchanges)
+ dat += "[sig]
"
+ holder << browse(dat, "window=lawchanges;size=800x500")
+ if("showailaws")
+ holder.holder.output_ai_laws()//huh, inconvenient var naming, huh?
+ if("showgm")
+ if(!SSticker.HasRoundStarted())
+ alert("The game hasn't started yet!")
+ else if (SSticker.mode)
+ alert("The game mode is [SSticker.mode.name]")
+ else
+ alert("For some reason there's a SSticker, but not a game mode")
+ if("manifest")
+ var/dat = "Showing Crew Manifest.
"
+ dat += "| Name | Position |
"
+ for(var/datum/data/record/t in GLOB.data_core.general)
+ dat += "| [t.fields["name"]] | [t.fields["rank"]] |
"
+ dat += "
"
+ holder << browse(dat, "window=manifest;size=440x410")
+ if("dna")
+ var/dat = "Showing DNA from blood.
"
+ dat += "| Name | DNA | Blood Type |
"
+ for(var/i in GLOB.human_list)
+ var/mob/living/carbon/human/H = i
+ if(H.ckey)
+ dat += "| [H] | [H.dna.unique_enzymes] | [H.dna.blood_type] |
"
+ dat += "
"
+ holder << browse(dat, "window=DNA;size=440x410")
+ if("fingerprints")
+ var/dat = "Showing Fingerprints.
"
+ dat += "| Name | Fingerprints |
"
+ for(var/i in GLOB.human_list)
+ var/mob/living/carbon/human/H = i
+ if(H.ckey)
+ dat += "| [H] | [md5(H.dna.uni_identity)] |
"
+ dat += "
"
+ holder << browse(dat, "window=fingerprints;size=440x410")
+ if("ctfbutton")
+ toggle_all_ctf(holder)
+ if("tdomereset")
var/delete_mobs = alert("Clear all mobs?","Confirm","Yes","No","Cancel")
if(delete_mobs == "Cancel")
return
- log_admin("[key_name(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].", 1)
- message_admins("[key_name_admin(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].")
+ log_admin("[key_name(holder)] reset the thunderdome to default with delete_mobs==[delete_mobs].", 1)
+ message_admins("[key_name_admin(holder)] reset the thunderdome to default with delete_mobs==[delete_mobs].")
- var/area/thunderdome = locate(/area/tdome/arena)
+ var/area/thunderdome = GLOB.areas_by_type[/area/tdome/arena]
if(delete_mobs == "Yes")
for(var/mob/living/mob in thunderdome)
qdel(mob) //Clear mobs
@@ -134,31 +178,24 @@
if(!istype(obj, /obj/machinery/camera) && !istype(obj, /obj/effect/abstract/proximity_checker))
qdel(obj) //Clear objects
- var/area/template = locate(/area/tdome/arena_source)
+ var/area/template = GLOB.areas_by_type[/area/tdome/arena_source]
template.copy_contents_to(thunderdome)
-
- if("clear_virus")
-
- var/choice = input("Are you sure you want to cure all disease?") in list("Yes", "Cancel")
- if(choice == "Yes")
- message_admins("[key_name_admin(usr)] has cured all diseases.")
- for(var/thing in SSdisease.active_diseases)
- var/datum/disease/D = thing
- D.cure(0)
if("set_name")
- if(!check_rights(R_ADMIN))
- return
- var/new_name = input(usr, "Please input a new name for the station.", "What?", "") as text|null
+ var/new_name = input(holder, "Please input a new name for the station.", "What?", "") as text|null
if(!new_name)
return
set_station_name(new_name)
- log_admin("[key_name(usr)] renamed the station to \"[new_name]\".")
- message_admins("[key_name_admin(usr)] renamed the station to: [new_name].")
+ log_admin("[key_name(holder)] renamed the station to \"[new_name]\".")
+ message_admins("[key_name_admin(holder)] renamed the station to: [new_name].")
+ priority_announce("[command_name()] has renamed the station to \"[new_name]\".")
+ if("reset_name")
+ var/new_name = new_station_name()
+ set_station_name(new_name)
+ log_admin("[key_name(holder)] reset the station name.")
+ message_admins("[key_name_admin(holder)] reset the station name.")
priority_announce("[command_name()] has renamed the station to \"[new_name]\".")
if("night_shift_set")
- if(!check_rights(R_ADMIN))
- return
- var/val = alert(usr, "What do you want to set night shift to? This will override the automatic system until set to automatic again.", "Night Shift", "On", "Off", "Automatic")
+ var/val = alert(holder, "What do you want to set night shift to? This will override the automatic system until set to automatic again.", "Night Shift", "On", "Off", "Automatic")
switch(val)
if("Automatic")
if(CONFIG_GET(flag/enable_night_shifts))
@@ -172,321 +209,102 @@
if("Off")
SSnightshift.can_fire = FALSE
SSnightshift.update_nightshift(FALSE, TRUE)
-
- if("reset_name")
- if(!check_rights(R_ADMIN))
- return
- var/new_name = new_station_name()
- set_station_name(new_name)
- log_admin("[key_name(usr)] reset the station name.")
- message_admins("[key_name_admin(usr)] reset the station name.")
- priority_announce("[command_name()] has renamed the station to \"[new_name]\".")
-
- if("list_bombers")
- if(!check_rights(R_ADMIN))
- return
- var/dat = "Bombing List
"
- for(var/l in GLOB.bombers)
- dat += text("[l]
")
- usr << browse(dat, "window=bombers")
-
- if("list_signalers")
- if(!check_rights(R_ADMIN))
- return
- var/dat = "Showing last [length(GLOB.lastsignalers)] signalers.
"
- for(var/sig in GLOB.lastsignalers)
- dat += "[sig]
"
- usr << browse(dat, "window=lastsignalers;size=800x500")
-
- if("list_lawchanges")
- if(!check_rights(R_ADMIN))
- return
- var/dat = "Showing last [length(GLOB.lawchanges)] law changes.
"
- for(var/sig in GLOB.lawchanges)
- dat += "[sig]
"
- usr << browse(dat, "window=lawchanges;size=800x500")
-
- if("moveminingshuttle")
- if(!check_rights(R_ADMIN))
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Mining Shuttle"))
- if(!SSshuttle.toggleShuttle("mining","mining_home","mining_away"))
- message_admins("[key_name_admin(usr)] moved mining shuttle")
- log_admin("[key_name(usr)] moved the mining shuttle")
-
- if("movelaborshuttle")
- if(!check_rights(R_ADMIN))
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Labor Shuttle"))
- if(!SSshuttle.toggleShuttle("laborcamp","laborcamp_home","laborcamp_away"))
- message_admins("[key_name_admin(usr)] moved labor shuttle")
- log_admin("[key_name(usr)] moved the labor shuttle")
-
if("moveferry")
- if(!check_rights(R_ADMIN))
- return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send CentCom Ferry"))
if(!SSshuttle.toggleShuttle("ferry","ferry_home","ferry_away"))
- message_admins("[key_name_admin(usr)] moved the CentCom ferry")
- log_admin("[key_name(usr)] moved the CentCom ferry")
-
+ message_admins("[key_name_admin(holder)] moved the CentCom ferry")
+ log_admin("[key_name(holder)] moved the CentCom ferry")
if("togglearrivals")
- if(!check_rights(R_ADMIN))
- return
var/obj/docking_port/mobile/arrivals/A = SSshuttle.arrivals
if(A)
var/new_perma = !A.perma_docked
A.perma_docked = new_perma
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Permadock Arrivals Shuttle", "[new_perma ? "Enabled" : "Disabled"]"))
- message_admins("[key_name_admin(usr)] [new_perma ? "stopped" : "started"] the arrivals shuttle")
- log_admin("[key_name(usr)] [new_perma ? "stopped" : "started"] the arrivals shuttle")
+ message_admins("[key_name_admin(holder)] [new_perma ? "stopped" : "started"] the arrivals shuttle")
+ log_admin("[key_name(holder)] [new_perma ? "stopped" : "started"] the arrivals shuttle")
else
- to_chat(usr, "There is no arrivals shuttle")
- if("showailaws")
- if(!check_rights(R_ADMIN))
- return
- output_ai_laws()
- if("showgm")
- if(!check_rights(R_ADMIN))
- return
- if(!SSticker.HasRoundStarted())
- alert("The game hasn't started yet!")
- else if (SSticker.mode)
- alert("The game mode is [SSticker.mode.name]")
- else alert("For some reason there's a SSticker, but not a game mode")
- if("manifest")
- if(!check_rights(R_ADMIN))
- return
- var/dat = "Showing Crew Manifest.
"
- dat += "| Name | Position |
"
- for(var/datum/data/record/t in GLOB.data_core.general)
- dat += "| [t.fields["name"]] | [t.fields["rank"]] |
"
- dat += "
"
- usr << browse(dat, "window=manifest;size=440x410")
- if("DNA")
- if(!check_rights(R_ADMIN))
- return
- var/dat = "Showing DNA from blood.
"
- dat += "| Name | DNA | Blood Type |
"
- for(var/mob/living/carbon/human/H in GLOB.carbon_list)
- if(H.ckey)
- dat += "| [H] | [H.dna.unique_enzymes] | [H.dna.blood_type] |
"
- dat += "
"
- usr << browse(dat, "window=DNA;size=440x410")
- if("fingerprints")
- if(!check_rights(R_ADMIN))
- return
- var/dat = "Showing Fingerprints.
"
- dat += "| Name | Fingerprints |
"
- for(var/mob/living/carbon/human/H in GLOB.carbon_list)
- if(H.ckey)
- dat += "| [H] | [md5(H.dna.uni_identity)] |
"
- dat += "
"
- usr << browse(dat, "window=fingerprints;size=440x410")
-
- if("monkey")
- if(!check_rights(R_FUN))
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Monkeyize All Humans"))
- for(var/mob/living/carbon/human/H in GLOB.carbon_list)
- spawn(0)
- H.monkeyize()
- ok = 1
-
- if("allspecies")
- if(!check_rights(R_FUN))
- return
- var/result = input(usr, "Please choose a new species","Species") as null|anything in GLOB.species_list
- if(result)
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Species Change", "[result]"))
- log_admin("[key_name(usr)] turned all humans into [result]", 1)
- message_admins("\blue [key_name_admin(usr)] turned all humans into [result]")
- var/newtype = GLOB.species_list[result]
- for(var/mob/living/carbon/human/H in GLOB.carbon_list)
- H.set_species(newtype)
-
- if("tripleAI")
- if(!check_rights(R_FUN))
- return
- usr.client.triple_ai()
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Triple AI"))
-
- if("power")
- if(!check_rights(R_FUN))
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Power All APCs"))
- log_admin("[key_name(usr)] made all areas powered", 1)
- message_admins("[key_name_admin(usr)] made all areas powered")
- power_restore()
-
- if("unpower")
- if(!check_rights(R_FUN))
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Depower All APCs"))
- log_admin("[key_name(usr)] made all areas unpowered", 1)
- message_admins("[key_name_admin(usr)] made all areas unpowered")
- power_failure()
-
- if("quickpower")
- if(!check_rights(R_FUN))
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Power All SMESs"))
- log_admin("[key_name(usr)] made all SMESs powered", 1)
- message_admins("[key_name_admin(usr)] made all SMESs powered")
- power_restore_quick()
-
- if("traitor_all")
- if(!check_rights(R_FUN))
- return
- if(!SSticker.HasRoundStarted())
- alert("The game hasn't started yet!")
- return
- var/objective = stripped_input(usr, "Enter an objective")
- if(!objective)
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Traitor All", "[objective]"))
- for(var/mob/living/H in GLOB.player_list)
- if(!(ishuman(H)||istype(H, /mob/living/silicon/)))
- continue
- if(H.stat == DEAD || !H.client || !H.mind || ispAI(H))
- continue
- if(is_special_character(H))
- continue
- var/datum/antagonist/traitor/T = new()
- T.give_objectives = FALSE
- var/datum/objective/new_objective = new
- new_objective.owner = H
- new_objective.explanation_text = objective
- T.add_objective(new_objective)
- H.mind.add_antag_datum(T)
- message_admins("[key_name_admin(usr)] used everyone is a traitor secret. Objective is [objective]")
- log_admin("[key_name(usr)] used everyone is a traitor secret. Objective is [objective]")
-
- if("changebombcap")
- if(!check_rights(R_FUN))
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Bomb Cap"))
-
- var/newBombCap = input(usr,"What would you like the new bomb cap to be. (entered as the light damage range (the 3rd number in common (1,2,3) notation)) Must be above 4)", "New Bomb Cap", GLOB.MAX_EX_LIGHT_RANGE) as num|null
- if (!CONFIG_SET(number/bombcap, newBombCap))
- return
-
- message_admins("[key_name_admin(usr)] changed the bomb cap to [GLOB.MAX_EX_DEVESTATION_RANGE], [GLOB.MAX_EX_HEAVY_RANGE], [GLOB.MAX_EX_LIGHT_RANGE]")
- log_admin("[key_name(usr)] changed the bomb cap to [GLOB.MAX_EX_DEVESTATION_RANGE], [GLOB.MAX_EX_HEAVY_RANGE], [GLOB.MAX_EX_LIGHT_RANGE]")
-
- if("blackout")
- if(!check_rights(R_FUN))
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Break All Lights"))
- message_admins("[key_name_admin(usr)] broke all lights")
- for(var/obj/machinery/light/L in GLOB.machines)
- L.break_light_tube()
-
- if("anime")
- if(!check_rights(R_FUN))
- return
- var/animetype = alert("Would you like to have the clothes be changed?",,"Yes","No","Cancel")
-
- var/droptype
- if(animetype =="Yes")
- droptype = alert("Make the uniforms Nodrop?",,"Yes","No","Cancel")
-
- if(animetype == "Cancel" || droptype == "Cancel")
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Chinese Cartoons"))
- message_admins("[key_name_admin(usr)] made everything kawaii.")
- for(var/mob/living/carbon/human/H in GLOB.carbon_list)
- SEND_SOUND(H, sound(get_announcer_sound("animes")))
-
- if(H.dna.species.id == "human")
- if(H.dna.features["tail_human"] == "None" || H.dna.features["ears"] == "None")
- var/obj/item/organ/ears/cat/ears = new
- var/obj/item/organ/tail/cat/tail = new
- ears.Insert(H, drop_if_replaced=FALSE)
- tail.Insert(H, drop_if_replaced=FALSE)
- var/list/honorifics = list("[MALE]" = list("kun"), "[FEMALE]" = list("chan","tan"), "[NEUTER]" = list("san"), "[PLURAL]" = list("san")) //John Robust -> Robust-kun
- var/list/names = splittext(H.real_name," ")
- var/forename = names.len > 1 ? names[2] : names[1]
- var/newname = "[forename]-[pick(honorifics["[H.gender]"])]"
- H.fully_replace_character_name(H.real_name,newname)
- H.update_mutant_bodyparts()
- if(animetype == "Yes")
- var/seifuku = pick(typesof(/obj/item/clothing/under/costume/schoolgirl))
- var/obj/item/clothing/under/costume/schoolgirl/I = new seifuku
- var/olduniform = H.w_uniform
- H.temporarilyRemoveItemFromInventory(H.w_uniform, TRUE, FALSE)
- H.equip_to_slot_or_del(I, SLOT_W_UNIFORM)
- qdel(olduniform)
- if(droptype == "Yes")
- ADD_TRAIT(I, TRAIT_NODROP, ADMIN_TRAIT)
- else
- to_chat(H, "You're not kawaii enough for this.")
-
- if("whiteout")
- if(!check_rights(R_FUN))
- return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Fix All Lights"))
- message_admins("[key_name_admin(usr)] fixed all lights")
- for(var/obj/machinery/light/L in GLOB.machines)
- L.fix()
-
- if("floorlava")
- SSweather.run_weather(/datum/weather/floor_is_lava)
-
+ to_chat(holder, "There is no arrivals shuttle.", confidential = TRUE)
+ if("moveminingshuttle")
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Mining Shuttle"))
+ if(!SSshuttle.toggleShuttle("mining","mining_home","mining_away"))
+ message_admins("[key_name_admin(usr)] moved mining shuttle")
+ log_admin("[key_name(usr)] moved the mining shuttle")
+ if("movelaborshuttle")
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Labor Shuttle"))
+ if(!SSshuttle.toggleShuttle("laborcamp","laborcamp_home","laborcamp_away"))
+ message_admins("[key_name_admin(holder)] moved labor shuttle")
+ log_admin("[key_name(holder)] moved the labor shuttle")
+ //!fun! buttons.
if("virus")
- if(!check_rights(R_FUN))
+ if(!is_funmin)
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Virus Outbreak"))
switch(alert("Do you want this to be a random disease or do you have something in mind?",,"Make Your Own","Random","Choose"))
if("Make Your Own")
- AdminCreateVirus(usr.client)
+ AdminCreateVirus(holder)
if("Random")
- E = new /datum/round_event/disease_outbreak()
+ var/datum/round_event_control/disease_outbreak/DC = locate(/datum/round_event_control/disease_outbreak) in SSevents.control
+ E = DC.runEvent()
if("Choose")
- var/virus = input("Choose the virus to spread", "BIOHAZARD") as null|anything in typesof(/datum/disease)
- E = new /datum/round_event/disease_outbreak{}()
- var/datum/round_event/disease_outbreak/DO = E
+ var/virus = input("Choose the virus to spread", "BIOHAZARD") as null|anything in sortList(typesof(/datum/disease), /proc/cmp_typepaths_asc)
+ var/datum/round_event_control/disease_outbreak/DC = locate(/datum/round_event_control/disease_outbreak) in SSevents.control
+ var/datum/round_event/disease_outbreak/DO = DC.runEvent()
DO.virus_type = virus
-
- if("stupify")
- if(!check_rights(R_FUN))
+ E = DO
+ if("allspecies")
+ if(!is_funmin)
return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Braindamage"))
- for(var/mob/living/carbon/human/H in GLOB.player_list)
- to_chat(H, "You suddenly feel stupid.")
- H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 60, 80)
- message_admins("[key_name_admin(usr)] made everybody stupid")
-
- if("eagles")//SCRAW
- if(!check_rights(R_FUN))
+ var/result = input(holder, "Please choose a new species","Species") as null|anything in GLOB.species_list
+ if(result)
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Species Change", "[result]"))
+ log_admin("[key_name(holder)] turned all humans into [result]", 1)
+ message_admins("\blue [key_name_admin(holder)] turned all humans into [result]")
+ var/newtype = GLOB.species_list[result]
+ for(var/i in GLOB.human_list)
+ var/mob/living/carbon/human/H = i
+ H.set_species(newtype)
+ if("power")
+ if(!is_funmin)
return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Egalitarian Station"))
- for(var/obj/machinery/door/airlock/W in GLOB.machines)
- if(is_station_level(W.z) && !istype(get_area(W), /area/bridge) && !istype(get_area(W), /area/crew_quarters) && !istype(get_area(W), /area/security/prison))
- W.req_access = list()
- message_admins("[key_name_admin(usr)] activated Egalitarian Station mode")
- priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, "commandreport")
-
- if("ak47s")
- if(!check_rights(R_FUN))
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Power All APCs"))
+ log_admin("[key_name(holder)] made all areas powered", 1)
+ message_admins("[key_name_admin(holder)] made all areas powered")
+ power_restore()
+ if("unpower")
+ if(!is_funmin)
return
- message_admins("[key_name_admin(usr)] activated AK-47s for Everyone!")
- usr.client.ak47s()
- sound_to_playing_players('sound/misc/ak47s.ogg')
-
- if("ancap")
- if(!check_rights(R_FUN))
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Depower All APCs"))
+ log_admin("[key_name(holder)] made all areas unpowered", 1)
+ message_admins("[key_name_admin(holder)] made all areas unpowered")
+ power_failure()
+ if("quickpower")
+ if(!is_funmin)
return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Anarcho-capitalist Station"))
- SSeconomy.full_ancap = !SSeconomy.full_ancap
- message_admins("[key_name_admin(usr)] toggled Anarcho-capitalist mode")
- if(SSeconomy.full_ancap)
- priority_announce("The NAP is now in full effect.", null, "commandreport")
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Power All SMESs"))
+ log_admin("[key_name(holder)] made all SMESs powered", 1)
+ message_admins("[key_name_admin(holder)] made all SMESs powered")
+ power_restore_quick()
+ // if("anon_name")
+ // if(!is_funmin)
+ // return
+ // holder.anon_names()
+ // SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Anonymous Names"))
+ if("tripleAI")
+ if(!is_funmin)
+ return
+ holder.triple_ai()
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Triple AI"))
+ if("onlyone")
+ if(!is_funmin)
+ return
+ var/response = alert("Delay by 40 seconds?", "There can, in fact, only be one", "Instant!", "40 seconds (crush the hope of a normal shift)")
+ if(response == "Instant!")
+ holder.only_one()
else
- priority_announce("The NAP has been revoked.", null, "commandreport")
-
+ holder.only_one_delayed()
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("There Can Be Only One"))
if("guns")
- if(!check_rights(R_FUN))
+ if(!is_funmin)
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Guns"))
var/survivor_probability = 0
@@ -496,23 +314,21 @@
if("All Antags!")
survivor_probability = 100
- rightandwrong(SUMMON_GUNS, usr, survivor_probability)
-
+ rightandwrong(SUMMON_GUNS, holder, survivor_probability)
if("magic")
- if(!check_rights(R_FUN))
+ if(!is_funmin)
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Magic"))
var/survivor_probability = 0
- switch(alert("Do you want this to create survivors antagonists?",,"No Antags","Some Antags","All Antags!"))
+ switch(alert("Do you want this to create magician antagonists?",,"No Antags","Some Antags","All Antags!"))
if("Some Antags")
survivor_probability = 25
if("All Antags!")
survivor_probability = 100
- rightandwrong(SUMMON_MAGIC, usr, survivor_probability)
-
+ rightandwrong(SUMMON_MAGIC, holder, survivor_probability)
if("events")
- if(!check_rights(R_FUN))
+ if(!is_funmin)
return
if(!SSevents.wizardmode)
if(alert("Do you want to toggle summon events on?",,"Yes","No") == "Yes")
@@ -528,78 +344,41 @@
SSevents.toggleWizardmode()
SSevents.resetFrequency()
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Events", "Disable"))
-
- if("dorf")
- if(!check_rights(R_FUN))
+ if("eagles")
+ if(!is_funmin)
return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Dwarf Beards"))
- for(var/mob/living/carbon/human/B in GLOB.carbon_list)
- B.facial_hair_style = "Dward Beard"
- B.update_hair()
- message_admins("[key_name_admin(usr)] activated dorf mode")
-
- if("onlyone")
- if(!check_rights(R_FUN))
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Egalitarian Station"))
+ for(var/obj/machinery/door/airlock/W in GLOB.machines)
+ if(is_station_level(W.z) && !istype(get_area(W), /area/bridge) && !istype(get_area(W), /area/crew_quarters) && !istype(get_area(W), /area/security/prison))
+ W.req_access = list()
+ message_admins("[key_name_admin(holder)] activated Egalitarian Station mode")
+ priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, "commandreport")
+ if("ancap")
+ if(!is_funmin)
return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("There Can Be Only One"))
- usr.client.only_one()
- sound_to_playing_players('sound/misc/highlander.ogg')
-
- if("delayed_onlyone")
- if(!check_rights(R_FUN))
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Anarcho-capitalist Station"))
+ SSeconomy.full_ancap = !SSeconomy.full_ancap
+ message_admins("[key_name_admin(holder)] toggled Anarcho-capitalist mode")
+ if(SSeconomy.full_ancap)
+ priority_announce("The NAP is now in full effect.", null, "commandreport")
+ else
+ priority_announce("The NAP has been revoked.", null, "commandreport")
+ if("blackout")
+ if(!is_funmin)
return
- SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("There Can Be Only One"))
- usr.client.only_one_delayed()
- sound_to_playing_players('sound/misc/highlander_delayed.ogg')
-
- if("maint_access_brig")
- if(!check_rights(R_DEBUG))
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Break All Lights"))
+ message_admins("[key_name_admin(holder)] broke all lights")
+ for(var/obj/machinery/light/L in GLOB.machines)
+ L.break_light_tube()
+ if("whiteout")
+ if(!is_funmin)
return
- for(var/obj/machinery/door/airlock/maintenance/M in GLOB.machines)
- M.check_access()
- if (ACCESS_MAINT_TUNNELS in M.req_access)
- M.req_access = list(ACCESS_BRIG)
- message_admins("[key_name_admin(usr)] made all maint doors brig access-only.")
- if("maint_access_engiebrig")
- if(!check_rights(R_DEBUG))
- return
- for(var/obj/machinery/door/airlock/maintenance/M in GLOB.machines)
- M.check_access()
- if (ACCESS_MAINT_TUNNELS in M.req_access)
- M.req_access = list()
- M.req_one_access = list(ACCESS_BRIG,ACCESS_ENGINE)
- message_admins("[key_name_admin(usr)] made all maint doors engineering and brig access-only.")
- if("infinite_sec")
- if(!check_rights(R_DEBUG))
- return
- var/datum/job/J = SSjob.GetJob("Security Officer")
- if(!J)
- return
- J.total_positions = -1
- J.spawn_positions = -1
- message_admins("[key_name_admin(usr)] has removed the cap on security officers.")
-
- if("ctfbutton")
- if(!check_rights(R_ADMIN))
- return
- toggle_all_ctf(usr)
- if("masspurrbation")
- if(!check_rights(R_FUN))
- return
- mass_purrbation()
- message_admins("[key_name_admin(usr)] has put everyone on \
- purrbation!")
- log_admin("[key_name(usr)] has put everyone on purrbation.")
- if("massremovepurrbation")
- if(!check_rights(R_FUN))
- return
- mass_remove_purrbation()
- message_admins("[key_name_admin(usr)] has removed everyone from \
- purrbation.")
- log_admin("[key_name(usr)] has removed everyone from purrbation.")
-
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Fix All Lights"))
+ message_admins("[key_name_admin(holder)] fixed all lights")
+ for(var/obj/machinery/light/L in GLOB.machines)
+ L.fix()
if("customportal")
- if(!check_rights(R_FUN))
+ if(!is_funmin)
return
var/list/settings = list(
@@ -620,14 +399,14 @@
)
)
- message_admins("[key_name(usr)] is creating a custom portal storm...")
- var/list/prefreturn = presentpreflikepicker(usr,"Customize Portal Storm", "Customize Portal Storm", Button1="Ok", width = 600, StealFocus = 1,Timeout = 0, settings=settings)
+ message_admins("[key_name(holder)] is creating a custom portal storm...")
+ var/list/prefreturn = presentpreflikepicker(holder,"Customize Portal Storm", "Customize Portal Storm", Button1="Ok", width = 600, StealFocus = 1,Timeout = 0, settings=settings)
if (prefreturn["button"] == 1)
var/list/prefs = settings["mainsettings"]
if (prefs["amount"]["value"] < 1 || prefs["portalnum"]["value"] < 1)
- to_chat(usr, "Number of portals and mobs to spawn must be at least 1")
+ to_chat(holder, "Number of portals and mobs to spawn must be at least 1.", confidential = TRUE)
return
var/mob/pathToSpawn = prefs["typepath"]["value"]
@@ -635,7 +414,7 @@
pathToSpawn = text2path(pathToSpawn)
if (!ispath(pathToSpawn))
- to_chat(usr, "Invalid path [pathToSpawn]")
+ to_chat(holder, "Invalid path [pathToSpawn].", confidential = TRUE)
return
var/list/candidates = list()
@@ -653,8 +432,8 @@
var/mutable_appearance/storm = mutable_appearance('icons/obj/tesla_engine/energy_ball.dmi', "energy_ball_fast", FLY_LAYER)
storm.color = prefs["color"]["value"]
- message_admins("[key_name_admin(usr)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]")
- log_admin("[key_name(usr)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]")
+ message_admins("[key_name_admin(holder)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]")
+ log_admin("[key_name(holder)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]")
var/outfit = prefs["humanoutfit"]["value"]
if (!ispath(outfit))
@@ -668,20 +447,157 @@
addtimer(CALLBACK(GLOBAL_PROC, .proc/doPortalSpawn, get_random_station_turf(), pathToSpawn, length(ghostcandidates), storm, ghostcandidates, outfit), i*prefs["delay"]["value"])
else if (prefs["playersonly"]["value"] != "Yes")
addtimer(CALLBACK(GLOBAL_PROC, .proc/doPortalSpawn, get_random_station_turf(), pathToSpawn, prefs["amount"]["value"], storm, null, outfit), i*prefs["delay"]["value"])
+ if("changebombcap")
+ if(!is_funmin)
+ return
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Bomb Cap"))
+ var/newBombCap = input(holder,"What would you like the new bomb cap to be. (entered as the light damage range (the 3rd number in common (1,2,3) notation)) Must be above 4)", "New Bomb Cap", GLOB.MAX_EX_LIGHT_RANGE) as num|null
+ if (!CONFIG_SET(number/bombcap, newBombCap))
+ return
+
+ message_admins("[key_name_admin(holder)] changed the bomb cap to [GLOB.MAX_EX_DEVESTATION_RANGE], [GLOB.MAX_EX_HEAVY_RANGE], [GLOB.MAX_EX_LIGHT_RANGE]")
+ log_admin("[key_name(holder)] changed the bomb cap to [GLOB.MAX_EX_DEVESTATION_RANGE], [GLOB.MAX_EX_HEAVY_RANGE], [GLOB.MAX_EX_LIGHT_RANGE]")
+ //buttons that are fun for exactly you and nobody else.
+ if("monkey")
+ if(!is_funmin)
+ return
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Monkeyize All Humans"))
+ for(var/i in GLOB.human_list)
+ var/mob/living/carbon/human/H = i
+ INVOKE_ASYNC(H, /mob/living/carbon.proc/monkeyize)
+ ok = TRUE
+ if("traitor_all")
+ if(!is_funmin)
+ return
+ if(!SSticker.HasRoundStarted())
+ alert("The game hasn't started yet!")
+ return
+ var/objective = stripped_input(holder, "Enter an objective")
+ if(!objective)
+ return
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Traitor All", "[objective]"))
+ for(var/mob/living/H in GLOB.player_list)
+ if(!(ishuman(H)||istype(H, /mob/living/silicon/)))
+ continue
+ if(H.stat == DEAD || !H.mind || ispAI(H))
+ continue
+ if(is_special_character(H))
+ continue
+ var/datum/antagonist/traitor/T = new()
+ T.give_objectives = FALSE
+ var/datum/objective/new_objective = new
+ new_objective.owner = H
+ new_objective.explanation_text = objective
+ T.add_objective(new_objective)
+ H.mind.add_antag_datum(T)
+ message_admins("[key_name_admin(holder)] used everyone is a traitor secret. Objective is [objective]")
+ log_admin("[key_name(holder)] used everyone is a traitor secret. Objective is [objective]")
+ if("ak47s")
+ if(!is_funmin)
+ return
+ if(!SSticker.HasRoundStarted())
+ alert("The game hasn't started yet!")
+ return
+ message_admins("[key_name_admin(holder)] activated AK-47s for Everyone!")
+ holder.ak47s()
+ sound_to_playing_players('sound/misc/ak47s.ogg')
+
+ if("massbraindamage")
+ if(!is_funmin)
+ return
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Braindamage"))
+ for(var/mob/living/carbon/human/H in GLOB.player_list)
+ to_chat(H, "You suddenly feel stupid.", confidential = TRUE)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 60, 80)
+ message_admins("[key_name_admin(holder)] made everybody brain damaged")
+ if("floorlava")
+ SSweather.run_weather(/datum/weather/floor_is_lava)
+ if("anime")
+ if(!is_funmin)
+ return
+ var/animetype = alert("Would you like to have the clothes be changed?",,"Yes","No","Cancel")
+
+ var/droptype
+ if(animetype =="Yes")
+ droptype = alert("Make the uniforms Nodrop?",,"Yes","No","Cancel")
+
+ if(animetype == "Cancel" || droptype == "Cancel")
+ return
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Chinese Cartoons"))
+ message_admins("[key_name_admin(holder)] made everything kawaii.")
+ for(var/i in GLOB.human_list)
+ var/mob/living/carbon/human/H = i
+ SEND_SOUND(H, sound(get_announcer_sound("animes")))
+
+ if(H.dna.species.id == "human")
+ if(H.dna.features["tail_human"] == "None" || H.dna.features["ears"] == "None")
+ var/obj/item/organ/ears/cat/ears = new
+ var/obj/item/organ/tail/cat/tail = new
+ ears.Insert(H, drop_if_replaced=FALSE)
+ tail.Insert(H, drop_if_replaced=FALSE)
+ var/list/honorifics = list("[MALE]" = list("kun"), "[FEMALE]" = list("chan","tan"), "[NEUTER]" = list("san"), "[PLURAL]" = list("san")) //John Robust -> Robust-kun
+ var/list/names = splittext(H.real_name," ")
+ var/forename = names.len > 1 ? names[2] : names[1]
+ var/newname = "[forename]-[pick(honorifics["[H.gender]"])]"
+ H.fully_replace_character_name(H.real_name,newname)
+ H.update_mutant_bodyparts()
+ if(animetype == "Yes")
+ var/seifuku = pick(typesof(/obj/item/clothing/under/costume/schoolgirl))
+ var/obj/item/clothing/under/costume/schoolgirl/I = new seifuku
+ var/olduniform = H.w_uniform
+ H.temporarilyRemoveItemFromInventory(H.w_uniform, TRUE, FALSE)
+ H.equip_to_slot_or_del(I, ITEM_SLOT_ICLOTHING)
+ qdel(olduniform)
+ if(droptype == "Yes")
+ ADD_TRAIT(I, TRAIT_NODROP, ADMIN_TRAIT)
+ else
+ to_chat(H, "You're not kawaii enough for this!", confidential = TRUE)
+ if("masspurrbation")
+ if(!is_funmin)
+ return
+ mass_purrbation()
+ message_admins("[key_name_admin(holder)] has put everyone on \
+ purrbation!")
+ log_admin("[key_name(holder)] has put everyone on purrbation.")
+ if("massremovepurrbation")
+ if(!is_funmin)
+ return
+ mass_remove_purrbation()
+ message_admins("[key_name_admin(holder)] has removed everyone from \
+ purrbation.")
+ log_admin("[key_name(holder)] has removed everyone from purrbation.")
+ // if("massimmerse") // my immursion is ruinned :(
+ // if(!is_funmin)
+ // return
+ // mass_immerse()
+ // message_admins("[key_name_admin(holder)] has Fully Immersed
+ // everyone!")
+ // log_admin("[key_name(holder)] has Fully Immersed everyone.")
+ // if("unmassimmerse")
+ // if(!is_funmin)
+ // return
+ // mass_immerse(remove=TRUE)
+ // message_admins("[key_name_admin(holder)] has Un-Fully Immersed
+ // everyone!")
+ // log_admin("[key_name(holder)] has Un-Fully Immersed everyone.")
if(E)
E.processing = FALSE
if(E.announceWhen>0)
- if(alert(usr, "Would you like to alert the crew?", "Alert", "Yes", "No") == "No")
- E.announceWhen = -1
+ switch(alert(holder, "Would you like to alert the crew?", "Alert", "Yes", "No", "Cancel"))
+ if("Cancel")
+ E.kill()
+ return
+ if("No")
+ E.announceWhen = -1
E.processing = TRUE
- if (usr)
- log_admin("[key_name(usr)] used secret [item]")
- if (ok)
- to_chat(world, text("A secret has been activated by []!", usr.key))
+ if(holder)
+ log_admin("[key_name(holder)] used secret [action]")
+ if(ok)
+ to_chat(world, text("A secret has been activated by []!", holder.key), confidential = TRUE)
/proc/portalAnnounce(announcement, playlightning)
- set waitfor = 0
+ set waitfor = FALSE
if (playlightning)
sound_to_playing_players('sound/magic/lightning_chargeup.ogg')
sleep(80)
@@ -697,11 +613,11 @@
var/mob/chosen = players[1]
if (chosen.client)
chosen.client.prefs.copy_to(spawnedMob)
- chosen.transfer_ckey(spawnedMob)
+ spawnedMob.key = chosen.key
players -= chosen
if (ishuman(spawnedMob) && ispath(humanoutfit, /datum/outfit))
var/mob/living/carbon/human/H = spawnedMob
H.equipOutfit(humanoutfit)
var/turf/T = get_step(loc, SOUTHWEST)
flick_overlay_static(portal_appearance, T, 15)
- playsound(T, 'sound/magic/lightningbolt.ogg', rand(80, 100), 1)
+ playsound(T, 'sound/magic/lightningbolt.ogg', rand(80, 100), TRUE)
diff --git a/code/modules/admin/view_variables/filterrific.dm b/code/modules/admin/view_variables/filterrific.dm
new file mode 100644
index 0000000000..e651028cbe
--- /dev/null
+++ b/code/modules/admin/view_variables/filterrific.dm
@@ -0,0 +1,99 @@
+/datum/filter_editor
+ var/atom/target
+
+/datum/filter_editor/New(atom/target)
+ src.target = target
+
+/datum/filter_editor/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/filter_editor/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Filteriffic")
+ ui.open()
+
+/datum/filter_editor/ui_static_data(mob/user)
+ var/list/data = list()
+ data["filter_info"] = GLOB.master_filter_info
+ return data
+
+/datum/filter_editor/ui_data()
+ var/list/data = list()
+ data["target_name"] = target.name
+ data["target_filter_data"] = target.filter_data
+ return data
+
+/datum/filter_editor/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("add_filter")
+ var/target_name = params["name"]
+ while(target.filter_data && target.filter_data[target_name])
+ target_name = "[target_name]-dupe"
+ target.add_filter(target_name, params["priority"], list("type" = params["type"]))
+ . = TRUE
+ if("remove_filter")
+ target.remove_filter(params["name"])
+ . = TRUE
+ if("rename_filter")
+ var/list/filter_data = target.filter_data[params["name"]]
+ target.remove_filter(params["name"])
+ target.add_filter(params["new_name"], filter_data["priority"], filter_data)
+ . = TRUE
+ if("edit_filter")
+ target.remove_filter(params["name"])
+ target.add_filter(params["name"], params["priority"], params["new_filter"])
+ . = TRUE
+ if("change_priority")
+ var/new_priority = params["new_priority"]
+ target.change_filter_priority(params["name"], new_priority)
+ . = TRUE
+ if("transition_filter_value")
+ target.transition_filter(params["name"], 4, params["new_data"])
+ . = TRUE
+ if("modify_filter_value")
+ var/list/old_filter_data = target.filter_data[params["name"]]
+ var/list/new_filter_data = old_filter_data.Copy()
+ for(var/entry in params["new_data"])
+ new_filter_data[entry] = params["new_data"][entry]
+ for(var/entry in new_filter_data)
+ if(entry == GLOB.master_filter_info[old_filter_data["type"]]["defaults"][entry])
+ new_filter_data.Remove(entry)
+ target.remove_filter(params["name"])
+ target.add_filter(params["name"], old_filter_data["priority"], new_filter_data)
+ . = TRUE
+ if("modify_color_value")
+ var/new_color = input(usr, "Pick new filter color", "Filteriffic Colors!") as color|null
+ if(new_color)
+ target.transition_filter(params["name"], 4, list("color" = new_color))
+ . = TRUE
+ if("modify_icon_value")
+ var/icon/new_icon = input("Pick icon:", "Icon") as null|icon
+ if(new_icon)
+ target.filter_data[params["name"]]["icon"] = new_icon
+ target.update_filters()
+ . = TRUE
+ if("mass_apply")
+ if(!check_rights_for(usr.client, R_FUN))
+ to_chat(usr, "Someone awarded you a heart![heart_reason ? " They said: [heart_reason]!" : ""]")
+ if(!src)
+ return
+ prefs.hearted_until = world.realtime + (24 HOURS)
+ prefs.hearted = TRUE
+ if(!src)
+ return
+ prefs.save_preferences()
+
/// compiles a full list of verbs and sends it to the browser
/client/proc/init_verbs()
if(IsAdminAdvancedProcCall())
@@ -1048,3 +1067,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
return TRUE
return FALSE
+/client/proc/open_filter_editor(atom/in_atom)
+ if(holder)
+ holder.filteriffic = new /datum/filter_editor(in_atom)
+ holder.filteriffic.ui_interact(mob)
diff --git a/code/modules/client/player_details.dm b/code/modules/client/player_details.dm
index 6b2a936533..0c06d96b64 100644
--- a/code/modules/client/player_details.dm
+++ b/code/modules/client/player_details.dm
@@ -3,4 +3,21 @@
var/list/logging = list()
var/list/post_login_callbacks = list()
var/list/post_logout_callbacks = list()
+ var/list/played_names = list() //List of names this key played under this round
var/byond_version = "Unknown"
+ var/datum/achievement_data/achievements
+
+/datum/player_details/New(key)
+ achievements = new(key)
+
+/proc/log_played_names(ckey, ...)
+ if(!ckey)
+ return
+ if(args.len < 2)
+ return
+ var/list/names = args.Copy(2)
+ var/datum/player_details/P = GLOB.player_details[ckey]
+ if(P)
+ for(var/name in names)
+ if(name)
+ P.played_names |= name
diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm
index 90ebe7f6a1..a9ee0f5412 100644
--- a/code/modules/events/immovable_rod.dm
+++ b/code/modules/events/immovable_rod.dm
@@ -160,6 +160,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
wizard.apply_damage(25, BRUTE)
qdel(src)
else
+ U.client.give_award(/datum/award/achievement/misc/feat_of_strength, U) //rod-form wizards would probably make this a lot easier to get so keep it to regular rods only
U.visible_message("[U] suplexes [src] into the ground!", "You suplex [src] into the ground!")
new /obj/structure/festivus/anchored(drop_location())
new /obj/effect/anomaly/flux(drop_location())
diff --git a/code/modules/mafia/controller.dm b/code/modules/mafia/controller.dm
index cd8c382f30..54dcaebeec 100644
--- a/code/modules/mafia/controller.dm
+++ b/code/modules/mafia/controller.dm
@@ -1,21 +1,21 @@
/**
- * The mafia controller handles the mafia minigame in progress.
- * It is first created when the first ghost signs up to play.
- */
+ * The mafia controller handles the mafia minigame in progress.
+ * It is first created when the first ghost signs up to play.
+ */
/datum/mafia_controller
///list of observers that should get game updates.
var/list/spectators = list()
///all roles in the game, dead or alive. check their game status if you only want living or dead.
var/list/all_roles = list()
- ///exists to speed up role retrieval, it's a dict. player_role_lookup[player ckey] will give you the role they play
+ ///exists to speed up role retrieval, it's a dict. `player_role_lookup[player ckey]` will give you the role they play
var/list/player_role_lookup = list()
///what part of the game you're playing in. day phases, night phases, judgement phases, etc.
var/phase = MAFIA_PHASE_SETUP
///how long the game has gone on for, changes with every sunrise. day one, night one, day two, etc.
var/turn = 0
- ///for debugging and testing a full game, or adminbuse. If this is not null, it will use this as a setup. clears when game is over
+ ///for debugging and testing a full game, or adminbuse. If this is not empty, it will use this as a setup. clears when game is over
var/list/custom_setup = list()
///first day has no voting, and thus is shorter
var/first_day_phase_period = 20 SECONDS
@@ -82,20 +82,20 @@
qdel(map_deleter)
/**
- * Triggers at beginning of the game when there is a confirmed list of valid, ready players.
- * Creates a 100% ready game that has NOT started (no players in bodies)
- * Followed by start game
- *
- * Does the following:
- * * Picks map, and loads it
- * * Grabs landmarks if it is the first time it's loading
- * * Sets up the role list
- * * Puts players in each role randomly
- * Arguments:
- * * setup_list: list of all the datum setups (fancy list of roles) that would work for the game
- * * ready_players: list of filtered, sane players (so not playing or disconnected) for the game to put into roles
- */
-/datum/mafia_controller/proc/prepare_game(setup_list, ready_players)
+ * Triggers at beginning of the game when there is a confirmed list of valid, ready players.
+ * Creates a 100% ready game that has NOT started (no players in bodies)
+ * Followed by start game
+ *
+ * Does the following:
+ * * Picks map, and loads it
+ * * Grabs landmarks if it is the first time it's loading
+ * * Sets up the role list
+ * * Puts players in each role randomly
+ * Arguments:
+ * * setup_list: list of all the datum setups (fancy list of roles) that would work for the game
+ * * ready_players: list of filtered, sane players (so not playing or disconnected) for the game to put into roles
+ */
+/datum/mafia_controller/proc/prepare_game(setup_list,ready_players)
var/list/possible_maps = subtypesof(/datum/map_template/mafia)
var/turf/spawn_area = get_turf(locate(/obj/effect/landmark/mafia_game_area) in GLOB.landmarks_list)
@@ -166,23 +166,23 @@
to_chat(M, "[link] MAFIA: [msg] [team_suffix]")
/**
- * The game by this point is now all set up, and so we can put people in their bodies and start the first phase.
- *
- * Does the following:
- * * Creates bodies for all of the roles with the first proc
- * * Starts the first day manually (so no timer) with the second proc
- */
+ * The game by this point is now all set up, and so we can put people in their bodies and start the first phase.
+ *
+ * Does the following:
+ * * Creates bodies for all of the roles with the first proc
+ * * Starts the first day manually (so no timer) with the second proc
+ */
/datum/mafia_controller/proc/start_game()
create_bodies()
start_day()
/**
- * How every day starts.
- *
- * What players do in this phase:
- * * If day one, just a small starting period to see who is in the game and check role, leading to the night phase.
- * * Otherwise, it's a longer period used to discuss events that happened during the night, leading to the voting phase.
- */
+ * How every day starts.
+ *
+ * What players do in this phase:
+ * * If day one, just a small starting period to see who is in the game and check role, leading to the night phase.
+ * * Otherwise, it's a longer period used to discuss events that happened during the night, leading to the voting phase.
+ */
/datum/mafia_controller/proc/start_day()
turn += 1
phase = MAFIA_PHASE_DAY
@@ -198,12 +198,12 @@
SStgui.update_uis(src)
/**
- * Players have finished the discussion period, and now must put up someone to the chopping block.
- *
- * What players do in this phase:
- * * Vote on which player to put up for lynching, leading to the judgement phase.
- * * If no votes are case, the judgement phase is skipped, leading to the night phase.
- */
+ * Players have finished the discussion period, and now must put up someone to the chopping block.
+ *
+ * What players do in this phase:
+ * * Vote on which player to put up for lynching, leading to the judgement phase.
+ * * If no votes are case, the judgement phase is skipped, leading to the night phase.
+ */
/datum/mafia_controller/proc/start_voting_phase()
phase = MAFIA_PHASE_VOTING
next_phase_timer = addtimer(CALLBACK(src, .proc/check_trial, TRUE),voting_phase_period,TIMER_STOPPABLE) //be verbose!
@@ -211,21 +211,21 @@
SStgui.update_uis(src)
/**
- * Players have voted someone up, and now the person must defend themselves while the town votes innocent or guilty.
- *
- * What players do in this phase:
- * * Vote innocent or guilty, if they are not on trial.
- * * Defend themselves and wait for judgement, if they are.
- * * Leads to the lynch phase.
- * Arguments:
- * * verbose: boolean, announces whether there were votes or not. after judgement it goes back here with no voting period to end the day.
- */
+ * Players have voted someone up, and now the person must defend themselves while the town votes innocent or guilty.
+ *
+ * What players do in this phase:
+ * * Vote innocent or guilty, if they are not on trial.
+ * * Defend themselves and wait for judgement, if they are.
+ * * Leads to the lynch phase.
+ * Arguments:
+ * * verbose: boolean, announces whether there were votes or not. after judgement it goes back here with no voting period to end the day.
+ */
/datum/mafia_controller/proc/check_trial(verbose = TRUE)
var/datum/mafia_role/loser = get_vote_winner("Day")//, majority_of_town = TRUE)
- // var/loser_votes = get_vote_count(loser,"Day")
+ var/loser_votes = get_vote_count(loser,"Day")
if(loser)
- // if(loser_votes > 12)
- // loser.body.client?.give_award(/datum/award/achievement/mafia/universally_hated, loser.body)
+ if(loser_votes > 12)
+ award_role(/datum/award/achievement/mafia/universally_hated, loser)
send_message("[loser.body.real_name] wins the day vote, Listen to their defense and vote \"INNOCENT\" or \"GUILTY\"!")
//refresh the lists
judgement_abstain_votes = list()
@@ -248,12 +248,12 @@
SStgui.update_uis(src)
/**
- * Players have voted innocent or guilty on the person on trial, and that person is now killed or returned home.
- *
- * What players do in this phase:
- * * r/watchpeopledie
- * * If the accused is killed, their true role is revealed to the rest of the players.
- */
+ * Players have voted innocent or guilty on the person on trial, and that person is now killed or returned home.
+ *
+ * What players do in this phase:
+ * * r/watchpeopledie
+ * * If the accused is killed, their true role is revealed to the rest of the players.
+ */
/datum/mafia_controller/proc/lynch()
for(var/i in judgement_innocent_votes)
var/datum/mafia_role/role = i
@@ -276,25 +276,25 @@
next_phase_timer = addtimer(CALLBACK(src, .proc/check_trial, FALSE),judgement_lynch_period,TIMER_STOPPABLE)// small pause to see the guy dead, no verbosity since we already did this
/**
- * Teenie helper proc to move players back to their home.
- * Used in the above, but also used in the debug button "send all players home"
- * Arguments:
- * * role: mafia role that is getting sent back to the game.
- */
+ * Teenie helper proc to move players back to their home.
+ * Used in the above, but also used in the debug button "send all players home"
+ * Arguments:
+ * * role: mafia role that is getting sent back to the game.
+ */
/datum/mafia_controller/proc/send_home(datum/mafia_role/role)
role.body.forceMove(get_turf(role.assigned_landmark))
/**
- * Checks to see if a faction (or solo antagonist) has won.
- *
- * Calculates in this order:
- * * counts up town, mafia, and solo
- * * solos can count as town members for the purposes of mafia winning
- * * sends the amount of living people to the solo antagonists, and see if they won OR block the victory of the teams
- * * checks if solos won from above, then if town, then if mafia
- * * starts the end of the game if a faction won
- * * returns TRUE if someone won the game, halting other procs from continuing in the case of a victory
- */
+ * Checks to see if a faction (or solo antagonist) has won.
+ *
+ * Calculates in this order:
+ * * counts up town, mafia, and solo
+ * * solos can count as town members for the purposes of mafia winning
+ * * sends the amount of living people to the solo antagonists, and see if they won OR block the victory of the teams
+ * * checks if solos won from above, then if town, then if mafia
+ * * starts the end of the game if a faction won
+ * * returns TRUE if someone won the game, halting other procs from continuing in the case of a victory
+ */
/datum/mafia_controller/proc/check_victory()
//needed for achievements
var/list/total_town = list()
@@ -336,8 +336,7 @@
var/solo_end = FALSE
for(var/datum/mafia_role/winner in total_victors)
send_message("!! [uppertext(winner.name)] VICTORY !!")
- // var/client/winner_client = GLOB.directory[winner.player_key]
- // winner_client?.give_award(winner.winner_award, winner.body)
+ award_role(winner.winner_award, winner)
solo_end = TRUE
if(solo_end)
start_the_end()
@@ -345,28 +344,39 @@
if(blocked_victory)
return FALSE
if(alive_mafia == 0)
- // for(var/datum/mafia_role/townie in total_town)
- // var/client/townie_client = GLOB.directory[townie.player_key]
- // townie_client?.give_award(townie.winner_award, townie.body)
+ for(var/datum/mafia_role/townie in total_town)
+ award_role(townie.winner_award, townie)
start_the_end("!! TOWN VICTORY !!")
return TRUE
else if(alive_mafia >= alive_town) //guess could change if town nightkill is added
start_the_end("!! MAFIA VICTORY !!")
- // for(var/datum/mafia_role/changeling in total_mafia)
- // var/client/changeling_client = GLOB.directory[changeling.player_key]
- // changeling_client?.give_award(changeling.winner_award, changeling.body)
+ for(var/datum/mafia_role/changeling in total_mafia)
+ award_role(changeling.winner_award, changeling)
return TRUE
/**
- * The end of the game is in two procs, because we want a bit of time for players to see eachothers roles.
- * Because of how check_victory works, the game is halted in other places by this point.
- *
- * What players do in this phase:
- * * See everyone's role postgame
- * * See who won the game
- * Arguments:
- * * message: string, if non-null it sends it to all players. used to announce team victories while solos are handled in check victory
- */
+ * Lets the game award roles with all their checks and sanity, prevents achievements given out for debug games
+ *
+ * Arguments:
+ * * award: path of the award
+ * * role: mafia_role datum to reward.
+ */
+/datum/mafia_controller/proc/award_role(award, datum/mafia_role/rewarded)
+ if(custom_setup.len)
+ return
+ var/client/role_client = GLOB.directory[rewarded.player_key]
+ role_client?.give_award(award, rewarded.body)
+
+/**
+ * The end of the game is in two procs, because we want a bit of time for players to see eachothers roles.
+ * Because of how check_victory works, the game is halted in other places by this point.
+ *
+ * What players do in this phase:
+ * * See everyone's role postgame
+ * * See who won the game
+ * Arguments:
+ * * message: string, if non-null it sends it to all players. used to announce team victories while solos are handled in check victory
+ */
/datum/mafia_controller/proc/start_the_end(message)
SEND_SIGNAL(src,COMSIG_MAFIA_GAME_END)
if(message)
@@ -377,8 +387,8 @@
next_phase_timer = addtimer(CALLBACK(src,.proc/end_game),victory_lap_period,TIMER_STOPPABLE)
/**
- * Cleans up the game, resetting variables back to the beginning and removing the map with the generator.
- */
+ * Cleans up the game, resetting variables back to the beginning and removing the map with the generator.
+ */
/datum/mafia_controller/proc/end_game()
map_deleter.generate() //remove the map, it will be loaded at the start of the next one
QDEL_LIST(all_roles)
@@ -392,17 +402,17 @@
phase = MAFIA_PHASE_SETUP
/**
- * After the voting and judgement phases, the game goes to night shutting the windows and beginning night with a proc.
- */
+ * After the voting and judgement phases, the game goes to night shutting the windows and beginning night with a proc.
+ */
/datum/mafia_controller/proc/lockdown()
toggle_night_curtains(close=TRUE)
start_night()
/**
- * Shuts poddoors attached to mafia.
- * Arguments:
- * * close: boolean, the state you want the curtains in.
- */
+ * Shuts poddoors attached to mafia.
+ * Arguments:
+ * * close: boolean, the state you want the curtains in.
+ */
/datum/mafia_controller/proc/toggle_night_curtains(close)
for(var/obj/machinery/door/poddoor/D in GLOB.machines) //I really dislike pathing of these
if(D.id != "mafia") //so as to not trigger shutters on station, lol
@@ -413,12 +423,12 @@
INVOKE_ASYNC(D, /obj/machinery/door/poddoor.proc/open)
/**
- * The actual start of night for players. Mostly info is given at the start of the night as the end of the night is when votes and actions are submitted and tried.
- *
- * What players do in this phase:
- * * Mafia are told to begin voting on who to kill
- * * Powers that are picked during the day announce themselves right now
- */
+ * The actual start of night for players. Mostly info is given at the start of the night as the end of the night is when votes and actions are submitted and tried.
+ *
+ * What players do in this phase:
+ * * Mafia are told to begin voting on who to kill
+ * * Powers that are picked during the day announce themselves right now
+ */
/datum/mafia_controller/proc/start_night()
phase = MAFIA_PHASE_NIGHT
send_message("Night [turn] started! Lockdown will end in 45 seconds.")
@@ -427,16 +437,16 @@
SStgui.update_uis(src)
/**
- * The end of the night, and a series of signals for the order of events on a night.
- *
- * Order of events, and what they mean:
- * * Start of resolve (NIGHT_START) is for activating night abilities that MUST go first
- * * Action phase (NIGHT_ACTION_PHASE) is for non-lethal day abilities
- * * Mafia then tallies votes and kills the highest voted person (note: one random voter visits that person for the purposes of roleblocking)
- * * Killing phase (NIGHT_KILL_PHASE) is for lethal night abilities
- * * End of resolve (NIGHT_END) is for cleaning up abilities that went off and i guess doing some that must go last
- * * Finally opens the curtains and calls the start of day phase, completing the cycle until check victory returns TRUE
- */
+ * The end of the night, and a series of signals for the order of events on a night.
+ *
+ * Order of events, and what they mean:
+ * * Start of resolve (NIGHT_START) is for activating night abilities that MUST go first
+ * * Action phase (NIGHT_ACTION_PHASE) is for non-lethal day abilities
+ * * Mafia then tallies votes and kills the highest voted person (note: one random voter visits that person for the purposes of roleblocking)
+ * * Killing phase (NIGHT_KILL_PHASE) is for lethal night abilities
+ * * End of resolve (NIGHT_END) is for cleaning up abilities that went off and i guess doing some that must go last
+ * * Finally opens the curtains and calls the start of day phase, completing the cycle until check victory returns TRUE
+ */
/datum/mafia_controller/proc/resolve_night()
SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_START)
SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_ACTION_PHASE)
@@ -457,15 +467,15 @@
SStgui.update_uis(src)
/**
- * Proc that goes off when players vote for something with their mafia panel.
- *
- * If teams, it hides the tally overlay and only sends the vote messages to the team that is voting
- * Arguments:
- * * voter: the mafia role that is trying to vote for...
- * * target: the mafia role that is getting voted for
- * * vote_type: type of vote submitted (is this the day vote? is this the mafia night vote?)
- * * teams: see mafia team defines for what to put in, makes the messages only send to a specific team (so mafia night votes only sending messages to mafia at night)
- */
+ * Proc that goes off when players vote for something with their mafia panel.
+ *
+ * If teams, it hides the tally overlay and only sends the vote messages to the team that is voting
+ * Arguments:
+ * * voter: the mafia role that is trying to vote for...
+ * * target: the mafia role that is getting voted for
+ * * vote_type: type of vote submitted (is this the day vote? is this the mafia night vote?)
+ * * teams: see mafia team defines for what to put in, makes the messages only send to a specific team (so mafia night votes only sending messages to mafia at night)
+ */
/datum/mafia_controller/proc/vote_for(datum/mafia_role/voter,datum/mafia_role/target,vote_type, teams)
if(!votes[vote_type])
votes[vote_type] = list()
@@ -485,8 +495,8 @@
old.body.update_icon()
/**
- * Clears out the votes of a certain type (day votes, mafia kill votes) while leaving others untouched
- */
+ * Clears out the votes of a certain type (day votes, mafia kill votes) while leaving others untouched
+ */
/datum/mafia_controller/proc/reset_votes(vote_type)
var/list/bodies_to_update = list()
for(var/vote in votes[vote_type])
@@ -497,11 +507,11 @@
M.update_icon()
/**
- * Returns how many people voted for the role, in whatever vote (day vote, night kill vote)
- * Arguments:
- * * role: the mafia role the proc tries to get the amount of votes for
- * * vote_type: the vote type (getting how many day votes were for the role, or mafia night votes for the role)
- */
+ * Returns how many people voted for the role, in whatever vote (day vote, night kill vote)
+ * Arguments:
+ * * role: the mafia role the proc tries to get the amount of votes for
+ * * vote_type: the vote type (getting how many day votes were for the role, or mafia night votes for the role)
+ */
/datum/mafia_controller/proc/get_vote_count(role,vote_type)
. = 0
for(var/v in votes[vote_type])
@@ -510,11 +520,11 @@
. += votee.vote_power
/**
- * Returns whichever role got the most votes, in whatever vote (day vote, night kill vote)
- * returns null if no votes
- * Arguments:
- * * vote_type: the vote type (getting the role that got the most day votes, or the role that got the most mafia votes)
- */
+ * Returns whichever role got the most votes, in whatever vote (day vote, night kill vote)
+ * returns null if no votes
+ * Arguments:
+ * * vote_type: the vote type (getting the role that got the most day votes, or the role that got the most mafia votes)
+ */
/datum/mafia_controller/proc/get_vote_winner(vote_type)
var/list/tally = list()
for(var/votee in votes[vote_type])
@@ -526,21 +536,23 @@
return length(tally) ? tally[1] : null
/**
- * Returns a random person who voted for whatever vote (day vote, night kill vote)
- * Arguments:
- * * vote_type: vote type (getting a random day voter, or mafia night voter)
- */
+ * Returns a random person who voted for whatever vote (day vote, night kill vote)
+ * Arguments:
+ * * vote_type: vote type (getting a random day voter, or mafia night voter)
+ */
/datum/mafia_controller/proc/get_random_voter(vote_type)
if(length(votes[vote_type]))
return pick(votes[vote_type])
/**
- * Adds mutable appearances to people who get publicly voted on (so not night votes) showing how many people are picking them
- * Arguments:
- * * source: the body of the role getting the overlays
- * * overlay_list: signal var passing the overlay list of the mob
- */
+ * Adds mutable appearances to people who get publicly voted on (so not night votes) showing how many people are picking them
+ * Arguments:
+ * * source: the body of the role getting the overlays
+ * * overlay_list: signal var passing the overlay list of the mob
+ */
/datum/mafia_controller/proc/display_votes(atom/source, list/overlay_list)
+ SIGNAL_HANDLER
+
if(phase != MAFIA_PHASE_VOTING)
return
var/v = get_vote_count(player_role_lookup[source],"Day")
@@ -548,14 +560,14 @@
overlay_list += MA
/**
- * Called when the game is setting up, AFTER map is loaded but BEFORE the phase timers start. Creates and places each role's body and gives the correct player key
- *
- * Notably:
- * * Toggles godmode so the mafia players cannot kill themselves
- * * Adds signals for voting overlays, see display_votes proc
- * * gives mafia panel
- * * sends the greeting text (goals, role name, etc)
- */
+ * Called when the game is setting up, AFTER map is loaded but BEFORE the phase timers start. Creates and places each role's body and gives the correct player key
+ *
+ * Notably:
+ * * Toggles godmode so the mafia players cannot kill themselves
+ * * Adds signals for voting overlays, see display_votes proc
+ * * gives mafia panel
+ * * sends the greeting text (goals, role name, etc)
+ */
/datum/mafia_controller/proc/create_bodies()
for(var/datum/mafia_role/role in all_roles)
var/mob/living/carbon/human/H = new(get_turf(role.assigned_landmark))
@@ -716,13 +728,14 @@
if(GLOB.mafia_signup[C.ckey])
GLOB.mafia_signup -= C.ckey
to_chat(usr, "You unregister from Mafia.")
- return
+ return TRUE
else
GLOB.mafia_signup[C.ckey] = C
to_chat(usr, "You sign up for Mafia.")
if(phase == MAFIA_PHASE_SETUP)
check_signups()
try_autostart()
+ return TRUE
if("mf_spectate")
if(C.ckey in spectators)
to_chat(usr, "You will no longer get messages from the game.")
@@ -730,6 +743,7 @@
else
to_chat(usr, "You will now get messages from the game.")
spectators += C.ckey
+ return TRUE
if(user_role.game_status == MAFIA_DEAD)
return
//User actions (just living)
@@ -800,13 +814,13 @@
. += L[key]
/**
- * Returns a semirandom setup, with...
- * Town, Two invest roles, one protect role, sometimes a misc role, and the rest assistants for town.
- * Mafia, 2 normal mafia and one special.
- * Neutral, two disruption roles, sometimes one is a killing.
- *
- * See _defines.dm in the mafia folder for a rundown on what these groups of roles include.
- */
+ * Returns a semirandom setup, with...
+ * Town, Two invest roles, one protect role, sometimes a misc role, and the rest assistants for town.
+ * Mafia, 2 normal mafia and one special.
+ * Neutral, two disruption roles, sometimes one is a killing.
+ *
+ * See _defines.dm in the mafia folder for a rundown on what these groups of roles include.
+ */
/datum/mafia_controller/proc/generate_random_setup()
var/invests_left = 2
var/protects_left = 1
@@ -845,8 +859,8 @@
return random_setup
/**
- * Helper proc that adds a random role of a type to a setup. if it doesn't exist in the setup, it adds the path to the list and otherwise bumps the path in the list up one
- */
+ * Helper proc that adds a random role of a type to a setup. if it doesn't exist in the setup, it adds the path to the list and otherwise bumps the path in the list up one
+ */
/datum/mafia_controller/proc/add_setup_role(setup_list, wanted_role_type)
var/list/role_type_paths = list()
for(var/path in typesof(/datum/mafia_role))
@@ -868,17 +882,17 @@
setup_list[mafia_path] = 1
/**
- * Called when enough players have signed up to fill a setup. DOESN'T NECESSARILY MEAN THE GAME WILL START.
- *
- * Checks for a custom setup, if so gets the required players from that and if not it sets the player requirement to required_player(max_player) and generates one IF basic setup starts a game.
- * Checks if everyone signed up is an observer, and is still connected. If people aren't, they're removed from the list.
- * If there aren't enough players post sanity, it aborts. otherwise, it selects enough people for the game and starts preparing the game for real.
- */
+ * Called when enough players have signed up to fill a setup. DOESN'T NECESSARILY MEAN THE GAME WILL START.
+ *
+ * Checks for a custom setup, if so gets the required players from that and if not it sets the player requirement to MAFIA_MAX_PLAYER_COUNT and generates one IF basic setup starts a game.
+ * Checks if everyone signed up is an observer, and is still connected. If people aren't, they're removed from the list.
+ * If there aren't enough players post sanity, it aborts. otherwise, it selects enough people for the game and starts preparing the game for real.
+ */
/datum/mafia_controller/proc/basic_setup()
var/req_players
var/list/setup = custom_setup
if(!setup.len)
- req_players = required_player //max_player
+ req_players = max_player //MAFIA_MAX_PLAYER_COUNT
else
req_players = assoc_value_sum(setup)
@@ -918,10 +932,10 @@
start_game()
/**
- * Called when someone signs up, and sees if there are enough people in the signup list to begin.
- *
- * Only checks if everyone is actually valid to start (still connected and an observer) if there are enough players (basic_setup)
- */
+ * Called when someone signs up, and sees if there are enough people in the signup list to begin.
+ *
+ * Only checks if everyone is actually valid to start (still connected and an observer) if there are enough players (basic_setup)
+ */
/datum/mafia_controller/proc/try_autostart()
if(phase != MAFIA_PHASE_SETUP) // || !(GLOB.ghost_role_flags & GHOSTROLE_MINIGAME))
return
@@ -929,10 +943,10 @@
basic_setup()
/**
- * Filters inactive player into a different list until they reconnect, and removes players who are no longer ghosts.
- *
- * If a disconnected player gets a non-ghost mob and reconnects, they will be first put back into mafia_signup then filtered by that.
- */
+ * Filters inactive player into a different list until they reconnect, and removes players who are no longer ghosts.
+ *
+ * If a disconnected player gets a non-ghost mob and reconnects, they will be first put back into mafia_signup then filtered by that.
+ */
/datum/mafia_controller/proc/check_signups()
for(var/bad_key in GLOB.mafia_bad_signup)
if(GLOB.directory[bad_key])//they have reconnected if we can search their key and get a client
@@ -962,8 +976,8 @@
parent.ui_interact(owner)
/**
- * Creates the global datum for playing mafia games, destroys the last if that's required and returns the new.
- */
+ * Creates the global datum for playing mafia games, destroys the last if that's required and returns the new.
+ */
/proc/create_mafia_game()
if(GLOB.mafia_game)
QDEL_NULL(GLOB.mafia_game)
diff --git a/code/modules/mafia/roles.dm b/code/modules/mafia/roles.dm
index 2461a93976..34cbd75bfc 100644
--- a/code/modules/mafia/roles.dm
+++ b/code/modules/mafia/roles.dm
@@ -19,7 +19,7 @@
var/list/actions = list()
var/list/targeted_actions = list()
//what the role gets when it wins a game
- // var/winner_award = /datum/award/achievement/mafia/assistant
+ var/winner_award = /datum/award/achievement/mafia/assistant
//so mafia have to also kill them to have a majority
var/solo_counts_as_town = FALSE //(don't set this for town)
@@ -124,7 +124,7 @@
desc = "You can investigate a single person each night to learn their team."
revealed_outfit = /datum/outfit/mafia/detective
role_type = TOWN_INVEST
- // winner_award = /datum/award/achievement/mafia/detective
+ winner_award = /datum/award/achievement/mafia/detective
hud_icon = "huddetective"
revealed_icon = "detective"
@@ -151,6 +151,8 @@
current_investigation = target
/datum/mafia_role/detective/proc/investigate(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
var/datum/mafia_role/target = current_investigation
if(target)
if(target.detect_immune)
@@ -178,7 +180,7 @@
desc = "You can visit someone ONCE PER GAME to reveal their true role in the morning!"
revealed_outfit = /datum/outfit/mafia/psychologist
role_type = TOWN_INVEST
- // winner_award = /datum/award/achievement/mafia/psychologist
+ winner_award = /datum/award/achievement/mafia/psychologist
hud_icon = "hudpsychologist"
revealed_icon = "psychologist"
@@ -202,6 +204,8 @@
current_target = target
/datum/mafia_role/psychologist/proc/therapy_reveal(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"reveal",current_target) & MAFIA_PREVENT_ACTION || game_status != MAFIA_ALIVE) //Got lynched or roleblocked by a lawyer.
current_target = null
if(current_target)
@@ -218,7 +222,7 @@
role_type = TOWN_INVEST
hud_icon = "hudchaplain"
revealed_icon = "chaplain"
- // winner_award = /datum/award/achievement/mafia/chaplain
+ winner_award = /datum/award/achievement/mafia/chaplain
targeted_actions = list("Pray")
var/current_target
@@ -238,6 +242,8 @@
current_target = target
/datum/mafia_role/chaplain/proc/commune(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
var/datum/mafia_role/target = current_target
if(target)
to_chat(body,"You invoke spirit of [target.body.real_name] and learn their role was [target.name].")
@@ -251,7 +257,7 @@
role_type = TOWN_PROTECT
hud_icon = "hudmedicaldoctor"
revealed_icon = "medicaldoctor"
- // winner_award = /datum/award/achievement/mafia/md
+ winner_award = /datum/award/achievement/mafia/md
targeted_actions = list("Protect")
var/datum/mafia_role/current_protected
@@ -277,16 +283,22 @@
current_protected = target
/datum/mafia_role/md/proc/protect(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
if(current_protected)
RegisterSignal(current_protected,COMSIG_MAFIA_ON_KILL,.proc/prevent_kill)
add_note("N[game.turn] - Protected [current_protected.body.real_name]")
/datum/mafia_role/md/proc/prevent_kill(datum/source)
+ SIGNAL_HANDLER
+
to_chat(body,"The person you protected tonight was attacked!")
to_chat(current_protected.body,"You were attacked last night, but someone nursed you back to life!")
return MAFIA_PREVENT_KILL
/datum/mafia_role/md/proc/end_protection(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
if(current_protected)
UnregisterSignal(current_protected,COMSIG_MAFIA_ON_KILL)
current_protected = null
@@ -298,7 +310,7 @@
role_type = TOWN_PROTECT
hud_icon = "hudlawyer"
revealed_icon = "lawyer"
- // winner_award = /datum/award/achievement/mafia/lawyer
+ winner_award = /datum/award/achievement/mafia/lawyer
targeted_actions = list("Advise")
var/datum/mafia_role/current_target
@@ -310,6 +322,8 @@
RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/release)
/datum/mafia_role/lawyer/proc/roleblock_text(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"roleblock",current_target) & MAFIA_PREVENT_ACTION || game_status != MAFIA_ALIVE) //Got lynched or roleblocked by another lawyer.
current_target = null
if(current_target)
@@ -335,16 +349,22 @@
to_chat(body,"You will block [target.body.real_name] tonight.")
/datum/mafia_role/lawyer/proc/try_to_roleblock(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
if(current_target)
RegisterSignal(current_target,COMSIG_MAFIA_CAN_PERFORM_ACTION, .proc/prevent_action)
/datum/mafia_role/lawyer/proc/release(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
. = ..()
if(current_target)
UnregisterSignal(current_target, COMSIG_MAFIA_CAN_PERFORM_ACTION)
current_target = null
/datum/mafia_role/lawyer/proc/prevent_action(datum/source)
+ SIGNAL_HANDLER
+
if(game_status == MAFIA_ALIVE) //in case we got killed while imprisoning sk - bad luck edge
return MAFIA_PREVENT_ACTION
@@ -355,7 +375,7 @@
role_type = TOWN_MISC
hud_icon = "hudheadofpersonnel"
revealed_icon = "headofpersonnel"
- // winner_award = /datum/award/achievement/mafia/hop
+ winner_award = /datum/award/achievement/mafia/hop
targeted_actions = list("Reveal")
@@ -378,7 +398,7 @@
role_type = MAFIA_REGULAR
hud_icon = "hudchangeling"
revealed_icon = "changeling"
- // winner_award = /datum/award/achievement/mafia/changeling
+ winner_award = /datum/award/achievement/mafia/changeling
revealed_outfit = /datum/outfit/mafia/changeling
special_theme = "syndicate"
@@ -389,6 +409,8 @@
RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/mafia_text)
/datum/mafia_role/mafia/proc/mafia_text(datum/mafia_controller/source)
+ SIGNAL_HANDLER
+
to_chat(body,"Vote for who to kill tonight. The killer will be chosen randomly from voters.")
//better detective for mafia
@@ -398,7 +420,7 @@
role_type = MAFIA_SPECIAL
hud_icon = "hudthoughtfeeder"
revealed_icon = "thoughtfeeder"
- // winner_award = /datum/award/achievement/mafia/thoughtfeeder
+ winner_award = /datum/award/achievement/mafia/thoughtfeeder
targeted_actions = list("Learn Role")
var/datum/mafia_role/current_investigation
@@ -418,6 +440,8 @@
current_investigation = target
/datum/mafia_role/mafia/thoughtfeeder/proc/investigate(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
var/datum/mafia_role/target = current_investigation
current_investigation = null
if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"thoughtfeed",target) & MAFIA_PREVENT_ACTION)
@@ -441,7 +465,7 @@
win_condition = "kill everyone."
team = MAFIA_TEAM_SOLO
role_type = NEUTRAL_KILL
- // winner_award = /datum/award/achievement/mafia/traitor
+ winner_award = /datum/award/achievement/mafia/traitor
targeted_actions = list("Night Kill")
revealed_outfit = /datum/outfit/mafia/traitor
@@ -464,6 +488,8 @@
return TRUE //while alive, town AND mafia cannot win (though since mafia know who is who it's pretty easy to win from that point)
/datum/mafia_role/traitor/proc/nightkill_immunity(datum/source,datum/mafia_controller/game,lynch)
+ SIGNAL_HANDLER
+
if(game.phase == MAFIA_PHASE_NIGHT && !lynch)
to_chat(body,"You were attacked, but they'll have to try harder than that to put you down.")
return MAFIA_PREVENT_KILL
@@ -481,6 +507,8 @@
to_chat(body,"You will attempt to kill [target.body.real_name] tonight.")
/datum/mafia_role/traitor/proc/try_to_kill(datum/mafia_controller/source)
+ SIGNAL_HANDLER
+
var/datum/mafia_role/target = current_victim
current_victim = null
if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,source,"traitor kill",target) & MAFIA_PREVENT_ACTION)
@@ -500,7 +528,7 @@
special_theme = "neutral"
hud_icon = "hudnightmare"
revealed_icon = "nightmare"
- // winner_award = /datum/award/achievement/mafia/nightmare
+ winner_award = /datum/award/achievement/mafia/nightmare
targeted_actions = list("Flicker", "Hunt")
var/list/flickering = list()
@@ -543,6 +571,8 @@
to_chat(body,"You will hunt everyone in a flickering room down tonight.")
/datum/mafia_role/nightmare/proc/flicker_or_hunt(datum/mafia_controller/source)
+ SIGNAL_HANDLER
+
if(game_status != MAFIA_ALIVE || !flicker_target)
return
if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,source,"nightmare actions",flicker_target) & MAFIA_PREVENT_ACTION)
@@ -576,7 +606,7 @@
special_theme = "neutral"
hud_icon = "hudfugitive"
revealed_icon = "fugitive"
- // winner_award = /datum/award/achievement/mafia/fugitive
+ winner_award = /datum/award/achievement/mafia/fugitive
actions = list("Self Preservation")
var/charges = 2
@@ -604,11 +634,15 @@
protection_status = !protection_status
/datum/mafia_role/fugitive/proc/night_start(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
if(protection_status == FUGITIVE_WILL_PRESERVE)
to_chat(body,"Your preparations are complete. Nothing could kill you tonight!")
RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/prevent_death)
/datum/mafia_role/fugitive/proc/night_end(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
if(protection_status == FUGITIVE_WILL_PRESERVE)
charges--
UnregisterSignal(src,COMSIG_MAFIA_ON_KILL)
@@ -616,13 +650,16 @@
protection_status = FUGITIVE_NOT_PRESERVING
/datum/mafia_role/fugitive/proc/prevent_death(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
to_chat(body,"You were attacked! Luckily, you were ready for this!")
return MAFIA_PREVENT_KILL
/datum/mafia_role/fugitive/proc/survived(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
if(game_status == MAFIA_ALIVE)
- // var/client/winner_client = GLOB.directory[player_key]
- // winner_client?.give_award(winner_award, body)
+ game.award_role(winner_award, src)
game.send_message("!! FUGITIVE VICTORY !!")
#undef FUGITIVE_NOT_PRESERVING
@@ -640,7 +677,7 @@
hud_icon = "hudobsessed"
revealed_icon = "obsessed"
- // winner_award = /datum/award/achievement/mafia/obsessed
+ winner_award = /datum/award/achievement/mafia/obsessed
revealed_outfit = /datum/outfit/mafia/obsessed // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these)
solo_counts_as_town = TRUE //after winning or whatever, can side with whoever. they've already done their objective!
@@ -652,6 +689,8 @@
RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/find_obsession)
/datum/mafia_role/obsessed/proc/find_obsession(datum/mafia_controller/game)
+ SIGNAL_HANDLER
+
var/list/all_roles_shuffle = shuffle(game.all_roles)
for(var/role in all_roles_shuffle)
var/datum/mafia_role/possible = role
@@ -667,13 +706,14 @@
UnregisterSignal(game,COMSIG_MAFIA_SUNDOWN)
/datum/mafia_role/obsessed/proc/check_victory(datum/source,datum/mafia_controller/game,lynch)
+ SIGNAL_HANDLER
+
UnregisterSignal(source,COMSIG_MAFIA_ON_KILL)
if(game_status == MAFIA_DEAD)
return
if(lynch)
game.send_message("!! OBSESSED VICTORY !!")
- // var/client/winner_client = GLOB.directory[player_key]
- // winner_client?.give_award(winner_award, body)
+ game.award_role(winner_award, src)
reveal_role(game, FALSE)
else
to_chat(body, "You have failed your objective to lynch [obsession.body]!")
@@ -689,17 +729,18 @@
special_theme = "neutral"
hud_icon = "hudclown"
revealed_icon = "clown"
- // winner_award = /datum/award/achievement/mafia/clown
+ winner_award = /datum/award/achievement/mafia/clown
/datum/mafia_role/clown/New(datum/mafia_controller/game)
. = ..()
RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/prank)
/datum/mafia_role/clown/proc/prank(datum/source,datum/mafia_controller/game,lynch)
+ SIGNAL_HANDLER
+
if(lynch)
var/datum/mafia_role/victim = pick(game.judgement_guilty_votes + game.judgement_abstain_votes)
game.send_message("[body.real_name] WAS A CLOWN! HONK! They take down [victim.body.real_name] with their last prank.")
game.send_message("!! CLOWN VICTORY !!")
- // var/client/winner_client = GLOB.directory[player_key]
- // winner_client?.give_award(winner_award, body)
+ game.award_role(winner_award, src)
victim.kill(game,FALSE)
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index a82151cc1d..50f2922df7 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -75,6 +75,7 @@
var/lastpuke = 0
var/account_id
var/last_fire_update
+ var/hardcore_survival_score = 0
/// Unarmed parry data for human
/datum/block_parry_data/unarmed/human
diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
index e7c5644e26..deb3a101e0 100644
--- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
@@ -14,8 +14,8 @@
model = "Cleanbot"
bot_core_type = /obj/machinery/bot_core/cleanbot
window_id = "autoclean"
- window_name = "Automatic Station Cleaner v1.3"
- pass_flags = PASSMOB
+ window_name = "Automatic Station Cleaner v1.4"
+ pass_flags = PASSMOB // | PASSFLAPS
path_image_color = "#993299"
weather_immunities = list("lava","ash")
@@ -25,6 +25,7 @@
var/blood = 1
var/trash = 0
var/pests = 0
+ var/drawn = 0
var/list/target_types
var/obj/effect/decal/cleanable/target
@@ -53,6 +54,9 @@
var/list/prefixes
var/list/suffixes
+ var/ascended = FALSE // if we have all the top titles, grant achievements to living mobs that gaze upon our cleanbot god
+
+
/mob/living/simple_animal/bot/cleanbot/proc/deputize(obj/item/W, mob/user)
if(in_range(src, user))
to_chat(user, "You attach \the [W] to \the [src].")
@@ -66,6 +70,8 @@
/mob/living/simple_animal/bot/cleanbot/proc/update_titles()
var/working_title = ""
+ ascended = TRUE
+
for(var/pref in prefixes)
for(var/title in pref)
if(title in stolen_valor)
@@ -73,6 +79,8 @@
if(title in officers)
commissioned = TRUE
break
+ else
+ ascended = FALSE // we didn't have the first entry in the list if we got here, so we're not achievement worthy yet
working_title += chosen_name
@@ -81,6 +89,8 @@
if(title in stolen_valor)
working_title += " " + suf[title]
break
+ else
+ ascended = FALSE
name = working_title
@@ -89,8 +99,12 @@
if(weapon)
. += " Is that \a [weapon] taped to it...?"
+ if(ascended && user.stat == CONSCIOUS && user.client)
+ user.client.give_award(/datum/award/achievement/misc/cleanboss, user)
+
/mob/living/simple_animal/bot/cleanbot/Initialize()
. = ..()
+
chosen_name = name
get_targets()
icon_state = "cleanbot[on]"
@@ -98,7 +112,6 @@
var/datum/job/janitor/J = new/datum/job/janitor
access_card.access += J.get_access()
prev_access = access_card.access
-
stolen_valor = list()
prefixes = list(command, security, engineering)
@@ -123,7 +136,7 @@
/mob/living/simple_animal/bot/cleanbot/bot_reset()
..()
- if(weapon && (emagged == 2))
+ if(weapon && emagged == 2)
weapon.force = weapon_orig_force
ignore_list = list() //Allows the bot to clean targets it previously ignored due to being unreachable.
target = null
@@ -151,7 +164,7 @@
C.Knockdown(20)
/mob/living/simple_animal/bot/cleanbot/attackby(obj/item/W, mob/user, params)
- if(istype(W, /obj/item/card/id)||istype(W, /obj/item/pda))
+ if(W.GetID())
if(bot_core.allowed(user) && !open && !emagged)
locked = !locked
to_chat(user, "You [ locked ? "lock" : "unlock"] \the [src] behaviour controls.")
@@ -161,7 +174,7 @@
if(open)
to_chat(user, "Please close the access panel before locking it.")
else
- to_chat(user, "The [src] doesn't seem to respect your authority.")
+ to_chat(user, "\The [src] doesn't seem to respect your authority.")
else if(istype(W, /obj/item/kitchen/knife) && user.a_intent != INTENT_HARM)
to_chat(user, "You start attaching \the [W] to \the [src]...")
@@ -203,7 +216,8 @@
return ..()
/mob/living/simple_animal/bot/cleanbot/emag_act(mob/user)
- . = ..()
+ ..()
+
if(emagged == 2)
if(weapon)
weapon.force = weapon_orig_force
@@ -259,6 +273,9 @@
if(!target && trash) //Then for trash.
target = scan(/obj/item/trash)
+ // if(!target && trash) //Search for dead mices.
+ // target = scan(/obj/item/food/deadmouse)
+
if(!target && auto_patrol) //Search for cleanables it can see.
if(mode == BOT_IDLE || mode == BOT_START_PATROL)
start_patrol()
@@ -335,13 +352,18 @@
target_types += /mob/living/simple_animal/cockroach
target_types += /mob/living/simple_animal/mouse
+ if(drawn)
+ target_types += /obj/effect/decal/cleanable/crayon
+
if(trash)
target_types += /obj/item/trash
target_types += /obj/item/reagent_containers/food/snacks/meat/slab/human
target_types = typecacheof(target_types)
-/mob/living/simple_animal/bot/cleanbot/UnarmedAttack(atom/A, proximity, intent = a_intent, flags = NONE)
+/mob/living/simple_animal/bot/cleanbot/UnarmedAttack(atom/A)
+ if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
+ return
if(istype(A, /obj/effect/decal/cleanable))
anchored = TRUE
icon_state = "cleanbot-c"
@@ -361,8 +383,9 @@
icon_state = "cleanbot[on]"
else if(istype(A, /obj/item) || istype(A, /obj/effect/decal/remains))
visible_message("[src] sprays hydrofluoric acid at [A]!")
- playsound(src, 'sound/effects/spray2.ogg', 50, 1, -6)
+ playsound(src, 'sound/effects/spray2.ogg', 50, TRUE, -6)
A.acid_act(75, 10)
+ target = null
else if(istype(A, /mob/living/simple_animal/cockroach) || istype(A, /mob/living/simple_animal/mouse))
var/mob/living/simple_animal/M = target
if(!M.stat)
@@ -383,7 +406,7 @@
"FREED AT LEST FROM FILTHY PROGRAMMING.")
say(phrase)
victim.emote("scream")
- playsound(src.loc, 'sound/effects/spray2.ogg', 50, 1, -6)
+ playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE, -6)
victim.acid_act(5, 100)
else if(A == src) // Wets floors and spawns foam randomly
if(prob(75))
@@ -412,10 +435,14 @@
do_sparks(3, TRUE, src)
..()
+/mob/living/simple_animal/bot/cleanbot/medbay
+ name = "Scrubs, MD"
+ bot_core_type = /obj/machinery/bot_core/cleanbot/medbay
+ on = FALSE
+
/obj/machinery/bot_core/cleanbot
req_one_access = list(ACCESS_JANITOR, ACCESS_ROBOTICS)
-
/mob/living/simple_animal/bot/cleanbot/get_controls(mob/user)
var/dat
dat += hack(user)
@@ -424,9 +451,10 @@
Status: [on ? "On" : "Off"]
Behaviour controls are [locked ? "locked" : "unlocked"]
Maintenance panel panel is [open ? "opened" : "closed"]"})
- if(!locked || hasSiliconAccessInArea(user)|| IsAdminGhost(user))
+ if(!locked || issilicon(user)|| IsAdminGhost(user))
dat += "
Clean Blood: [blood ? "Yes" : "No"]"
dat += "
Clean Trash: [trash ? "Yes" : "No"]"
+ dat += "
Clean Graffiti: [drawn ? "Yes" : "No"]"
dat += "
Exterminate Pests: [pests ? "Yes" : "No"]"
dat += "
Patrol Station: [auto_patrol ? "Yes" : "No"]"
return dat
@@ -442,5 +470,10 @@ Maintenance panel panel is [open ? "opened" : "closed"]"})
pests = !pests
if("trash")
trash = !trash
+ if("drawn")
+ drawn = !drawn
get_targets()
update_controls()
+
+/obj/machinery/bot_core/cleanbot/medbay
+ req_one_access = list(ACCESS_JANITOR, ACCESS_ROBOTICS, ACCESS_MEDICAL)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
index 2a5f279386..ffad0ca3ee 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
@@ -44,6 +44,9 @@ Difficulty: Medium
wander = FALSE
del_on_death = TRUE
blood_volume = BLOOD_VOLUME_NORMAL
+ achievement_type = /datum/award/achievement/boss/blood_miner_kill
+ crusher_achievement_type = /datum/award/achievement/boss/blood_miner_crusher
+ score_achievement_type = /datum/award/score/blood_miner_score
medal_type = BOSS_MEDAL_MINER
var/obj/item/melee/transforming/cleaving_saw/miner/miner_saw
var/time_until_next_transform = 0
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index 519d6402e6..d5b78b14b6 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -51,8 +51,11 @@ Difficulty: Hard
crusher_loot = list(/obj/structure/closet/crate/necropolis/bubblegum/crusher)
loot = list(/obj/structure/closet/crate/necropolis/bubblegum)
var/charging = 0
- medal_type = BOSS_MEDAL_BUBBLEGUM
- score_type = BUBBLEGUM_SCORE
+
+ achievement_type = /datum/award/achievement/boss/bubblegum_kill
+ crusher_achievement_type = /datum/award/achievement/boss/bubblegum_crusher
+ score_achievement_type = /datum/award/score/bubblegum_score
+
deathmessage = "sinks into a pool of blood, fleeing the battle. You've won, for now... "
death_sound = 'sound/magic/enter_blood.ogg'
@@ -153,6 +156,20 @@ Difficulty: Hard
charging = 0
Goto(target, move_to_delay, minimum_distance)
+/**
+ * Attack by override for bubblegum
+ *
+ * This is used to award the frenching achievement for hitting bubblegum with a tongue
+ *
+ * Arguments:
+ * * obj/item/W the item hitting bubblegum
+ * * mob/user The user of the item
+ * * params, extra parameters
+ */
+/mob/living/simple_animal/hostile/megafauna/bubblegum/attackby(obj/item/W, mob/user, params)
+ . = ..()
+ if(istype(W, /obj/item/organ/tongue))
+ user.client?.give_award(/datum/award/achievement/misc/frenching, user)
/mob/living/simple_animal/hostile/megafauna/bubblegum/Bump(atom/A)
if(charging)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index a584d34995..73efad31a5 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -43,9 +43,10 @@ Difficulty: Very Hard
move_to_delay = 10
ranged = 1
pixel_x = -32
- del_on_death = 1
- medal_type = BOSS_MEDAL_COLOSSUS
- score_type = COLOSSUS_SCORE
+ del_on_death = TRUE
+ achievement_type = /datum/award/achievement/boss/colossus_kill
+ crusher_achievement_type = /datum/award/achievement/boss/colossus_crusher
+ score_achievement_type = /datum/award/score/colussus_score
crusher_loot = list(/obj/structure/closet/crate/necropolis/colossus/crusher)
loot = list(/obj/structure/closet/crate/necropolis/colossus)
butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/animalhide/ashdrake = 10, /obj/item/stack/sheet/bone = 30)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
index 5e866f95f1..2bcca74f30 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
@@ -31,6 +31,9 @@ Difficulty: Extremely Hard
wander = FALSE
del_on_death = TRUE
blood_volume = BLOOD_VOLUME_NORMAL
+ achievement_type = /datum/award/achievement/boss/demonic_miner_kill
+ crusher_achievement_type = /datum/award/achievement/boss/demonic_miner_crusher
+ score_achievement_type = /datum/award/score/demonic_miner_score
deathmessage = "falls to the ground, decaying into plasma particles."
deathsound = "bodyfall"
attack_action_types = list(/datum/action/innate/megafauna_attack/frost_orbs,
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
index 477483862b..b2c0807222 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
@@ -64,8 +64,9 @@ Difficulty: Medium
guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/ashdrake = 10)
var/swooping = NONE
var/swoop_cooldown = 0
- medal_type = BOSS_MEDAL_DRAKE
- score_type = DRAKE_SCORE
+ achievement_type = /datum/award/achievement/boss/drake_kill
+ crusher_achievement_type = /datum/award/achievement/boss/drake_crusher
+ score_achievement_type = /datum/award/score/drake_score
deathmessage = "collapses into a pile of bones, its flesh sloughing away."
death_sound = 'sound/magic/demon_dies.ogg'
var/datum/action/small_sprite/smallsprite = new/datum/action/small_sprite/drake()
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
index 006bef974d..32300dea18 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -61,8 +61,9 @@ Difficulty: Normal
loot = list(/obj/item/hierophant_club)
crusher_loot = list(/obj/item/hierophant_club)
wander = FALSE
- medal_type = BOSS_MEDAL_HIEROPHANT
- score_type = HIEROPHANT_SCORE
+ achievement_type = /datum/award/achievement/boss/hierophant_kill
+ crusher_achievement_type = /datum/award/achievement/boss/hierophant_crusher
+ score_achievement_type = /datum/award/score/hierophant_score
del_on_death = TRUE
death_sound = 'sound/magic/repulse.ogg'
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
index 174883650d..07c1957da2 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
@@ -42,8 +42,9 @@ SHITCODE AHEAD. BE ADVISED. Also comment extravaganza
retreat_distance = 5
minimum_distance = 5
ranged_cooldown_time = 10
- medal_type = BOSS_MEDAL_LEGION
- score_type = LEGION_SCORE
+ achievement_type = /datum/award/achievement/boss/legion_kill
+ crusher_achievement_type = /datum/award/achievement/boss/legion_crusher
+ score_achievement_type = /datum/award/score/legion_score
pixel_y = -16
pixel_x = -32
loot = list(/obj/item/stack/sheet/bone = 3)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
index 7009f13f36..e2d6602a88 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
@@ -28,24 +28,31 @@
layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise
flags_1 = PREVENT_CONTENTS_EXPLOSION_1 | HEAR_1
has_field_of_vision = FALSE //You are a frikkin boss
- /// Crusher loot dropped when fauna killed with a crusher
+ /// Crusher loot dropped when the megafauna is killed with a crusher
var/list/crusher_loot
- var/medal_type
- /// Score given to players when the fauna is killed
- var/score_type = BOSS_SCORE
- /// If the megafauna is actually killed (vs entering another phase)
+ /// Achievement given to surrounding players when the megafauna is killed
+ var/achievement_type
+ /// Crusher achievement given to players when megafauna is killed
+ var/crusher_achievement_type
+ /// Score given to players when megafauna is killed
+ var/score_achievement_type
+ /// If the megafauna was actually killed (not just dying, then transforming into another type)
var/elimination = 0
/// Modifies attacks when at lower health
var/anger_modifier = 0
/// Internal tracking GPS inside fauna
var/obj/item/gps/internal
- /// Next time fauna can use a melee attack
+ /// Next time the megafauna can use a melee attack
var/recovery_time = 0
-
- var/true_spawn = TRUE // if this is a megafauna that should grant achievements, or have a gps signal
+ /// If this is a megafauna that is real (has achievements, gps signal)
+ var/true_spawn = TRUE
+ /// Range the megafauna can move from their nest (if they have one
var/nest_range = 10
- var/chosen_attack = 1 // chosen attack num
+ /// The chosen attack by the megafauna
+ var/chosen_attack = 1
+ /// Attack actions, sets chosen_attack to the number in the action
var/list/attack_action_types = list()
+ /// If there is a small sprite icon for players controlling the megafauna to use
var/small_sprite_type
/mob/living/simple_animal/hostile/megafauna/Initialize(mapload)
@@ -73,23 +80,22 @@
return
return ..()
-/mob/living/simple_animal/hostile/megafauna/death(gibbed)
+/mob/living/simple_animal/hostile/megafauna/death(gibbed, list/force_grant)
if(health > 0)
return
- else
- var/datum/status_effect/crusher_damage/C = has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
- var/crusher_kill = FALSE
- if(C && crusher_loot && C.total_damage >= maxHealth * 0.6)
- spawn_crusher_loot()
- crusher_kill = TRUE
- if(!(flags_1 & ADMIN_SPAWNED_1))
- var/tab = "megafauna_kills"
- if(crusher_kill)
- tab = "megafauna_kills_crusher"
+ var/datum/status_effect/crusher_damage/crusher_dmg = has_status_effect(STATUS_EFFECT_CRUSHERDAMAGETRACKING)
+ var/crusher_kill = FALSE
+ if(crusher_dmg && crusher_loot && crusher_dmg.total_damage >= maxHealth * 0.6)
+ spawn_crusher_loot()
+ crusher_kill = TRUE
+ if(true_spawn && !(flags_1 & ADMIN_SPAWNED_1))
+ var/tab = "megafauna_kills"
+ if(crusher_kill)
+ tab = "megafauna_kills_crusher"
+ if(!elimination) //used so the achievment only occurs for the last legion to die.
+ grant_achievement(achievement_type, score_achievement_type, crusher_kill, force_grant)
SSblackbox.record_feedback("tally", tab, 1, "[initial(name)]")
- if(!elimination) //used so the achievment only occurs for the last legion to die.
- grant_achievement(medal_type, score_type, crusher_kill)
- ..()
+ return ..()
/mob/living/simple_animal/hostile/megafauna/proc/spawn_crusher_loot()
loot = crusher_loot
@@ -143,26 +149,29 @@
if(EXPLODE_LIGHT)
adjustBruteLoss(50)
-/mob/living/simple_animal/hostile/megafauna/proc/SetRecoveryTime(buffer_time)
+/// Sets the next time the megafauna can use a melee or ranged attack, in deciseconds
+/mob/living/simple_animal/hostile/megafauna/proc/SetRecoveryTime(buffer_time, ranged_buffer_time)
recovery_time = world.time + buffer_time
- ranged_cooldown = max(ranged_cooldown, world.time + buffer_time) // CITADEL BANDAID FIX FOR MEGAFAUNA NOT RESPECTING RECOVERY TIME.
+ ranged_cooldown = world.time + buffer_time
+ if(ranged_buffer_time)
+ ranged_cooldown = world.time + ranged_buffer_time
-/mob/living/simple_animal/hostile/megafauna/proc/grant_achievement(medaltype, scoretype, crusher_kill)
- if(!medal_type || (flags_1 & ADMIN_SPAWNED_1)) //Don't award medals if the medal type isn't set
+/// Grants medals and achievements to surrounding players
+/mob/living/simple_animal/hostile/megafauna/proc/grant_achievement(medaltype, scoretype, crusher_kill, list/grant_achievement = list())
+ if(!achievement_type || (flags_1 & ADMIN_SPAWNED_1) || !SSachievements.achievements_enabled) //Don't award medals if the medal type isn't set
return FALSE
- if(!SSmedals.hub_enabled) // This allows subtypes to carry on other special rewards not tied with medals. (such as bubblegum's arena shuttle)
- return TRUE
-
- for(var/mob/living/L in view(7,src))
+ if(!grant_achievement.len)
+ for(var/mob/living/L in view(7,src))
+ grant_achievement += L
+ for(var/mob/living/L in grant_achievement)
if(L.stat || !L.client)
continue
- var/client/C = L.client
- SSmedals.UnlockMedal("Boss [BOSS_KILL_MEDAL]", C)
- SSmedals.UnlockMedal("[medaltype] [BOSS_KILL_MEDAL]", C)
+ L.client.give_award(/datum/award/achievement/boss/boss_killer, L)
+ L.client.give_award(achievement_type, L)
if(crusher_kill && istype(L.get_active_held_item(), /obj/item/kinetic_crusher))
- SSmedals.UnlockMedal("[medaltype] [BOSS_KILL_MEDAL_CRUSHER]", C)
- SSmedals.SetScore(BOSS_SCORE, C, 1)
- SSmedals.SetScore(score_type, C, 1)
+ L.client.give_award(crusher_achievement_type, L)
+ L.client.give_award(/datum/award/score/boss_score, L) //Score progression for bosses killed in general
+ L.client.give_award(score_achievement_type, L) //Score progression for specific boss killed
return TRUE
/datum/action/innate/megafauna_attack
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
index db6468d1b5..923a626b28 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
@@ -48,8 +48,9 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa
health = 750
maxHealth = 750 //""""low-ish"""" HP because it's a passive boss, and the swarm itself is the real foe
mob_biotypes = MOB_ROBOTIC
- medal_type = BOSS_MEDAL_SWARMERS
- score_type = SWARMER_BEACON_SCORE
+ achievement_type = /datum/award/achievement/boss/swarmer_beacon_kill
+ crusher_achievement_type = /datum/award/achievement/boss/swarmer_beacon_crusher
+ score_achievement_type = /datum/award/score/swarmer_beacon_score
faction = list("mining", "boss", "swarmer")
weather_immunities = list("lava","ash")
stop_automated_movement = TRUE
diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm
index 900d452547..7948db2dfd 100644
--- a/code/modules/surgery/surgery_step.dm
+++ b/code/modules/surgery/surgery_step.dm
@@ -81,13 +81,14 @@
surgery.status++
if(surgery.status > surgery.steps.len)
surgery.complete()
- surgery.step_in_progress = FALSE
- return advance
- else
- surgery.step_in_progress = FALSE
- if(repeatable)
- return FALSE //This is how the repeatable surgery detects it shouldn't cycle
- return TRUE //Stop the attack chain! - Except on repeatable steps, because otherwise we land in an infinite loop.
+
+ if(target.stat == DEAD && was_sleeping && user.client)
+ user.client.give_award(/datum/award/achievement/misc/sandman, user)
+
+ surgery.step_in_progress = FALSE
+ if(repeatable)
+ return FALSE //This is how the repeatable surgery detects it shouldn't cycle
+ return advance //Stop the attack chain! - Except on repeatable steps, because otherwise we land in an infinite loop.
/datum/surgery_step/proc/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index a115300085..20133ee99b 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -559,6 +559,9 @@ GLOBAL_LIST_EMPTY(vending_products)
if(crit_case)
L.apply_damage(squish_damage, forced=TRUE)
+ if(was_alive && L.stat == DEAD && L.client)
+ L.client.give_award(/datum/award/achievement/misc/vendor_squish, L) // good job losing a fight with an inanimate object idiot
+
L.Paralyze(60)
L.emote("scream")
playsound(L, 'sound/effects/blobattack.ogg', 40, TRUE)
diff --git a/icons/UI_Icons/Achievements/Boss/bbgum.png b/icons/UI_Icons/Achievements/Boss/bbgum.png
new file mode 100644
index 0000000000000000000000000000000000000000..a0962fb1ced0f6df721a22c0fd8785e326a3e4a9
GIT binary patch
literal 4864
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
b|JMHkW8YiG?!TZL00000NkvXXu0mjfpe$H2
literal 0
HcmV?d00001
diff --git a/icons/UI_Icons/Achievements/Boss/drake.png b/icons/UI_Icons/Achievements/Boss/drake.png
new file mode 100644
index 0000000000000000000000000000000000000000..19ecc5ae941ae630433e434b46595a80f53ce9b2
GIT binary patch
literal 7847
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
FV1nlaDp3Fc
literal 0
HcmV?d00001
diff --git a/icons/UI_Icons/Achievements/Boss/hierophant.png b/icons/UI_Icons/Achievements/Boss/hierophant.png
new file mode 100644
index 0000000000000000000000000000000000000000..a8f2e6a45edc56d7130e9f28253e8c081699ba3c
GIT binary patch
literal 2345
zcmV+^3D)+BP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9
za%BK;VQFr3E^cLXAT%y9E;jv63FrU-2%1SmK~#8N?VL@E9Yqv|vz$d1S!9t#en`mH
zY{Vca23fb#>gP#D1vgs7ksa3i`%B+Y3{5s=8IZXEYl1&d29xXTT9~3S0-9;4Zii
z9)cu;`wZjXc+3sJb9nBI7kAgVfn9(M?ghue2KXDOl%_C_TYz!y^RC$iZeT2sk^SH`
zz|!#u!L=*N=>4@6?lWwCMULUI-(NZ_=i0cy
zWAGU`2@ZN`iG>^379{!r_z-M^G*f5aNNz8lNq$;cfP|GDDNTV$#>F@pcYdApnm0iw
z;@rTdAd&mP8j#ZT$+_9&yG!Sid#g+1B_}rTuHqVtQu=sJUOP*}I*59^x`A~;qF9N~
zfj@!#$9cPr-f32nPQUP+;Qg%>{A0G3&e~GPG|hu34+%H04oJvRz$cj`@SBA*X?^5n
zrUDdo`PG+?lDV@a^KfN+V3;Q7;d5a09D?1S|noxXD)wZzjL3UGfuO5tVi9*fHVa
zD=)T_2AC$MjcLr%y5!}hDsEsIxG0+lY#vC_mx|m;Y#pqu<>h4|mNddNn%k6VZ*+^g
zfs??6_ypYIg*VU7B@f%6Kvm-5C3NPY1gtCf+>9$K0zbm?7Ka4L{kHB=J<~%pM0&pOQXk^qOX5Ww*f*yflp1h9jhR`wBp-C@F+0cM05
zDiWq!k+3}6e0p)llqJB0sr>g^Vf^)2e&cgbY(XfkiLiXrFnosbMVFN!zk8;o?6?&s
zBN8#XK^~#BP?#m(xbMLYpEjA+!LVj9wmtPC@k;|PUu`cSLJYE@o;_8H0G;lqdX64DgL
z!MGTwcdeLk3PL9A6oqkqmduRt4vq;k$EF4|%cmPN&c>~WHW&0MXe;=>h0#qW>^g->
z`%9)AIS0oi6lSGaK^`hgKl>C$H<_?a3ghg)C_~U?gu+b~=A6qfA&RO~buodlkkOe=6)Dg~=E9
zz{pGzrXq;_UUXcq!hm`jgfJBqGU=FU3hVWjB+P6KG(PWIQ3-Cz-YR{<4BApw6x<(;YrA<>7FGnQYMUlpPI0pCnTqK&gc*|SA}&pL|w*^
zOPCp=zGcVsJNpr&3PToF^dkso?8xLfPGR&;?2^F>gNy8EHcqmWnLHvalg9OivMS6y
zxkH93j4tSeZq$<%#>JWP2=z|xlA#JS&54tqu!CS*CII))5w{Fd7#x_%DEVU5Tv#TJ
zD_Lc|p&Lm@3{jY@xlBvfP3tHW#!}XLLS0=3Cv1E|-Pls-F<}G;aM`ac&U7Q|fT0O9
z3scuaKVx4A4q_AIFP_kLQgBs!d}>Zz*wq!}
zaZXEDv&C5oB9O^;Tq4(1gLbOI&|fs;T^pVQiyLS`B%J@s#h0=eGsXhbkZB4tO^7%P
zObH^&moRhLZ)|DP686LL1(g+V3A2I-^F{Jf(jpE6^)3u)Hzh0nB6)=o11V`zr$u}a
zPza}BQjnF!@Tjs-!J9gZdFkdg3<@s5+f(W^K0RYLIaCfsbs;N#(MSF?WFESG(T9Qy
zMSqnH#c+)hMd{=^?(kX*-Mb_bM3+E*kA+MU)cOW8CL$+C~`DWV$=kd0CMK
z31E*vx>2Of>#!(>P!!Sc-m#@14Z-WMn8xY84og8o^o46O%E6-F^o45_#FEyyWT9~#
zyl{BF*4PP
z^&Y1x*pkS7hCS`CCG|i?*c8zh-%5|6C5+?gdhu-$$RHmQPvEPSM|=JQ8dw&!84{_)
P00000NkvXXu0mjf0ODMv
literal 0
HcmV?d00001
diff --git a/icons/UI_Icons/Achievements/Boss/legion.png b/icons/UI_Icons/Achievements/Boss/legion.png
new file mode 100644
index 0000000000000000000000000000000000000000..dc9470a8a9739bc427a9264efc63ca52fc84c49d
GIT binary patch
literal 7945
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 3?Js}lAQsg-?g5oJ9kdU@m`Cth%U3y
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
A!P<}
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
IM6N<$f}#+CIsgCw
literal 0
HcmV?d00001
diff --git a/icons/UI_Icons/Achievements/Boss/tendril.png b/icons/UI_Icons/Achievements/Boss/tendril.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b6b908d3ebb676b8dc2cd0a8d3d7c69f39c8b2b
GIT binary patch
literal 6594
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
AbN~PV
literal 0
HcmV?d00001
diff --git a/icons/UI_Icons/Achievements/Mafia/assistant.png b/icons/UI_Icons/Achievements/Mafia/assistant.png
new file mode 100644
index 0000000000000000000000000000000000000000..0cfc93b166fbb925f5af4236f8d332914c6c7a8c
GIT binary patch
literal 2374
zcmV-M3Ay%(P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2-`_SK~#8N?VMd~
zR7DiWXJ)tGTS{A?Rj?Kcf)o-Wg!16a3WgVmMPD?*#Aq~{K!O5>2Ms0gA{0vki4O)}
zY@#o!YiMao!@O
z_OB)#H}i(r`7MXssEes$2JFXE9#_T~tK^!tmA+O|<6U~q3?-#k5_={@ohI0sw?1|E
z88uN=RDso3d(u``lzeonscoCaxLa_RVN|iqv>cY5k;}5uvRMXQnRE#*)RpGc9~<|L
zF->aBFa2z6e1xuHHu7YM4L|X+(eWYm3?wDmi+7mI;5;Z=5(YPPexXL%}4ME99=!b-9$|z
z%Vc9zRU@7Jt4Ma&hycSNKA7YGmQJN8T>e
z>iUV`5!awN6^Th=*1&$M_G}YUJ4u6hDZbAmO9Cj2Pje}Zb@zI2Qb##W{0iXUro|%LrJ$Q5zo`80l5uhQ|i<$$`nL%Ke*<7}d|L
z83BWfkU64#LsqpD-S35hGFFuLf>9fvdeDED-R)`ByQ!45F0c4-CSCNIfnf%eT>FJ!
zZQ$I&$`+NYK|K-FKhnc)KfJ~?nj{F?UtEgqZ+!0VGOEMUGXbkV?%71sPXl$qqI72#
zTVAx5Wu@mBwTU5&ZLJTlDxZ$J=mT>6DtGL4HyPE_Dr{g1mXwY32RSbCr7LWJ8UsAWEWmQv@JJg!wnd-
z&<&FTmMOFalNQ#p)YaB|UD~%sRY*5M0gp;7HVS+xSzwo(MIbSpvgCn*OpB%bj@l;3Fxt6>TP!DFzmr)FrV+)Z9-}y*APa+o>gTeJbDpvK
z`So+YMKf0#9vfuW?_Z+vUyeB>FP-Sn23Z)km9bNb&A(KI1)=R=02buX9yE%T0v1+Z
z?a3fyUh*QGYnN2Se5W+t^u#yHu5@06m*FG3D%`QpJ%I9ng{`b8`5G@Qum_*&uplr1
z3os!zpBcsf1H0AKwhW)C;B$p5lLfJdXd#n{$%tdkB)7xA%iB#k8cSWm@3Y
z@AUduS{UNdZruA5z*^u|sQ4`eve~khgFB2n<`jr2#a+ArgC$vAb19PzSdkN}2jH4v
zlC~zxWCK=l*AFpYaztl5cy%8D_{sPvt_LS8)C)lk2UV%T%0U$6{}FDSSt~BEABkJ&U%I(
zkfX2d7D#kLA>PH=vKE#`5Lg9mAg)=mi58k!s?wG&j9*tfn^b6L?jT!ky@F4q(Am;uvLH5-qjy@)w;xAxW-kqL_Cj1=a3l8;
zOo&Y&PAmXy`vLboT3psjS~nrzFx90P(E1uoh)tl|1%N?F`$Z-zk~5K$ld3}7!2m44
z#3)t@0%qh$*I=MCUMr=f&~}w3NGzVt8xH~oAxE0X4j_{i0kWwkv>9y&1F$fP1DXU3
z)2HC_Fz!>)%FH%{fmL$-Bw!E{%E=(xk@kyB)>P5MflfWunc5BpMsbiVf$8f@z6%~M
z(teRvlI%hoJ07yf>qDGl5`P@STEkZ|F;TfBJ--P+`suUd9
zl1JJvGFg$FnTSF+gk>pM(MXavHSrh*jA_VZ%}TIi7%;%MKDerP!)PAEU`m}ZASBJ8
zY(3I`kq3_C%tR1e2h4_kZCs66=!Q`}=)+VZ9wh^(6n>UkBgXP5nTfIwE1H2+GCh;0
z=!aB9al9UfMRwH=4}MOM!!m_;o5+!tTpSXd%|kbhmjlwtxH+#I+zQTCC3Gn9R3
z!^4+~PKmak<5u>b%707*qoM6N<$f@0umegFUf
literal 0
HcmV?d00001
diff --git a/icons/UI_Icons/Achievements/Mafia/changeling.png b/icons/UI_Icons/Achievements/Mafia/changeling.png
new file mode 100644
index 0000000000000000000000000000000000000000..9e44c902633876bda1357d73e9b0debd89e2a328
GIT binary patch
literal 2363
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