diff --git a/_maps/RandomRuins/SpaceRuins/Academy.dmm b/_maps/RandomRuins/SpaceRuins/Academy.dmm
index e47d7983e950..8530c2c8aa32 100644
--- a/_maps/RandomRuins/SpaceRuins/Academy.dmm
+++ b/_maps/RandomRuins/SpaceRuins/Academy.dmm
@@ -201,7 +201,7 @@
/area/awaymission/academy/academyclassroom)
"du" = (
/obj/structure/table/wood,
-/obj/item/book/granter/spell/smoke/lesser,
+/obj/item/book/granter/action/spell/smoke/lesser,
/turf/open/floor/carpet,
/area/awaymission/academy/academyaft)
"dw" = (
@@ -358,7 +358,7 @@
"gg" = (
/obj/structure/closet/crate,
/obj/item/crowbar/red,
-/obj/item/book/granter/spell/smoke/lesser,
+/obj/item/book/granter/action/spell/smoke/lesser,
/turf/open/floor/plasteel/dark,
/area/awaymission/academy/academyclassroom)
"gr" = (
@@ -952,7 +952,7 @@
"kH" = (
/obj/structure/rack,
/obj/item/pen/fourcolor,
-/obj/item/book/granter/spell/summonitem,
+/obj/item/book/granter/action/spell/summonitem,
/turf/open/floor/vault,
/area/awaymission/academy/academycellar)
"kJ" = (
diff --git a/_maps/RandomZLevels/VR/vrhub.dmm b/_maps/RandomZLevels/VR/vrhub.dmm
index e8fa6f2478ac..0c21c794f44e 100644
--- a/_maps/RandomZLevels/VR/vrhub.dmm
+++ b/_maps/RandomZLevels/VR/vrhub.dmm
@@ -612,7 +612,7 @@
/turf/open/indestructible,
/area/awaymission/vr/syndicate)
"ek" = (
-/obj/item/book/granter/spell/charge,
+/obj/item/book/granter/action/spell/charge,
/turf/open/floor/bronze{
initial_gas_mix = "o2=14;n2=23;TEMP=300"
},
@@ -625,7 +625,7 @@
},
/area/awaymission/vr/syndicate)
"eq" = (
-/obj/item/book/granter/spell/forcewall,
+/obj/item/book/granter/action/spell/forcewall,
/turf/open/floor/bronze{
initial_gas_mix = "o2=14;n2=23;TEMP=300"
},
@@ -1565,7 +1565,7 @@
/turf/closed/indestructible/fakeglass,
/area/awaymission/vr/syndicate)
"uE" = (
-/obj/item/book/granter/spell/fireball,
+/obj/item/book/granter/action/spell/fireball,
/turf/open/floor/bronze{
initial_gas_mix = "o2=14;n2=23;TEMP=300"
},
@@ -2953,7 +2953,7 @@
/turf/open/indestructible,
/area/awaymission/vr/hub)
"RY" = (
-/obj/item/book/granter/spell/sacredflame,
+/obj/item/book/granter/action/spell/sacredflame,
/turf/open/floor/bronze{
initial_gas_mix = "o2=14;n2=23;TEMP=300"
},
@@ -3054,7 +3054,7 @@
/turf/open/indestructible,
/area/awaymission/vr/syndicate)
"Uf" = (
-/obj/item/book/granter/spell/barnyard,
+/obj/item/book/granter/action/spell/barnyard,
/turf/open/floor/bronze{
initial_gas_mix = "o2=14;n2=23;TEMP=300"
},
diff --git a/_maps/RandomZLevels/caves.dmm b/_maps/RandomZLevels/caves.dmm
index b4404f146a8b..c8b16e9ce4e7 100644
--- a/_maps/RandomZLevels/caves.dmm
+++ b/_maps/RandomZLevels/caves.dmm
@@ -188,7 +188,7 @@
amount = 25
},
/obj/item/coin/antagtoken,
-/obj/item/book/granter/spell/summonitem{
+/obj/item/book/granter/action/spell/summonitem{
name = "an extremely flamboyant book"
},
/turf/open/floor/engine/cult{
diff --git a/_maps/RandomZLevels/research.dmm b/_maps/RandomZLevels/research.dmm
index b99d2889f7f1..a41f693aecc1 100644
--- a/_maps/RandomZLevels/research.dmm
+++ b/_maps/RandomZLevels/research.dmm
@@ -6348,7 +6348,7 @@
/area/space/nearstation)
"nb" = (
/obj/structure/table/wood,
-/obj/item/book/granter/spell/random,
+/obj/item/book/granter/action/spell/random,
/turf/open/floor/mineral/plasma,
/area/space/nearstation)
"nc" = (
diff --git a/_maps/map_files/Yogsmeta/Yogsmeta.dmm b/_maps/map_files/Yogsmeta/Yogsmeta.dmm
index 67327d789d5f..7d0b893f400a 100644
--- a/_maps/map_files/Yogsmeta/Yogsmeta.dmm
+++ b/_maps/map_files/Yogsmeta/Yogsmeta.dmm
@@ -7933,7 +7933,7 @@
/area/maintenance/central)
"avQ" = (
/obj/structure/table/wood,
-/obj/item/book/granter/spell/smoke/lesser{
+/obj/item/book/granter/action/spell/smoke/lesser{
name = "mysterious old book of cloud-chasing"
},
/obj/item/reagent_containers/food/drinks/bottle/holywater{
diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm
index b6fa10f24aaa..9afd1124f72e 100644
--- a/_maps/map_files/generic/CentCom.dmm
+++ b/_maps/map_files/generic/CentCom.dmm
@@ -19677,18 +19677,18 @@
"aOc" = (
/obj/structure/glowshroom/glowcap,
/obj/structure/table/wood,
-/obj/item/book/granter/spell/barnyard,
-/obj/item/book/granter/spell/blind,
-/obj/item/book/granter/spell/charge,
-/obj/item/book/granter/spell/fireball,
-/obj/item/book/granter/spell/forcewall,
-/obj/item/book/granter/spell/knock,
-/obj/item/book/granter/spell/mimery_blockade,
-/obj/item/book/granter/spell/mimery_guns,
-/obj/item/book/granter/spell/mindswap,
-/obj/item/book/granter/spell/sacredflame,
-/obj/item/book/granter/spell/smoke,
-/obj/item/book/granter/spell/summonitem,
+/obj/item/book/granter/action/spell/barnyard,
+/obj/item/book/granter/action/spell/blind,
+/obj/item/book/granter/action/spell/charge,
+/obj/item/book/granter/action/spell/fireball,
+/obj/item/book/granter/action/spell/forcewall,
+/obj/item/book/granter/action/spell/knock,
+/obj/item/book/granter/action/spell/mime/mimery_blockade,
+/obj/item/book/granter/action/spell/mime/mimery_guns,
+/obj/item/book/granter/action/spell/mindswap,
+/obj/item/book/granter/action/spell/sacredflame,
+/obj/item/book/granter/action/spell/smoke,
+/obj/item/book/granter/action/spell/summonitem,
/obj/item/book/granter/action/origami,
/obj/item/book_of_babel,
/turf/open/floor/wood,
@@ -21171,7 +21171,7 @@
},
/obj/structure/table/reinforced,
/obj/item/voodoo,
-/obj/item/warpwhistle,
+/obj/item/warp_whistle,
/turf/open/floor/plasteel/bluespace,
/area/centcom/testchamber)
"aRb" = (
diff --git a/code/__DEFINES/_click.dm b/code/__DEFINES/_click.dm
new file mode 100644
index 000000000000..07d044f8b896
--- /dev/null
+++ b/code/__DEFINES/_click.dm
@@ -0,0 +1,45 @@
+//Defines file for byond click related parameters
+//this is mostly for ease of use and for finding all the things that use say RIGHT_CLICK rather then just searching "right"
+
+//Mouse buttons held
+#define RIGHT_CLICK "right"
+#define MIDDLE_CLICK "middle"
+#define LEFT_CLICK "left"
+
+///Mouse button that was just clicked/released
+///if(modifiers[BUTTON] == LEFT_CLICK)
+#define BUTTON "button"
+
+//Keys held down during the mouse action
+#define CTRL_CLICK "ctrl"
+#define ALT_CLICK "alt"
+#define SHIFT_CLICK "shift"
+
+//Cells involved if using a Grid control
+#define DRAG_CELL "drag-cell"
+#define DROP_CELL "drop-cell"
+
+//The button used for dragging (only sent for unrelated mouse up/down messages during a drag)
+#define DRAG "drag"
+
+//If the mouse is over a link in maptext, or this event is related to clicking such a link
+#define LINK "link"
+
+//Pixel coordinates relative to the icon's position on screen
+#define VIS_X "vis-x"
+#define VIS_Y "vis-y"
+
+//Pixel coordinates within the icon, in the icon's coordinate space
+#define ICON_X "icon-x"
+#define ICON_Y "icon-y"
+
+//Pixel coordinates in screen_loc format ("[tile_x]:[pixel_x],[tile_y]:[pixel_y]")
+#define SCREEN_LOC "screen-loc"
+
+//https://secure.byond.com/docs/ref/info.html#/atom/var/mouse_opacity
+/// Objects will ignore being clicked on regardless of their transparency (used in parallax, lighting effects, holograms, lasers, etc.)
+#define MOUSE_OPACITY_TRANSPARENT 0
+/// Objects will be clicked on if it is the topmost object and the pixel isn't transparent at the position of the mouse (default behavior for 99.99% of game objects)
+#define MOUSE_OPACITY_ICON 1
+/// Objects will be always be clicked on regardless of pixel transparency or other objects at that location (used in space vines, megafauna, storage containers)
+#define MOUSE_OPACITY_OPAQUE 2
diff --git a/code/__DEFINES/actions.dm b/code/__DEFINES/actions.dm
new file mode 100644
index 000000000000..33d35dedf3a0
--- /dev/null
+++ b/code/__DEFINES/actions.dm
@@ -0,0 +1,30 @@
+///Action button checks if hands are unusable
+#define AB_CHECK_HANDS_BLOCKED (1<<0)
+///Action button checks if user is immobile
+#define AB_CHECK_IMMOBILE (1<<1)
+///Action button checks if user is resting
+#define AB_CHECK_LYING (1<<2)
+///Action button checks if user is conscious
+#define AB_CHECK_CONSCIOUS (1<<3)
+///Action button checks if user is incapacitated
+#define AB_CHECK_INCAPACITATED (1<<4)
+
+///Action button triggered with right click
+#define TRIGGER_SECONDARY_ACTION (1<<0)
+
+// Defines for formatting cooldown actions for the stat panel.
+/// The stat panel the action is displayed in.
+#define PANEL_DISPLAY_PANEL "panel"
+/// The status shown in the stat panel.
+/// Can be stuff like "ready", "on cooldown", "active", "charges", "charge cost", etc.
+#define PANEL_DISPLAY_STATUS "status"
+/// The name shown in the stat panel.
+#define PANEL_DISPLAY_NAME "name"
+
+#define ACTION_BUTTON_DEFAULT_BACKGROUND "_use_ui_default_background"
+
+#define UPDATE_BUTTON_NAME (1<<0)
+#define UPDATE_BUTTON_ICON (1<<1)
+#define UPDATE_BUTTON_BACKGROUND (1<<2)
+#define UPDATE_BUTTON_OVERLAY (1<<3)
+#define UPDATE_BUTTON_STATUS (1<<4)
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 6400d4231af6..7f5d198bbe4e 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -122,8 +122,33 @@
#define TIER_3 6
#define TIER_ASCEND 7
-//Bloodsuckers
+///Whether a mob is a Bloodsucker
#define IS_BLOODSUCKER(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/bloodsucker))
+///Whether a mob is a Vassal
#define IS_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal))
+///Whether a mob is a Favorite Vassal
+#define IS_FAVORITE_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal/favorite))
+///Whether a mob is a Revenge Vassal
+#define IS_REVENGE_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal/revenge))
+///Whether a mob is a Monster Hunter
#define IS_MONSTERHUNTER(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/monsterhunter))
+/// The Classic Wizard wizard loadout.
+#define WIZARD_LOADOUT_CLASSIC "loadout_classic"
+/// Mjolnir's Power wizard loadout.
+#define WIZARD_LOADOUT_MJOLNIR "loadout_hammer"
+/// Fantastical Army wizard loadout.
+#define WIZARD_LOADOUT_WIZARMY "loadout_army"
+/// Soul Tapper wizard loadout.
+#define WIZARD_LOADOUT_SOULTAP "loadout_tap"
+/// Convenient list of all wizard loadouts for unit testing.
+#define ALL_WIZARD_LOADOUTS list( \
+ WIZARD_LOADOUT_CLASSIC, \
+ WIZARD_LOADOUT_MJOLNIR, \
+ WIZARD_LOADOUT_WIZARMY, \
+ WIZARD_LOADOUT_SOULTAP, \
+)
+
+/// Used in logging spells for roundend results
+#define LOG_SPELL_TYPE "type"
+#define LOG_SPELL_AMOUNT "amount"
\ No newline at end of file
diff --git a/code/__DEFINES/assert.dm b/code/__DEFINES/assert.dm
new file mode 100644
index 000000000000..cff78107714c
--- /dev/null
+++ b/code/__DEFINES/assert.dm
@@ -0,0 +1,13 @@
+#undef ASSERT
+
+/// Override BYOND's native ASSERT to optionally specify a message
+#define ASSERT(condition, message...) \
+ if (!(condition)) { \
+ CRASH(assertion_message(__FILE__, __LINE__, #condition, ##message)) \
+ }
+
+/proc/assertion_message(file, line, condition, message)
+ if (!isnull(message))
+ message = " - [message]"
+
+ return "[file]:[line]:Assertion failed: [condition][message]"
diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm
index 11027d0b7f13..1b77cee4f3ca 100644
--- a/code/__DEFINES/atom_hud.dm
+++ b/code/__DEFINES/atom_hud.dm
@@ -63,36 +63,6 @@
#define DATA_HUD_AI_DETECT 9
#define DATA_HUD_SECURITY_MEDICAL 10
-//antag HUD defines
-#define ANTAG_HUD_CULT 11
-#define ANTAG_HUD_REV 12
-#define ANTAG_HUD_OPS 13
-#define ANTAG_HUD_WIZ 14
-#define ANTAG_HUD_SHADOW 15
-#define ANTAG_HUD_TRAITOR 16
-#define ANTAG_HUD_NINJA 17
-#define ANTAG_HUD_CHANGELING 18
-#define ANTAG_HUD_ABDUCTOR 19
-#define ANTAG_HUD_DEVIL 20
-#define ANTAG_HUD_SINTOUCHED 21
-#define ANTAG_HUD_SOULLESS 22
-#define ANTAG_HUD_CLOCKWORK 23
-#define ANTAG_HUD_BROTHER 24
-#define ANTAG_HUD_HIVE 25
-#define ANTAG_HUD_OBSESSED 26
-#define ANTAG_HUD_FUGITIVE 27
-#define ANTAG_HUD_VAMPIRE 28
-#define ANTAG_HUD_DARKSPAWN 29
-#define ANTAG_HUD_CAPITALIST 30
-#define ANTAG_HUD_COMMUNIST 31
-#define ANTAG_HUD_HERETIC 32
-#define ANTAG_HUD_MINDSLAVE 33
-#define ANTAG_HUD_ZOMBIE 34
-#define ANTAG_HUD_INFILTRATOR 35
-#define ANTAG_HUD_BLOODSUCKER 36
-#define ANTAG_HUD_MHUNTER 37
-#define ANTAG_HUD_BRAINWASHED 38
-
// Notification action types
#define NOTIFY_JUMP "jump"
#define NOTIFY_ATTACK "attack"
diff --git a/code/__DEFINES/bloodsuckers.dm b/code/__DEFINES/bloodsuckers.dm
index d8be795f7d80..a8f508fb295d 100644
--- a/code/__DEFINES/bloodsuckers.dm
+++ b/code/__DEFINES/bloodsuckers.dm
@@ -1,12 +1,12 @@
-/**
- * Bloodsucker defines
- */
+///Uncomment this to enable testing of Bloodsucker features (such as vassalizing people with a mind instead of a client).
+//#define BLOODSUCKER_TESTING
+
/// Determines Bloodsucker regeneration rate
#define BS_BLOOD_VOLUME_MAX_REGEN 700
-/// Cost to torture someone, in blood
-#define TORTURE_BLOOD_COST "15"
+/// Cost to torture someone halfway, in blood. Called twice for full cost
+#define TORTURE_BLOOD_HALF_COST 8
/// Cost to convert someone after successful torture, in blood
-#define TORTURE_CONVERSION_COST "50"
+#define TORTURE_CONVERSION_COST 50
/// Deals with constant processes off of LifeTick()
#define COMSIG_LIVING_BIOLOGICAL_LIFE "biological_life"
/// Once blood is this low, will enter Frenzy
@@ -16,6 +16,37 @@
/// You have special interactions with Bloodsuckers
#define TRAIT_BLOODSUCKER_HUNTER "bloodsucker_hunter"
+///Drinks blood the normal Bloodsucker way.
+#define BLOODSUCKER_DRINK_NORMAL "bloodsucker_drink_normal"
+///Drinks blood but is snobby, refusing to drink from mindless
+#define BLOODSUCKER_DRINK_SNOBBY "bloodsucker_drink_snobby"
+///Drinks blood from disgusting creatures without Humanity consequences.
+#define BLOODSUCKER_DRINK_INHUMANELY "bloodsucker_drink_imhumanely"
+
+///Controls blood, just like all Bloodsuckers do
+#define BLOODSUCKER_CONTROL_BLOOD 1
+///Controls shadows and blood, as it creates and destroys. Able to make shadow rituals
+#define BLOODSUCKER_CONTROL_SHADOWS 2
+///Controls metal and blood to make art works to buff mood and other benefits
+#define BLOODSUCKER_CONTROL_METAL 4
+///Controls the inner machinations of one's being, being able to use the vassalrack and sculpt flesh monsters
+#define BLOODSUCKER_CONTROL_FLESH 8
+
+#define BLOODSUCKER_RANK_UP_NORMAL "bloodsucker_rank_up_normal"
+
+#define DANGER_LEVEL_FIRST_WARNING 1
+#define DANGER_LEVEL_SECOND_WARNING 2
+#define DANGER_LEVEL_THIRD_WARNING 3
+#define DANGER_LEVEL_SOL_ROSE 4
+#define DANGER_LEVEL_SOL_ENDED 5
+
+///If someone passes all checks and can be vassalized
+#define VASSALIZATION_ALLOWED 0
+///If someone has to accept vassalization
+#define VASSALIZATION_DISLOYAL 1
+///If someone is not allowed under any circimstances to become a Vassal
+#define VASSALIZATION_BANNED 2
+
/**
* Cooldown defines
* Used in Cooldowns Bloodsuckers use to prevent spamming
@@ -28,6 +59,7 @@
/**
* Clan defines
*/
+#define CLAN_NONE "Caitiff"
#define CLAN_BRUJAH "Brujah Clan"
#define CLAN_NOSFERATU "Nosferatu Clan"
#define CLAN_TREMERE "Tremere Clan"
@@ -38,6 +70,10 @@
#define CLAN_LASOMBRA "Lasombra Clan"
#define CLAN_TZIMISCE "Tzimisce Clan"
+#define TREMERE_VASSAL "tremere_vassal"
+#define FAVORITE_VASSAL "favorite_vassal"
+#define REVENGE_VASSAL "revenge_vassal"
+
#define TRIPLECHEST_MONSTER "Triple Chest (300 Blood)"
#define ARMMY_MONSTER "Armmy (100 Blood)"
#define CALCIUM_MONSTER "Calcium (150 Blood)"
@@ -60,16 +96,18 @@
/// This Power can be purchased by Bloodsuckers
#define BLOODSUCKER_CAN_BUY (1<<0)
+/// This is a Default Power that all Bloodsuckers get.
+#define BLOODSUCKER_DEFAULT_POWER (1<<1)
/// This Power can be purchased by Lasombra Bloodsuckers
-#define LASOMBRA_CAN_BUY (1<<1)
+#define LASOMBRA_CAN_BUY (1<<2)
/// This Power can be purchased by Gangrel Bloodsuckers
-#define GANGREL_CAN_BUY (1<<2)
+#define GANGREL_CAN_BUY (1<<3)
/// This Power can be purchased by Vassals
-#define VASSAL_CAN_BUY (1<<3)
+#define VASSAL_CAN_BUY (1<<4)
/// This Power can be purchased by Monster Hunters
-#define HUNTER_CAN_BUY (1<<4)
+#define HUNTER_CAN_BUY (1<<5)
/// This Power can be purchased by Tzimisce Bloodsuckers
-#define TZIMISCE_CAN_BUY (1<<5)
+#define TZIMISCE_CAN_BUY (1<<6)
/// This Power is a Toggled Power
#define BP_AM_TOGGLE (1<<0)
@@ -80,6 +118,37 @@
/// This Power doesn't cost bloot to run while unconscious
#define BP_AM_COSTLESS_UNCONSCIOUS (1<<3)
-// Signals
+/**
+ * Signals
+ */
+///Called when a Bloodsucker ranks up: (datum/bloodsucker_datum, mob/owner, mob/target)
+#define BLOODSUCKER_RANK_UP "bloodsucker_rank_up"
+
+///Called when a Bloodsucker attempts to make a Vassal into their Favorite.
+#define BLOODSUCKER_PRE_MAKE_FAVORITE "bloodsucker_pre_make_favorite"
+///Called when a Bloodsucker makes a Vassal into their Favorite Vassal: (datum/vassal_datum, mob/master)
+#define BLOODSUCKER_MAKE_FAVORITE "bloodsucker_make_favorite"
+///Called when a new Vassal is successfully made: (datum/bloodsucker_datum)
+#define BLOODSUCKER_MADE_VASSAL "bloodsucker_made_vassal"
+
+///Called when a Bloodsucker exits Torpor.
+#define BLOODSUCKER_EXIT_TORPOR "bloodsucker_exit_torpor"
+///Called when a Bloodsucker reaches Final Death.
+#define BLOODSUCKER_FINAL_DEATH "bloodsucker_final_death"
-#define COMSIG_BLOODSUCKER_RANKS_SPENT
\ No newline at end of file
+#define COMSIG_BLOODSUCKER_BROKE_MASQUERADE "comsig_bloodsucker_broke_masquerade"
+
+///Whether the Bloodsucker should not be dusted when arriving Final Death
+#define DONT_DUST (1<<0)
+
+/**
+ * Sol signals
+ */
+#define COMSIG_SOL_RANKUP_BLOODSUCKERS "comsig_sol_rankup_bloodsuckers"
+#define COMSIG_SOL_RISE_TICK "comsig_sol_rise_tick"
+#define COMSIG_SOL_NEAR_START "comsig_sol_near_start"
+#define COMSIG_SOL_END "comsig_sol_end"
+///Sent when a warning for Sol is meant to go out: (danger_level, vampire_warning_message, vassal_warning_message)
+#define COMSIG_SOL_WARNING_GIVEN "comsig_sol_warning_given"
+///Called on a Bloodsucker's Lifetick.
+#define COMSIG_BLOODSUCKER_ON_LIFETICK "comsig_bloodsucker_on_lifetick"
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index 32974246884a..91ff62653b06 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -141,6 +141,13 @@
/// The default color for admin say, used as a fallback when the preference is not enabled
#define DEFAULT_ASAY_COLOR "#996600"
+/*
+ * Antag Specific Colors
+*/
+
+#define COLOR_CHANGELING_CHEMICALS "#DD66DD"
+#define COLOR_DARKSPAWN_PSI "#7264FF"
+
///Main colors for UI themes
#define COLOR_THEME_MIDNIGHT "#6086A0"
#define COLOR_THEME_PLASMAFIRE "#FFB200"
diff --git a/code/__DEFINES/cult.dm b/code/__DEFINES/cult.dm
index 054d6345dbd6..871c6c5d00e2 100644
--- a/code/__DEFINES/cult.dm
+++ b/code/__DEFINES/cult.dm
@@ -27,3 +27,7 @@
//misc
#define SOULS_TO_REVIVE 3
#define BLOODCULT_EYE "f00"
+//soulstone & construct themes
+#define THEME_CULT "cult"
+#define THEME_WIZARD "wizard"
+#define THEME_HOLY "holy"
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm
index 261c13c66735..3e613c11e005 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm
@@ -2,7 +2,9 @@
// When the signal is called: (signal arguments)
// All signals send the source datum of the signal as the first argument
-///from base of atom/Click(): (location, control, params, mob/user)
+///from base of client/Click(): (atom/target, atom/location, control, params, mob/user)
+#define COMSIG_CLIENT_CLICK "atom_client_click"
+///from base of atom/Click(): (atom/location, control, params, mob/user)
#define COMSIG_CLICK "atom_click"
///from base of atom/ShiftClick(): (/mob)
#define COMSIG_CLICK_SHIFT "shift_click"
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm
index 910def19c8d6..b9cf29bf2d47 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm
@@ -8,7 +8,7 @@
#define COMSIG_ATOM_EMP_ACT "atom_emp_act"
///from base of atom/fire_act(): (exposed_temperature, exposed_volume)
#define COMSIG_ATOM_FIRE_ACT "atom_fire_act"
-///from base of atom/bullet_act(): (/obj/projectile, def_zone)
+///from base of atom/bullet_act(): (/obj/item/projectile, def_zone)
#define COMSIG_ATOM_BULLET_ACT "atom_bullet_act"
///from base of atom/CheckParts(): (list/parts_list, datum/crafting_recipe/R)
#define COMSIG_ATOM_CHECKPARTS "atom_checkparts"
diff --git a/code/__DEFINES/dcs/signals/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum.dm
index 2cebc161d57e..9b3067bc8a45 100644
--- a/code/__DEFINES/dcs/signals/signals_datum.dm
+++ b/code/__DEFINES/dcs/signals/signals_datum.dm
@@ -13,3 +13,10 @@
#define COMSIG_ELEMENT_ATTACH "element_attach"
/// fires on the target datum when an element is attached to it (/datum/element)
#define COMSIG_ELEMENT_DETACH "element_detach"
+
+// Antagonist signals
+/// Called on the mind when an antagonist is being gained, after the antagonist list has updated (datum/antagonist/antagonist)
+#define COMSIG_ANTAGONIST_GAINED "antagonist_gained"
+
+/// Called on the mind when an antagonist is being removed, after the antagonist list has updated (datum/antagonist/antagonist)
+#define COMSIG_ANTAGONIST_REMOVED "antagonist_removed"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm
index 01c460d4a834..fca27b58a864 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm
@@ -111,7 +111,7 @@
///Called when a monkey turns into a human, from /mob/living/carbon/proc/finish_humanize(species)
#define COMSIG_MONKEY_HUMANIZE "monkey_humanize"
-///From mob/living/carbon/human/suicide()
+///From mob/living/carbon/human/handle_suicide(); on yog: mob/living/carbon/human/verb/suicide()
#define COMSIG_HUMAN_SUICIDE_ACT "human_suicide_act"
///from base of /mob/living/carbon/regenerate_limbs(): (excluded_limbs)
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm
index b878fb218234..cbec9632211d 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm
@@ -135,3 +135,6 @@
#define COMSIG_LIVING_BEFRIENDED "living_befriended"
/// From /mob/living/unfriend() : (mob/living/old_friend)
#define COMSIG_LIVING_UNFRIENDED "living_unfriended"
+
+///from mind/transfer_to. Sent after the mind has been transferred: (mob/previous_body)
+#define COMSIG_MIND_TRANSFERRED "mind_transferred"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm
index 3b0e66067243..649204104c1f 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm
@@ -69,7 +69,7 @@
#define COMSIG_MOB_RESTRICT_MAGIC "mob_cast_magic"
///from base of mob/can_block_magic(): (mob/user, casted_magic_flags, charge_cost)
#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic"
- #define COMPONENT_BLOCK_MAGIC (1<<0)
+ #define COMPONENT_MAGIC_BLOCKED (1<<0)
///from base of mob/create_mob_hud(): ()
#define COMSIG_MOB_HUD_CREATED "mob_hud_created"
diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index 4fb8922c3088..99aec06ba473 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -353,23 +353,23 @@
///called in /obj/item/gun/process_fire (user, target, params, zone_override)
#define COMSIG_GRENADE_ARMED "grenade_armed"
-// /obj/projectile signals (sent to the firer)
+// /obj/item/projectile signals (sent to the firer)
-///from base of /obj/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, Angle, hit_limb)
+///from base of /obj/item/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, Angle, hit_limb)
#define COMSIG_PROJECTILE_SELF_ON_HIT "projectile_self_on_hit"
-///from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle)
+///from base of /obj/item/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle)
#define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit"
-///from base of /obj/projectile/proc/fire(): (obj/projectile, atom/original_target)
+///from base of /obj/item/projectile/proc/fire(): (obj/item/projectile, atom/original_target)
#define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire"
-///from base of /obj/projectile/proc/fire(): (obj/projectile, atom/firer, atom/original_target)
+///from base of /obj/item/projectile/proc/fire(): (obj/item/projectile, atom/firer, atom/original_target)
#define COMSIG_PROJECTILE_FIRER_BEFORE_FIRE "projectile_firer_before_fire"
-///from the base of /obj/projectile/proc/fire(): ()
+///from the base of /obj/item/projectile/proc/fire(): ()
#define COMSIG_PROJECTILE_FIRE "projectile_fire"
///sent to targets during the process_hit proc of projectiles
#define COMSIG_PROJECTILE_PREHIT "com_proj_prehit"
-///from the base of /obj/projectile/Range(): ()
+///from the base of /obj/item/projectile/Range(): ()
#define COMSIG_PROJECTILE_RANGE "projectile_range"
-///from the base of /obj/projectile/on_range(): ()
+///from the base of /obj/item/projectile/on_range(): ()
#define COMSIG_PROJECTILE_RANGE_OUT "projectile_range_out"
///from [/obj/item/proc/tryEmbed] sent when trying to force an embed (mainly for projectiles and eating glass)
#define COMSIG_EMBED_TRY_FORCE "item_try_embed"
@@ -389,7 +389,7 @@
///from /datum/action/vehicle/sealed/headlights/vim/Trigger(): (headlights_on)
#define COMSIG_VIM_HEADLIGHTS_TOGGLED "vim_headlights_toggled"
-// /obj/vehicle/sealed/mecha signals
+// /obj/mecha signals
/// sent if you attach equipment to mecha
#define COMSIG_MECHA_EQUIPMENT_ATTACHED "mecha_equipment_attached"
diff --git a/code/__DEFINES/dcs/signals/signals_spell.dm b/code/__DEFINES/dcs/signals/signals_spell.dm
index 8c81adeb8f52..445a464873cb 100644
--- a/code/__DEFINES/dcs/signals/signals_spell.dm
+++ b/code/__DEFINES/dcs/signals/signals_spell.dm
@@ -33,7 +33,7 @@
// Spell type signals
// Pointed projectiles
-// Sent from /datum/action/cooldown/spell/pointed/projectile/fire_projectile() to the caster: (datum/action/cooldown/spell/spell, atom/cast_on, obj/projectile/to_fire)
+// Sent from /datum/action/cooldown/spell/pointed/projectile/fire_projectile() to the caster: (datum/action/cooldown/spell/spell, atom/cast_on, obj/item/projectile/to_fire)
#define COMSIG_MOB_SPELL_PROJECTILE "mob_spell_projectile"
/// Sent from /datum/action/cooldown/spell/pointed/projectile/on_cast_hit: (atom/firer, atom/target, atom/hit, angle, hit_limb)
#define COMSIG_SPELL_PROJECTILE_HIT "spell_projectile_hit"
diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index 8870a12b91e9..57cd7e418873 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -70,6 +70,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
/// If megafauna can be spawned by natural random generation
#define MEGAFAUNA_SPAWN_ALLOWED (1<<5)
/// Are you forbidden from teleporting to the area? (centcom, mobs, wizard, hand teleporter)
+#define NOTELEPORT (1<<6)
/*
These defines are used specifically with the atom/pass_flags bitmask
diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm
index 58d465d70aa3..2e5199d6fa43 100644
--- a/code/__DEFINES/hud.dm
+++ b/code/__DEFINES/hud.dm
@@ -13,3 +13,222 @@
#define UI_BOXCRAFT "EAST-4:22,SOUTH+1:6"
#define UI_BOXAREA "EAST-4:6,SOUTH+1:6"
#define UI_BOXLANG "EAST-5:22,SOUTH+1:6"
+
+/*
+ These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var.
+
+ The short version:
+
+ Everything is encoded as strings because apparently that's how Byond rolls.
+
+ "1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid.
+ "1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid.
+ Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy.
+
+ In addition, the keywords NORTH, SOUTH, EAST, WEST and CENTER can be used to represent their respective
+ screen borders. NORTH-1, for example, is the row just below the upper edge. Useful if you want your
+ UI to scale with screen size.
+
+ The size of the user's screen is defined by client.view (indirectly by world.view), in our case "15x15".
+ Therefore, the top right corner (except during admin shenanigans) is at "15,15"
+*/
+
+//Lower left, persistent menu
+#define ui_inventory "WEST:6,SOUTH:5"
+
+//Middle left indicators
+#define ui_lingchemdisplay "WEST,CENTER-1:15"
+#define ui_lingstingdisplay "WEST:6,CENTER-3:11"
+
+#define ui_devilsouldisplay "WEST:6,CENTER-1:15"
+
+//Lower center, persistent menu
+#define ui_sstore1 "CENTER-5:10,SOUTH:5"
+#define ui_id "CENTER-4:12,SOUTH:5"
+#define ui_belt "CENTER-3:14,SOUTH:5"
+#define ui_back "CENTER-2:14,SOUTH:5"
+
+/proc/ui_hand_position(i) //values based on old hand ui positions (CENTER:-/+16,SOUTH:5)
+ var/x_off = -(!(i % 2))
+ var/y_off = round((i-1) / 2)
+ return"CENTER+[x_off]:16,SOUTH+[y_off]:5"
+
+/proc/ui_equip_position(mob/M)
+ var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5)
+ return "CENTER:-16,SOUTH+[y_off+1]:5"
+
+/proc/ui_swaphand_position(mob/M, which = 1) //values based on old swaphand ui positions (CENTER: +/-16,SOUTH+1:5)
+ var/x_off = which == 1 ? -1 : 0
+ var/y_off = round((M.held_items.len-1) / 2)
+ return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5"
+
+#define ui_storage1 "CENTER+1:18,SOUTH:5"
+#define ui_storage2 "CENTER+2:20,SOUTH:5"
+
+#define ui_borg_sensor "CENTER-3:16, SOUTH:5" //borgs
+#define ui_borg_lamp "CENTER-4:16, SOUTH:5" //borgs
+#define ui_borg_thrusters "CENTER-5:16, SOUTH:5" //borgs
+#define ui_inv1 "CENTER-2:16,SOUTH:5" //borgs
+#define ui_inv2 "CENTER-1 :16,SOUTH:5" //borgs
+#define ui_inv3 "CENTER :16,SOUTH:5" //borgs
+#define ui_borg_module "CENTER+1:16,SOUTH:5" //borgs
+#define ui_borg_store "CENTER+2:16,SOUTH:5" //borgs
+#define ui_borg_camera "CENTER+3:21,SOUTH:5" //borgs
+#define ui_borg_tablet "CENTER+4:21,SOUTH:5" //borgs
+#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5" //borgs
+
+#define ui_monkey_head "CENTER-5:13,SOUTH:5" //monkey
+#define ui_monkey_mask "CENTER-4:14,SOUTH:5" //monkey
+#define ui_monkey_neck "CENTER-3:15,SOUTH:5" //monkey
+#define ui_monkey_back "CENTER-2:16,SOUTH:5" //monkey
+
+//#define ui_alien_storage_l "CENTER-2:14,SOUTH:5"//alien
+#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"//alien
+#define ui_alien_language_menu "EAST-3:26,SOUTH:5" //alien
+
+#define ui_drone_drop "CENTER+1:18,SOUTH:5" //maintenance drones
+#define ui_drone_pull "CENTER+2:2,SOUTH:5" //maintenance drones
+#define ui_drone_storage "CENTER-2:14,SOUTH:5" //maintenance drones
+#define ui_drone_head "CENTER-3:14,SOUTH:5" //maintenance drones
+
+//Lower right, persistent menu
+#define ui_drop_throw "EAST-1:28,SOUTH+1:7"
+#define ui_above_movement "EAST-2:26,SOUTH+1:7"
+#define ui_above_intent "EAST-3:24, SOUTH+1:7"
+#define ui_movi "EAST-2:26,SOUTH:5"
+#define ui_acti "EAST-3:24,SOUTH:5"
+#define ui_zonesel "EAST-1:28,SOUTH:5"
+#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12)
+#define ui_crafting "EAST-4:22,SOUTH:5"
+#define ui_building "EAST-4:22,SOUTH:21"
+#define ui_borg_pull "EAST-2:26,SOUTH+1:7"
+#define ui_borg_radio "EAST-1:28,SOUTH+1:7"
+#define ui_borg_intents "EAST-2:26,SOUTH:5"
+#define ui_language_menu "EAST-4:6,SOUTH:21"
+
+
+//Upper-middle right (alerts)
+#define ui_alert1 "EAST-1:28,CENTER+5:27"
+#define ui_alert2 "EAST-1:28,CENTER+4:25"
+#define ui_alert3 "EAST-1:28,CENTER+3:23"
+#define ui_alert4 "EAST-1:28,CENTER+2:21"
+#define ui_alert5 "EAST-1:28,CENTER+1:19"
+
+//Upper left (action buttons)
+#define ui_action_palette "WEST+0:23,NORTH-1:5"
+#define ui_action_palette_offset(north_offset) ("WEST+0:23,NORTH-[1+north_offset]:5")
+
+#define ui_palette_scroll "WEST+1:8,NORTH-6:28"
+#define ui_palette_scroll_offset(north_offset) ("WEST+1:8,NORTH-[6+north_offset]:28")
+
+//Middle right (status indicators)
+#define ui_healthdoll "EAST-1:28,CENTER-2:13"
+#define ui_health "EAST-1:28,CENTER-1:15"
+#define ui_internal "EAST-1:28,CENTER:17"
+#define ui_mood "EAST-1:28,CENTER-4:10"
+#define ui_stamina "EAST-1:28,CENTER-3:10"
+
+//living
+#define ui_living_pull "EAST-1:28,CENTER-3:15"
+#define ui_living_health "EAST-1:28,CENTER:15"
+#define ui_living_healthdoll "EAST-1:28,CENTER-1:15"
+
+//borgs
+#define ui_borg_health "EAST-1:28,CENTER-1:15" //borgs have the health display where humans have the pressure damage indicator.
+
+//aliens
+#define ui_alien_health "EAST,CENTER-1:15" //aliens have the health display where humans have the pressure damage indicator.
+#define ui_alienplasmadisplay "EAST,CENTER-2:15"
+#define ui_alien_queen_finder "EAST,CENTER-3:15"
+
+//constructs
+#define ui_construct_pull "EAST,CENTER-2:15"
+#define ui_construct_health "EAST,CENTER:15" //same as humans and slimes
+
+//slimes
+#define ui_slime_health "EAST,CENTER:15" //same as humans and constructs
+
+//Blobbernauts
+#define ui_blobbernaut_overmind_health "EAST-1:28,CENTER+0:19"
+
+// AI
+
+#define ui_ai_core "CENTER-6:-32,SOUTH:6"
+#define ui_ai_dashboard "CENTER-7,SOUTH+1:6"
+#define ui_ai_camera_list "CENTER-5:-32,SOUTH:6"
+#define ui_ai_track_with_camera "CENTER-4:-32,SOUTH:6"
+#define ui_ai_camera_light "CENTER-3:-32,SOUTH:6"
+#define ui_ai_crew_monitor "CENTER-2:-32,SOUTH:6"
+#define ui_ai_crew_manifest "CENTER-1:-32,SOUTH:6"
+#define ui_ai_alerts "CENTER:-32,SOUTH:6"
+#define ui_ai_announcement "CENTER+1:-32,SOUTH:6"
+#define ui_ai_shuttle "CENTER+2:-32,SOUTH:6"
+#define ui_ai_state_laws "CENTER+3:-32,SOUTH:6"
+#define ui_ai_pda_send "CENTER+4:-32,SOUTH:6"
+#define ui_ai_pda_log "CENTER+5:-32,SOUTH:6"
+#define ui_ai_take_picture "CENTER+6:-32,SOUTH:6"
+#define ui_ai_view_images "CENTER+7:-32,SOUTH:6"
+#define ui_ai_sensor "CENTER+7,SOUTH:6"
+#define ui_ai_multicam "CENTER+6,SOUTH+1:6"
+#define ui_ai_add_multicam "CENTER+7,SOUTH+1:6"
+#define ui_ai_language_menu "CENTER+5,SOUTH+1:9"
+#define ui_ai_dashboard_widescreen "CENTER-8,SOUTH:6"
+#define ui_ai_add_multicam_widescreen "CENTER+9,SOUTH:6"
+#define ui_ai_multicam_widescreen "CENTER+8,SOUTH:6"
+#define ui_ai_language_menu_widescreen "CENTER-9,SOUTH:10"
+
+// pAI
+
+#define ui_pai_software "SOUTH:6,WEST"
+#define ui_pai_shell "SOUTH:6,WEST+1"
+#define ui_pai_chassis "SOUTH:6,WEST+2"
+#define ui_pai_rest "SOUTH:6,WEST+3"
+#define ui_pai_light "SOUTH:6,WEST+4"
+#define ui_pai_newscaster "SOUTH:6,WEST+5"
+#define ui_pai_host_monitor "SOUTH:6,WEST+6"
+#define ui_pai_crew_manifest "SOUTH:6,WEST+7"
+#define ui_pai_state_laws "SOUTH:6,WEST+8"
+#define ui_pai_pda_send "SOUTH:6,WEST+9"
+#define ui_pai_pda_log "SOUTH:6,WEST+10"
+#define ui_pai_take_picture "SOUTH:6,WEST+12"
+#define ui_pai_view_images "SOUTH:6,WEST+13"
+
+//Pop-up inventory
+#define ui_shoes "WEST+1:8,SOUTH:5"
+
+#define ui_iclothing "WEST:6,SOUTH+1:7"
+#define ui_oclothing "WEST+1:8,SOUTH+1:7"
+#define ui_gloves "WEST+2:10,SOUTH+1:7"
+
+#define ui_glasses "WEST:6,SOUTH+3:11"
+#define ui_mask "WEST+1:8,SOUTH+2:9"
+#define ui_ears "WEST+2:10,SOUTH+2:9"
+#define ui_neck "WEST:6,SOUTH+2:9"
+#define ui_head "WEST+1:8,SOUTH+3:11"
+
+//Ghosts
+
+#define ui_ghost_jumptomob "SOUTH:6,CENTER-3:24"
+#define ui_ghost_orbit "SOUTH:6,CENTER-2:24"
+#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24"
+#define ui_ghost_teleport "SOUTH:6,CENTER:24"
+#define ui_ghost_spawners "SOUTH: 6, CENTER+1:24"
+#define ui_ghost_language_menu "SOUTH: 22,CENTER+2:8"
+#define ui_ghost_pai "SOUTH: 6,CENTER+2:8"
+#define ui_ghost_med "SOUTH: 6,CENTER+3:-8"
+#define ui_ghost_chem "SOUTH: 22,CENTER+3:-8"
+#define ui_ghost_nanite "SOUTH: 6,CENTER+3:8"
+#define ui_ghost_wound "SOUTH: 22,CENTER+3:8"
+
+// Defines relating to action button positions
+
+/// Whatever the base action datum thinks is best
+#define SCRN_OBJ_DEFAULT "default"
+/// Floating somewhere on the hud, not in any predefined place
+#define SCRN_OBJ_FLOATING "floating"
+/// In the list of buttons stored at the top of the screen
+#define SCRN_OBJ_IN_LIST "list"
+/// In the collapseable palette
+#define SCRN_OBJ_IN_PALETTE "palette"
+///Inserted first in the list
+#define SCRN_OBJ_INSERT_FIRST "first"
diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm
index 4f50fc79d426..24f92e5da204 100644
--- a/code/__DEFINES/inventory.dm
+++ b/code/__DEFINES/inventory.dm
@@ -31,15 +31,28 @@
#define ITEM_SLOT_ID (1<<8)
#define ITEM_SLOT_BELT (1<<9)
#define ITEM_SLOT_BACK (1<<10)
+/// Dextrous simplemob "hands" (used for Drones and Dextrous Guardians)
+#define ITEM_SLOT_DEX_STORAGE (1<<11)
/// this is to allow items with a w_class of WEIGHT_CLASS_NORMAL or WEIGHT_CLASS_BULKY to fit in pockets.
-#define ITEM_SLOT_POCKET (1<<11)
+#define ITEM_SLOT_POCKET (1<<12)
/// this is to deny items with a w_class of WEIGHT_CLASS_SMALL or WEIGHT_CLASS_TINY to fit in pockets.
-#define ITEM_SLOT_DENYPOCKET (1<<12)
-#define ITEM_SLOT_NECK (1<<13)
-#define ITEM_SLOT_HANDS (1<<14)
-#define ITEM_SLOT_BACKPACK (1<<15)
+#define ITEM_SLOT_DENYPOCKET (1<<13)
+#define ITEM_SLOT_NECK (1<<14)
+#define ITEM_SLOT_HANDS (1<<15)
+#define ITEM_SLOT_BACKPACK (1<<16)
/// Prevents items from being stored in suit storage
-#define ITEM_SLOT_DENY_S_STORE (1<<16)
+#define ITEM_SLOT_DENY_S_STORE (1<<17)
+/// Suit Storage slot
+#define ITEM_SLOT_SUITSTORE (1<<18)
+/// Left Pocket slot
+#define ITEM_SLOT_LPOCKET (1<<19)
+/// Right Pocket slot
+#define ITEM_SLOT_RPOCKET (1<<20)
+/// Handcuff slot
+#define ITEM_SLOT_HANDCUFFED (1<<21)
+/// Legcuff slot (bolas, beartraps)
+#define ITEM_SLOT_LEGCUFFED (1<<22)
+
//SLOTS
#define SLOT_BACK 1
@@ -60,7 +73,7 @@
#define SLOT_W_UNIFORM 14
#define SLOT_L_STORE 15
#define SLOT_R_STORE 16
-#define SLOT_S_STORE 17
+#define SLOT_SUIT_STORE 17
#define SLOT_IN_BACKPACK 18
#define SLOT_LEGCUFFED 19
#define SLOT_GENERC_DEXTROUS_STORAGE 20
@@ -101,6 +114,8 @@
. = ITEM_SLOT_HANDS
if(SLOT_IN_BACKPACK)
. = ITEM_SLOT_BACKPACK
+ if(SLOT_SUIT_STORE)
+ . = ITEM_SLOT_SUITSTORE
//Bit flags for the flags_inv variable, which determine when a piece of clothing hides another. IE a helmet hiding glasses.
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index 56875ba2867f..48621b85f3ad 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -154,3 +154,6 @@
#define SPLASHSCREEN_LAYER 23
#define SPLASHSCREEN_PLANE 23
#define SPLASHSCREEN_RENDER_TARGET "SPLASHSCREEN_PLANE"
+
+///1000 is an unimportant number, it's just to normalize copied layers
+#define RADIAL_CONTENT_LAYER 1000
diff --git a/code/__DEFINES/lighting.dm b/code/__DEFINES/lighting.dm
index 769331a9f4b2..18f3e09c7a6c 100644
--- a/code/__DEFINES/lighting.dm
+++ b/code/__DEFINES/lighting.dm
@@ -33,6 +33,9 @@
/// Set to zero to disable soft lighting. Luminosity changes then work if it's lit at all.
#define LIGHTING_SOFT_THRESHOLD 0
+/// The amount of lumcount on a tile for it to be considered dark (used to determine reading and nyctophobia)
+#define LIGHTING_TILE_IS_DARK 0.2
+
/// If I were you I'd leave this alone.
#define LIGHTING_BASE_MATRIX \
list \
diff --git a/code/__DEFINES/magic.dm b/code/__DEFINES/magic.dm
new file mode 100644
index 000000000000..7eeae378337c
--- /dev/null
+++ b/code/__DEFINES/magic.dm
@@ -0,0 +1,109 @@
+// Magic schools
+
+/// Unset / default / "not actually magic" school.
+#define SCHOOL_UNSET "unset"
+
+// GOOD SCHOOLS (allowed by honorbound gods, some of these you can get on station)
+/// Holy school (chaplain magic)
+#define SCHOOL_HOLY "holy"
+/// Psychic school. Not true magic, but psychic spells only benefit themselves.
+#define SCHOOL_PSYCHIC "psychic"
+/// Mime... school? Mime magic. It counts
+#define SCHOOL_MIME "mime"
+/// Restoration school, which is mostly healing stuff
+#define SCHOOL_RESTORATION "restoration"
+
+// NEUTRAL SPELLS (punished by honorbound gods if you get caught using it)
+/// Evocation school, usually involves killing or destroy stuff, usually out of thin air
+#define SCHOOL_EVOCATION "evocation"
+/// School of transforming stuff into other stuff
+#define SCHOOL_TRANSMUTATION "transmutation"
+/// School of transolcation, usually movement spells
+#define SCHOOL_TRANSLOCATION "translocation"
+/// Conjuration spells summon items / mobs / etc somehow
+#define SCHOOL_CONJURATION "conjuration"
+
+// EVIL SPELLS (instant smite + banishment)
+/// Necromancy spells, usually involves soul / evil / bad stuff
+#define SCHOOL_NECROMANCY "necromancy"
+/// Other forbidden magics, such as heretic spells
+#define SCHOOL_FORBIDDEN "forbidden"
+/// Blood magic, involves vampirism, draining blood, etc.
+#define SCHOOL_SANGUINE "sanguine"
+
+// Invocation types - what does the wizard need to do to invoke (cast) the spell?
+/// Allows being able to cast the spell without saying or doing anything.
+#define INVOCATION_NONE "none"
+/// Forces the wizard to shout the invocation to cast the spell.
+#define INVOCATION_SHOUT "shout"
+/// Forces the wizard to whisper the invocation to cast the spell.
+#define INVOCATION_WHISPER "whisper"
+/// Forces the wizard to emote to cast the spell.
+#define INVOCATION_EMOTE "emote"
+
+// Bitflags for spell requirements
+/// Whether the spell requires wizard clothes to cast.
+#define SPELL_REQUIRES_WIZARD_GARB (1 << 0)
+/// Whether the spell can only be cast by humans (mob type, not species).
+/// SPELL_REQUIRES_WIZARD_GARB comes with this flag implied, as carbons and below can't wear clothes.
+#define SPELL_REQUIRES_HUMAN (1 << 1)
+/// Whether the spell can be cast by mobs who are brains / mmis.
+/// When applying, bear in mind most spells will not function for brains out of the box.
+#define SPELL_CASTABLE_AS_BRAIN (1 << 2)
+/// Whether the spell can be cast while phased, such as blood crawling, ethereal jaunting or using rod form.
+#define SPELL_CASTABLE_WHILE_PHASED (1 << 3)
+/// Whether the spell can be cast while the user has antimagic on them that corresponds to the spell's own antimagic flags.
+#define SPELL_REQUIRES_NO_ANTIMAGIC (1 << 4)
+/// Whether the spell requires being on the station z-level to be cast.
+#define SPELL_REQUIRES_STATION (1 << 5)
+/// Whether the spell must be cast by someone with a mind datum.
+#define SPELL_REQUIRES_MIND (1 << 6)
+/// Whether the spell requires the caster have a mime vow (mindless mobs will succeed this check regardless).
+#define SPELL_REQUIRES_MIME_VOW (1 << 7)
+/// Whether the spell can be cast, even if the caster is unable to speak the invocation
+/// (effectively making the invocation flavor, instead of required).
+#define SPELL_CASTABLE_WITHOUT_INVOCATION (1 << 8)
+
+/*DEFINE_BITFIELD(spell_requirements, list(
+ "SPELL_CASTABLE_AS_BRAIN" = SPELL_CASTABLE_AS_BRAIN,
+ "SPELL_CASTABLE_WHILE_PHASED" = SPELL_CASTABLE_WHILE_PHASED,
+ "SPELL_CASTABLE_WITHOUT_INVOCATION" = SPELL_CASTABLE_WITHOUT_INVOCATION,
+ "SPELL_REQUIRES_HUMAN" = SPELL_REQUIRES_HUMAN,
+ "SPELL_REQUIRES_MIME_VOW" = SPELL_REQUIRES_MIME_VOW,
+ "SPELL_REQUIRES_MIND" = SPELL_REQUIRES_MIND,
+ "SPELL_REQUIRES_NO_ANTIMAGIC" = SPELL_REQUIRES_NO_ANTIMAGIC,
+ "SPELL_REQUIRES_STATION" = SPELL_REQUIRES_STATION,
+ "SPELL_REQUIRES_WIZARD_GARB" = SPELL_REQUIRES_WIZARD_GARB,
+))*/
+
+// Bitflags for teleport spells
+/// Whether the teleport spell skips over space turfs
+#define TELEPORT_SPELL_SKIP_SPACE (1 << 0)
+/// Whether the teleport spell skips over dense turfs
+#define TELEPORT_SPELL_SKIP_DENSE (1 << 1)
+/// Whether the teleport spell skips over blocked turfs
+#define TELEPORT_SPELL_SKIP_BLOCKED (1 << 2)
+
+// Bitflags for magic resistance types
+/// Default magic resistance that blocks normal magic (wizard, spells, magical staff projectiles)
+#define MAGIC_RESISTANCE (1<<0)
+/// Tinfoil hat magic resistance that blocks mental magic (telepathy / mind links, mind curses, abductors)
+#define MAGIC_RESISTANCE_MIND (1<<1)
+/// Holy magic resistance that blocks unholy magic (revenant, vampire, voice of god)
+#define MAGIC_RESISTANCE_HOLY (1<<2)
+
+/*DEFINE_BITFIELD(antimagic_flags, list(
+ "MAGIC_RESISTANCE" = MAGIC_RESISTANCE,
+ "MAGIC_RESISTANCE_HOLY" = MAGIC_RESISTANCE_HOLY,
+ "MAGIC_RESISTANCE_MIND" = MAGIC_RESISTANCE_MIND,
+))*/
+
+/**
+ * Checks if our mob is jaunting actively (within a phased mob object)
+ * Used in jaunting spells specifically to determine whether they should be entering or exiting jaunt
+ *
+ * If you want to use this in non-jaunt related code, it is preferable
+ * to instead check for trait [TRAIT_MAGICALLY_PHASED] instead of using this
+ * as it encompasses more states in which a mob may be "incorporeal from magic"
+ */
+#define is_jaunting(atom) (istype(atom.loc, /obj/effect/dummy/phased_mob))
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 8b116e4cce2c..c1a99d61b24b 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -355,11 +355,6 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
#define BEAT_SLOW 2
#define BEAT_NONE 0
-//https://secure.byond.com/docs/ref/info.html#/atom/var/mouse_opacity
-#define MOUSE_OPACITY_TRANSPARENT 0
-#define MOUSE_OPACITY_ICON 1
-#define MOUSE_OPACITY_OPAQUE 2
-
//world/proc/shelleo
#define SHELLEO_ERRORLEVEL 1
#define SHELLEO_STDOUT 2
@@ -400,13 +395,23 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
#define STACK_CHECK_ADJACENT "adjacent" //checks if there is an object of the result type within one tile
//text files
+/// File location for brain damage traumas
#define BRAIN_DAMAGE_FILE "traumas.json"
+/// File location for AI ion laws
#define ION_FILE "ion_laws.json"
+/// File location for pirate names
#define PIRATE_NAMES_FILE "pirates.json"
+/// File location for redpill questions
#define REDPILL_FILE "redpill.json"
+/// File location for wanted posters messages
#define WANTED_FILE "wanted_message.json"
+/// File location for really dumb suggestions memes
+#define VISTA_FILE "steve.json"
+/// File location for flesh wound descriptions
#define FLESH_SCAR_FILE "wounds/flesh_scar_desc.json"
+/// File location for bone wound descriptions
#define BONE_SCAR_FILE "wounds/bone_scar_desc.json"
+/// File location for scar wound descriptions
#define SCAR_LOC_FILE "wounds/scar_loc.json"
//Fullscreen overlay resolution in tiles.
@@ -450,6 +455,9 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
#define CLIENT_FROM_VAR(I) (ismob(I) ? I:client : (istype(I, /client) ? I : (istype(I, /datum/mind) ? I:current?:client : null)))
+/// Possible value of [/atom/movable/buckle_lying]. If set to a different (positive-or-zero) value than this, the buckling thing will force a lying angle on the buckled.
+#define NO_BUCKLE_LYING -1
+
#define AREASELECT_CORNERA "corner A"
#define AREASELECT_CORNERB "corner B"
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index ec37e27982dc..a1b8fa4e6387 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -18,6 +18,7 @@
#define MOVE_INTENT_RUN "run"
//Blood volumes, in cL
+#define BLOOD_VOLUME_MAX_LETHAL 2150 // The lethal amount for a good semaritan, based off IRL data about vampires
#define BLOOD_VOLUME_GENERIC 560 // The default amount of blood in a blooded creature, in cL, based off IRL data about humans
#define BLOOD_VOLUME_MONKEY 325 // Based on IRL data bout Chimpanzees
#define BLOOD_VOLUME_XENO 700 // Based off data from my asshole
diff --git a/code/__DEFINES/power.dm b/code/__DEFINES/power.dm
index f38b380aec9c..58edfeb91baa 100644
--- a/code/__DEFINES/power.dm
+++ b/code/__DEFINES/power.dm
@@ -6,3 +6,8 @@
#define TESLA_MINI_POWER 869130
//Multiplier of all power consumed.
#define POWER_MOD 1
+
+///conversion ratio from joules to watts
+#define WATTS / 0.002
+///conversion ratio from watts to joules
+#define JOULES * 0.002
diff --git a/code/__DEFINES/security.dm b/code/__DEFINES/security.dm
new file mode 100644
index 000000000000..a385d843398e
--- /dev/null
+++ b/code/__DEFINES/security.dm
@@ -0,0 +1,74 @@
+
+// CATEGORY HEADERS
+
+/// Fingerpints detected
+#define DETSCAN_CATEGORY_FINGERS "Prints"
+/// Displays any bloodprints found and their uefi
+#define DETSCAN_CATEGORY_BLOOD "Blood"
+/// Clothing and glove fibers
+#define DETSCAN_CATEGORY_FIBER "Fibers"
+/// Liquids detected
+#define DETSCAN_CATEGORY_DRINK "Reagents"
+/// ID Access
+#define DETSCAN_CATEGORY_ACCESS "ID Access"
+
+// The categories below do not have hard rules on what info is displayed, and are for categorizing info thematically.
+
+/// Generic extra information category
+#define DETSCAN_CATEGORY_NOTES "Additional Notes"
+/// Attributes that might be illegal, but don't have ties to syndicate/aren't exclusively produced by them
+#define DETSCAN_CATEGORY_ILLEGAL "Illegal Tech"
+/// The emags and other in-house technology from the syndicate
+#define DETSCAN_CATEGORY_SYNDIE "Syndicate Tech"
+/// praise be
+#define DETSCAN_CATEGORY_HOLY "Holy Data"
+/// The mode that the items in, what kind of item is dispensed, etc
+#define DETSCAN_CATEGORY_SETTINGS "Active Settings"
+
+// If your category is not in this list it WILL NOT BE DISPLAYED
+/// defines the order categories are displayed, with the original categories, then custom ones, then finally the extra info.
+#define DETSCAN_DEFAULT_ORDER(...) list(\
+ DETSCAN_CATEGORY_FINGERS, \
+ DETSCAN_CATEGORY_BLOOD, \
+ DETSCAN_CATEGORY_FIBER, \
+ DETSCAN_CATEGORY_DRINK, \
+ DETSCAN_CATEGORY_ACCESS, \
+ DETSCAN_CATEGORY_SETTINGS, \
+ DETSCAN_CATEGORY_HOLY, \
+ DETSCAN_CATEGORY_ILLEGAL, \
+ DETSCAN_CATEGORY_SYNDIE, \
+ DETSCAN_CATEGORY_NOTES, \
+)
+
+/// the order departments show up in for the id scan (its sorted by red to blue on the color wheel)
+#define DETSCAN_ACCESS_ORDER(...) list(\
+ REGION_SECURITY, \
+ REGION_ENGINEERING, \
+ REGION_SUPPLY, \
+ REGION_GENERAL, \
+ REGION_MEDBAY, \
+ REGION_COMMAND, \
+ REGION_RESEARCH, \
+ REGION_CENTCOM, \
+)
+
+/// if any categories list has this entry, it will be hidden
+#define DETSCAN_BLOCK "DETSCAN_BLOCK"
+
+/// Wanted statuses
+#define WANTED_ARREST "Arrest"
+#define WANTED_DISCHARGED "Discharged"
+#define WANTED_NONE "None"
+#define WANTED_PAROLE "Parole"
+#define WANTED_PRISONER "Incarcerated"
+#define WANTED_SUSPECT "Suspected"
+
+/// List of available wanted statuses
+#define WANTED_STATUSES(...) list(\
+ WANTED_NONE, \
+ WANTED_SUSPECT, \
+ WANTED_ARREST, \
+ WANTED_PRISONER, \
+ WANTED_PAROLE, \
+ WANTED_DISCHARGED, \
+)
diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm
index 55d0afdcd524..f7cb9002c996 100644
--- a/code/__DEFINES/sound.dm
+++ b/code/__DEFINES/sound.dm
@@ -10,6 +10,7 @@
#define CHANNEL_BICYCLE 1016
#define CHANNEL_VOICE_ANNOUNCE 1015
#define CHANNEL_MEGAFAUNA 1014 // battle music
+#define CHANNEL_CHARGED_SPELL 1013
//THIS SHOULD ALWAYS BE THE LOWEST ONE!
//KEEP IT UPDATED
diff --git a/code/__DEFINES/spells.dm b/code/__DEFINES/spells.dm
deleted file mode 100644
index 87541bde31f3..000000000000
--- a/code/__DEFINES/spells.dm
+++ /dev/null
@@ -1,11 +0,0 @@
-// Invocation Defines
-#define SPELL_INVOCATION_NONE 0 // Doesn't have an invocation
-#define SPELL_INVOCATION_SAY 1 // Forces the user to say the invocation message
-#define SPELL_INVOCATION_WHISPER 2 // Forces the user to whisper the invocation message
-#define SPELL_INVOCATION_EMOTE 3 // Forces the user to emote the invocation message
-#define SPELL_INVOCATION_MESSAGE 4 // The user creates a visible message, with the invocation being visaible to others and invocation_emote_self being visible to the user
-
-// Charge Type Defines
-#define SPELL_CHARGE_TYPE_RECHARGE 0 // Spell needs to recharge between uses
-#define SPELL_CHARGE_TYPE_CHARGES 1 // Spell has a set number of uses
-#define SPELL_CHARGE_TYPE_HOLDERVAR 2 // Spell adjusts the users 'holder_var_type' by 'holder_var_amount' on use
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 0d00b6c713ac..4d21211a78ce 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -9,6 +9,20 @@
#define STATUS_EFFECT_REFRESH 3 // if it only allows one, and new instances just instead refresh the timer
+///Processing flags - used to define the speed at which the status will work
+///This is fast - 0.2s between ticks (I believe!)
+#define STATUS_EFFECT_FAST_PROCESS 0
+///This is slower and better for more intensive status effects - 1s between ticks
+#define STATUS_EFFECT_NORMAL_PROCESS 1
+
+// Grouped effect sources, see also code/__DEFINES/traits.dm
+
+#define STASIS_MACHINE_EFFECT "stasis_machine"
+
+#define STASIS_CHEMICAL_EFFECT "stasis_chemical"
+
+#define STASIS_SHAPECHANGE_EFFECT "stasis_shapechange"
+
///////////
// BUFFS //
///////////
@@ -32,7 +46,7 @@
#define STATUS_EFFECT_EXERCISED /datum/status_effect/exercised //Prevents heart disease
-#define STATUS_EFFECT_HIPPOCRATIC_OATH /datum/status_effect/hippocraticOath //Gives you an aura of healing as well as regrowing the Rod of Asclepius if lost
+#define STATUS_EFFECT_HIPPOCRATIC_OATH /datum/status_effect/hippocratic_oath //Gives you an aura of healing as well as regrowing the Rod of Asclepius if lost
#define STATUS_EFFECT_GOOD_MUSIC /datum/status_effect/good_music
@@ -172,3 +186,91 @@
#define IS_IN_STASIS(mob) (mob.life_tickrate == 0)
#define LIFETICK_SKIP(living, tick) (living.life_tickrate && (tick % living.life_tickrate) == 0)
+
+// Status effect application helpers.
+// These are macros for easier use of adjust_timed_status_effect and set_timed_status_effect.
+//
+// adjust_x:
+// - Adds duration to a status effect
+// - Removes duration if a negative duration is passed.
+// - Ex: adjust_stutter(10 SECONDS) adds ten seconds of stuttering.
+// - Ex: adjust_jitter(-5 SECONDS) removes five seconds of jittering, or just removes jittering if less than five seconds exist.
+//
+// adjust_x_up_to:
+// - Will only add (or remove) duration of a status effect up to the second parameter
+// - If the duration will result in going beyond the second parameter, it will stop exactly at that parameter
+// - The second parameter cannot be negative.
+// - Ex: adjust_stutter_up_to(20 SECONDS, 10 SECONDS) adds ten seconds of stuttering.
+//
+// set_x:
+// - Set the duration of a status effect to the exact number.
+// - Setting duration to zero seconds is effectively the same as just using remove_status_effect, or qdelling the effect.
+// - Ex: set_stutter(10 SECONDS) sets the stuttering to ten seconds, regardless of whether they had more or less existing stutter.
+//
+// set_x_if_lower:
+// - Will only set the duration of that effect IF any existing duration is lower than what was passed.
+// - Ex: set_stutter_if_lower(10 SECONDS) will set stuttering to ten seconds if no stuttering or less than ten seconds of stuttering exists
+// - Ex: set_jitter_if_lower(20 SECONDS) will do nothing if more than twenty seconds of jittering already exists
+
+#define adjust_stutter(duration) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter)
+#define adjust_stutter_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter, up_to)
+#define set_stutter(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter)
+#define set_stutter_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter, TRUE)
+
+#define adjust_derpspeech(duration) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech)
+#define adjust_derpspeech_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech, up_to)
+#define set_derpspeech(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech)
+#define set_derpspeech_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech, TRUE)
+
+#define adjust_slurring(duration) adjust_timed_status_effect(duration, /datum/status_effect/speech/slurring/drunk)
+#define adjust_slurring_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/speech/slurring/drunk, up_to)
+#define set_slurring(duration) set_timed_status_effect(duration, /datum/status_effect/speech/slurring/drunk)
+#define set_slurring_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/speech/slurring/drunk, TRUE)
+
+#define adjust_dizzy(duration) adjust_timed_status_effect(duration, /datum/status_effect/dizziness)
+#define adjust_dizzy_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/dizziness, up_to)
+#define set_dizzy(duration) set_timed_status_effect(duration, /datum/status_effect/dizziness)
+#define set_dizzy_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/dizziness, TRUE)
+
+#define adjust_jitter(duration) adjust_timed_status_effect(duration, /datum/status_effect/jitter)
+#define adjust_jitter_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/jitter, up_to)
+#define set_jitter(duration) set_timed_status_effect(duration, /datum/status_effect/jitter)
+#define set_jitter_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/jitter, TRUE)
+
+#define adjust_confusion(duration) adjust_timed_status_effect(duration, /datum/status_effect/confusion)
+#define adjust_confusion_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/confusion, up_to)
+#define set_confusion(duration) set_timed_status_effect(duration, /datum/status_effect/confusion)
+#define set_confusion_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/confusion, TRUE)
+
+#define adjust_drugginess(duration) adjust_timed_status_effect(duration, /datum/status_effect/drugginess)
+#define adjust_drugginess_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/drugginess, up_to)
+#define set_drugginess(duration) set_timed_status_effect(duration, /datum/status_effect/drugginess)
+#define set_drugginess_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/drugginess, TRUE)
+
+#define adjust_silence(duration) adjust_timed_status_effect(duration, /datum/status_effect/silenced)
+#define adjust_silence_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/silenced, up_to)
+#define set_silence(duration) set_timed_status_effect(duration, /datum/status_effect/silenced)
+#define set_silence_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/silenced, TRUE)
+
+#define adjust_hallucinations(duration) adjust_timed_status_effect(duration, /datum/status_effect/hallucination)
+#define adjust_hallucinations_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/hallucination, up_to)
+#define set_hallucinations(duration) set_timed_status_effect(duration, /datum/status_effect/hallucination)
+#define set_hallucinations_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/hallucination, TRUE)
+
+#define adjust_drowsiness(duration) adjust_timed_status_effect(duration, /datum/status_effect/drowsiness)
+#define adjust_drowsiness_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/drowsiness, up_to)
+#define set_drowsiness(duration) set_timed_status_effect(duration, /datum/status_effect/drowsiness)
+#define set_drowsiness_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/drowsiness, TRUE)
+
+#define adjust_pacifism(duration) adjust_timed_status_effect(/datum/status_effect/pacify, duration)
+#define set_pacifism(duration) set_timed_status_effect(/datum/status_effect/pacify, duration)
+
+#define adjust_eye_blur(duration) adjust_timed_status_effect(duration, /datum/status_effect/eye_blur)
+#define adjust_eye_blur_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/eye_blur, up_to)
+#define set_eye_blur(duration) set_timed_status_effect(duration, /datum/status_effect/eye_blur)
+#define set_eye_blur_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/eye_blur, TRUE)
+
+#define adjust_temp_blindness(duration) adjust_timed_status_effect(duration, /datum/status_effect/temporary_blindness)
+#define adjust_temp_blindness_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/temporary_blindness, up_to)
+#define set_temp_blindness(duration) set_timed_status_effect(duration, /datum/status_effect/temporary_blindness)
+#define set_temp_blindness_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/temporary_blindness, TRUE)
diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm
index 8a314eba02de..823a88345de6 100644
--- a/code/__DEFINES/text.dm
+++ b/code/__DEFINES/text.dm
@@ -2,4 +2,9 @@
#define MAPTEXT(text) {"[##text]"}
/// Macro from Lummox used to get height from a MeasureText proc
-#define WXH_TO_HEIGHT(x) text2num(copytext(x, findtextEx(x, "x") + 1))
\ No newline at end of file
+#define WXH_TO_HEIGHT(x) text2num(copytext(x, findtextEx(x, "x") + 1))
+
+/*
+ * Uses MAPTEXT to format antag points into a more appealing format
+ */
+#define ANTAG_MAPTEXT(value, color) MAPTEXT("
[round(value)]
")
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 9fce5d4e1244..10c9b73121c7 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -114,6 +114,28 @@
//Remember to update code/datums/traits/ folder if you're adding/removing/renaming traits.
//mob traits
+/// Forces the user to stay unconscious.
+#define TRAIT_KNOCKEDOUT "knockedout"
+/// Prevents voluntary movement.
+#define TRAIT_IMMOBILIZED "immobilized"
+/// Prevents voluntary standing or staying up on its own.
+#define TRAIT_FLOORED "floored"
+/// Forces user to stay standing
+#define TRAIT_FORCED_STANDING "forcedstanding"
+/// Prevents usage of manipulation appendages (picking, holding or using items, manipulating storage).
+#define TRAIT_HANDS_BLOCKED "handsblocked"
+/// Inability to access UI hud elements. Turned into a trait from [MOBILITY_UI] to be able to track sources.
+#define TRAIT_UI_BLOCKED "uiblocked"
+/// Inability to pull things. Turned into a trait from [MOBILITY_PULL] to be able to track sources.
+#define TRAIT_PULL_BLOCKED "pullblocked"
+/// Abstract condition that prevents movement if being pulled and might be resisted against. Handcuffs and straight jackets, basically.
+#define TRAIT_RESTRAINED "restrained"
+/// In some kind of critical condition. Is able to succumb.
+#define TRAIT_CRITICAL_CONDITION "critical-condition"
+/// trait associated to a stat value or range of
+#define STAT_TRAIT "stat"
+#define TRAIT_INCAPACITATED "incapacitated"
+#define HANDCUFFED_TRAIT "handcuffed"
#define TRAIT_BLIND "blind"
#define TRAIT_MUTE "mute"
#define TRAIT_EMOTEMUTE "emotemute"
@@ -233,7 +255,19 @@
#define TRAIT_BADMAIL "badmail" //Your mail is going to be worse than average
#define TRAIT_SHORT_TELOMERES "short_telomeres" //You cannot be CLOONED
#define TRAIT_LONG_TELOMERES "long_telomeres" //You get CLOONED faster!!!
-#define TRAIT_NOVEHICLE "no_vehicle" //for one reason or another, you can't use vehicles
+/// Immune to being afflicted by time stop (spell)
+#define TRAIT_TIME_STOP_IMMUNE "time_stop_immune"
+/// This mob has no soul
+#define TRAIT_NO_SOUL "no_soul"
+/// Whether a spider's consumed this mob
+#define TRAIT_SPIDER_CONSUMED "spider_consumed"
+/// Whether we're sneaking, from the alien sneak ability.
+/// Maybe worth generalizing into a general "is sneaky" / "is stealth" trait in the future.
+#define TRAIT_ALIEN_SNEAK "sneaking_alien"
+/// This mob is phased out of reality from magic, either a jaunt or rod form
+#define TRAIT_MAGICALLY_PHASED "magically_phased"
+///This mob can't use vehicles
+#define TRAIT_NOVEHICLE "no_vehicle"
/// This person is crying
#define TRAIT_CRYING "crying"
@@ -283,6 +317,7 @@
// common trait sources
#define TRAIT_GENERIC "generic"
+#define UNCONSCIOUS_TRAIT "unconscious"
#define EYE_DAMAGE "eye_damage"
#define GENETIC_MUTATION "genetic"
#define OBESITY "obesity"
@@ -297,7 +332,9 @@
#define ADMIN_TRAIT "admin" // (B)admins only.
#define CHANGELING_TRAIT "changeling"
#define CULT_TRAIT "cult"
-#define CURSED_ITEM_TRAIT "cursed-item" // The item is magically cursed
+#define LICH_TRAIT "lich"
+/// The item is magically cursed
+#define CURSED_ITEM_TRAIT(item_type) "cursed_item_[item_type]"
#define ABSTRACT_ITEM_TRAIT "abstract-item"
#define STATUS_EFFECT_TRAIT "status-effect"
#define CLOTHING_TRAIT "clothing"
@@ -306,6 +343,10 @@
#define STATION_TRAIT "station-trait"
#define ATTACHMENT_TRAIT "attachment-trait"
#define GLASSES_TRAIT "glasses"
+/// A trait given by a specific status effect (not sure why we need both but whatever!)
+#define TRAIT_STATUS_EFFECT(effect_id) "[effect_id]-trait"
+/// trait associated to being held in a chokehold
+#define CHOKEHOLD_TRAIT "chokehold"
// unique trait sources, still defines
#define CLONING_POD_TRAIT "cloning-pod"
@@ -349,8 +390,13 @@
#define STARGAZER_TRAIT "stargazer"
#define RANDOM_BLACKOUTS "random_blackouts"
#define MADE_UNCLONEABLE "made-uncloneable"
+#define PULLED_WHILE_SOFTCRIT_TRAIT "pulled-while-softcrit"
+/// Source trait for Bloodsuckers-related traits
#define BLOODSUCKER_TRAIT "bloodsucker_trait"
+/// Source trait during a Frenzy
#define FRENZY_TRAIT "frenzy_trait"
+/// Source trait while Feeding
+#define FEED_TRAIT "feed_trait"
#define HORROR_TRAIT "horror"
#define HOLDER_TRAIT "holder_trait"
#define SINFULDEMON_TRAIT "sinfuldemon"
@@ -366,3 +412,17 @@
#define STATION_TRAIT_EMPTY_MAINT "station_trait_empty_maint"
#define STATION_TRAIT_PDA_GLITCHED "station_trait_pda_glitched"
#define STATION_TRAIT_STATION_ADRIFT "station_trait_station_adrift"
+
+//important_recursive_contents traits
+/*
+ * Used for movables that need to be updated, via COMSIG_ENTER_AREA and COMSIG_EXIT_AREA, when transitioning areas.
+ * Use [/atom/movable/proc/become_area_sensitive(trait_source)] to properly enable it. How you remove it isn't as important.
+ */
+#define TRAIT_AREA_SENSITIVE "area-sensitive"
+///every hearing sensitive atom has this trait
+#define TRAIT_HEARING_SENSITIVE "hearing_sensitive"
+///every object that is currently the active storage of some client mob has this trait
+#define TRAIT_ACTIVE_STORAGE "active_storage"
+
+///Organ traits
+#define TRAIT_BALLMER_SCIENTIST "ballmer_scientist"
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index 9139c6bbb8f5..95d7909b7bd3 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -67,6 +67,7 @@
#define VV_HK_ADDCOMPONENT "addcomponent"
#define VV_HK_MODIFY_TRAITS "modtraits"
#define VV_HK_VIEW_REFERENCES "viewreferences"
+#define VV_HK_WEAKREF_RESOLVE "weakref_resolve"
// /atom
#define VV_HK_MODIFY_TRANSFORM "atom_transform"
#define VV_HK_ADD_REAGENT "addreagent"
diff --git a/code/__DEFINES/{yogs_defines}/antagonists.dm b/code/__DEFINES/{yogs_defines}/antagonists.dm
index 7afc8ce41334..6007a5e48b67 100644
--- a/code/__DEFINES/{yogs_defines}/antagonists.dm
+++ b/code/__DEFINES/{yogs_defines}/antagonists.dm
@@ -9,4 +9,4 @@
#define NOT_DOMINATING -1
#define MAX_LEADERS_GANG 3
-#define INITIAL_DOM_ATTEMPTS 3
+#define INITIAL_DOM_ATTEMPTS 3
\ No newline at end of file
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index 8759f5ea2234..73d0f6b14af6 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -18,6 +18,8 @@
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
#define LAZYLEN(L) length(L)
+///Sets a list to null
+#define LAZYNULL(L) L = null
///Accesses an associative list, returns null if nothing is found
#define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null
///Qdel every item in the list before setting the list to null
diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm
index acc532c21490..dbfdae6bef69 100644
--- a/code/__HELPERS/_logging.dm
+++ b/code/__HELPERS/_logging.dm
@@ -183,6 +183,11 @@
if (CONFIG_GET(flag/log_job_debug))
WRITE_LOG(GLOB.world_job_debug_log, "JOB: [text]")
+/// Logging for wizard powers learned
+/proc/log_spellbook(text)
+ if (CONFIG_GET(flag/log_uplink))
+ WRITE_LOG(GLOB.world_uplink_log, "SPELLBOOK: [text]")
+
/* Log to both DD and the logfile. */
/proc/log_world(text)
#ifdef USE_CUSTOM_ERROR_HANDLER
@@ -204,6 +209,7 @@
/proc/log_mapping(text)
WRITE_LOG(GLOB.world_map_error_log, text)
+
/**
* Appends a tgui-related log entry. All arguments are optional.
diff --git a/code/__HELPERS/hallucinations.dm b/code/__HELPERS/hallucinations.dm
new file mode 100644
index 000000000000..67c1bc7a3d46
--- /dev/null
+++ b/code/__HELPERS/hallucinations.dm
@@ -0,0 +1,250 @@
+/// A global list of all ongoing hallucinations, primarily for easy access to be able to stop (delete) hallucinations.
+GLOBAL_LIST_EMPTY(all_ongoing_hallucinations)
+
+/// What typepath of the hallucination
+#define HALLUCINATION_ARG_TYPE 1
+/// Where the hallucination came from, for logging
+#define HALLUCINATION_ARG_SOURCE 2
+
+/// Onwards from this index, it's the arglist that gets passed into the hallucination created.
+#define HALLUCINATION_ARGLIST 3
+
+/// Biotypes which cannot hallucinate for balance and logic reasons (not code)
+#define NO_HALLUCINATION_BIOTYPES MOB_ROBOTIC || MOB_SPIRIT || MOB_EPIC
+
+// Macro wrapper for _cause_hallucination so we can cheat in named arguments, like AddComponent.
+/**
+ * Causes a hallucination of a certain type to the mob.
+ *
+ * First argument is always the type of halllucination, a /datum/hallucination, required.
+ * second argument is always the key source of the hallucination, used for admin logging, required.
+ *
+ * Additionally, named arguments are supported for passing them forward to the created hallucination's new().
+ */
+#define cause_hallucination(arguments...) _cause_hallucination(list(##arguments))
+
+/// Unless you need this for an explicit reason, use the cause_hallucination wrapper.
+/mob/living/proc/_cause_hallucination(list/raw_args)
+ if(!length(raw_args))
+ CRASH("cause_hallucination called with no arguments.")
+
+ var/datum/hallucination/hallucination_type = raw_args[HALLUCINATION_ARG_TYPE] // first arg is the type always
+ if(!ispath(hallucination_type))
+ CRASH("cause_hallucination was given a non-hallucination type.")
+
+ var/hallucination_source = raw_args[HALLUCINATION_ARG_SOURCE] // and second arg, the source
+ var/datum/hallucination/new_hallucination
+
+ if(length(raw_args) >= HALLUCINATION_ARGLIST)
+ var/list/passed_args = raw_args.Copy(HALLUCINATION_ARGLIST)
+ passed_args.Insert(HALLUCINATION_ARG_TYPE, src)
+
+ new_hallucination = new hallucination_type(arglist(passed_args))
+ else
+ new_hallucination = new hallucination_type(src)
+
+ // For some reason, we qdel'd in New, maybe something went wrong.
+ if(QDELETED(new_hallucination))
+ return
+ // It's not guaranteed that the hallucination passed can successfully be initiated.
+ // This means there may be cases where someone should have a hallucination but nothing happens,
+ // notably if you pass a randomly picked hallucination type into this.
+ // Maybe there should be a separate proc to reroll on failure?
+ if(!new_hallucination.start())
+ qdel(new_hallucination)
+ return
+
+ investigate_log("was afflicted with a hallucination of type [hallucination_type] by: [hallucination_source]. \
+ ([new_hallucination.feedback_details])", INVESTIGATE_HALLUCINATIONS)
+ return new_hallucination
+
+/**
+ * Emits a hallucinating pulse around the passed atom.
+ * Affects everyone in the passed radius who can view the center,
+ * except for those with TRAIT_MADNESS_IMMUNE, or those who are blind.
+ *
+ * center - required, the center of the pulse
+ * radius - the radius around that the pulse reaches
+ * hallucination_duration - how much hallucination is added by the pulse. reduced based on distance to the center.
+ * hallucination_max_duration - a cap on how much hallucination can be added
+ * optional_messages - optional list of messages passed. Those affected by pulses will be given one of the messages in said list.
+ */
+/proc/visible_hallucination_pulse(atom/center, radius = 7, hallucination_duration = 50 SECONDS, hallucination_max_duration, list/optional_messages)
+ for(var/mob/living/nearby_living in view(center, radius))
+// if(HAS_TRAIT(nearby_living, TRAIT_MADNESS_IMMUNE) || (nearby_living.mind && HAS_TRAIT(nearby_living.mind, TRAIT_MADNESS_IMMUNE)))
+// continue
+
+ if(nearby_living.mob_biotypes & NO_HALLUCINATION_BIOTYPES)
+ continue
+
+ if(is_blind(nearby_living))
+ continue
+
+ // Everyone else gets hallucinations.
+ var/dist = sqrt(1 / max(1, get_dist(nearby_living, center)))
+ nearby_living.adjust_hallucinations_up_to(hallucination_duration * dist, hallucination_max_duration)
+ if(length(optional_messages))
+ to_chat(nearby_living, pick(optional_messages))
+
+/// Global weighted list of all hallucinations that can show up randomly.
+GLOBAL_LIST_INIT(random_hallucination_weighted_list, generate_hallucination_weighted_list())
+
+/// Generates the global weighted list of random hallucinations.
+/proc/generate_hallucination_weighted_list()
+ var/list/weighted_list = list()
+
+ for(var/datum/hallucination/hallucination_type as anything in typesof(/datum/hallucination))
+ if(hallucination_type == initial(hallucination_type.abstract_hallucination_parent))
+ continue
+ var/weight = initial(hallucination_type.random_hallucination_weight)
+ if(weight <= 0)
+ continue
+
+ weighted_list[hallucination_type] = weight
+
+ return weighted_list
+
+/// Debug proc for getting the total weight of the random_hallucination_weighted_list
+/proc/debug_hallucination_weighted_list()
+ var/total_weight = 0
+ for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list)
+ total_weight += GLOB.random_hallucination_weighted_list[hallucination_type]
+
+ to_chat(usr, span_boldnotice("The total weight of the hallucination weighted list is [total_weight]."))
+ return total_weight
+
+/// Debug verb for getting the weight of each distinct type within the random_hallucination_weighted_list
+/client/proc/debug_hallucination_weighted_list_per_type()
+ set name = "Show Hallucination Weights"
+ set category = "Debug"
+
+ var/header = "| Type | Weight | Percent | "
+
+ var/total_weight = debug_hallucination_weighted_list()
+ var/list/all_weights = list()
+ var/datum/hallucination/last_type
+ var/last_type_weight = 0
+ for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list)
+ var/this_weight = GLOB.random_hallucination_weighted_list[hallucination_type]
+ // Last_type is the abstract parent of the last hallucination type we iterated over
+ if(last_type)
+ // If this hallucination is the same path as the last type (subtype), add it to the total of the last type weight
+ if(ispath(hallucination_type, last_type))
+ last_type_weight += this_weight
+ continue
+
+ // Otherwise we moved onto the next hallucination subtype so we can stop
+ else
+ all_weights["
|---|
| [last_type] | [last_type_weight] / [total_weight] | [round(100 * (last_type_weight / total_weight), 0.01)]% chance |
"] = last_type_weight
+
+ // Set last_type to the abstract parent of this hallucination
+ last_type = initial(hallucination_type.abstract_hallucination_parent)
+ // If last_type is the base hallucination it has no distinct subtypes so we can total it up immediately
+ if(last_type == /datum/hallucination)
+ all_weights["| [hallucination_type] | [this_weight] / [total_weight] | [round(100 * (this_weight / total_weight), 0.01)]% chance |
"] = this_weight
+ last_type = null
+
+ // Otherwise we start the weight sum for the next entry here
+ else
+ last_type_weight = this_weight
+
+ // Sort by weight descending, where weight is the values (not the keys). We assoc_to_keys later to get JUST the text
+ all_weights = sortTim(all_weights, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE)
+
+ var/page_style = ""
+ var/page_contents = "[page_style][header][jointext(assoc_to_keys(all_weights), "")]
"
+ var/datum/browser/popup = new(mob, "hallucinationdebug", "Hallucination Weights", 600, 400)
+ popup.set_content(page_contents)
+ popup.open()
+
+/// Gets a random subtype of the passed hallucination type that has a random_hallucination_weight > 0.
+/// If no subtype is passed, it will get any random hallucination subtype that is not abstract and has weight > 0.
+/// This can be used instead of picking from the global weighted list to just get a random valid hallucination.
+/proc/get_random_valid_hallucination_subtype(passed_type = /datum/hallucination)
+ if(!ispath(passed_type, /datum/hallucination))
+ CRASH("get_random_valid_hallucination_subtype - get_random_valid_hallucination_subtype passed not a hallucination subtype.")
+
+ for(var/datum/hallucination/hallucination_type as anything in shuffle(subtypesof(passed_type)))
+ if(initial(hallucination_type.abstract_hallucination_parent) == hallucination_type)
+ continue
+ if(initial(hallucination_type.random_hallucination_weight) <= 0)
+ continue
+
+ return hallucination_type
+
+ return null
+
+/// Helper to give the passed mob the ability to select a hallucination from the list of all hallucination subtypes.
+/proc/select_hallucination_type(mob/user, message = "Select a hallucination subtype", title = "Choose Hallucination")
+ var/static/list/hallucinations
+ if(!hallucinations)
+ hallucinations = typesof(/datum/hallucination)
+ for(var/datum/hallucination/hallucination_type as anything in hallucinations)
+ if(initial(hallucination_type.abstract_hallucination_parent) == hallucination_type)
+ hallucinations -= hallucination_type
+
+ var/chosen = tgui_input_list(user, message, title, hallucinations)
+ if(!chosen || !ispath(chosen, /datum/hallucination))
+ return null
+
+ return chosen
+
+/// Helper to give the passed mob the ability to create a delusion hallucination (even a custom one).
+/// Returns a list of arguments - pass these to _cause_hallucination to cause the desired hallucination
+/proc/create_delusion(mob/user)
+ var/static/list/delusions
+ if(!delusions)
+ delusions = typesof(/datum/hallucination/delusion)
+ for(var/datum/hallucination/delusion_type as anything in delusions)
+ if(initial(delusion_type.abstract_hallucination_parent) == delusion_type)
+ delusions -= delusion_type
+
+ var/chosen = tgui_input_list(user, "Select a delusion type. Custom will allow for custom icon entry.", "Select Delusion", delusions)
+ if(!chosen || !ispath(chosen, /datum/hallucination/delusion))
+ return
+
+ var/list/delusion_args = list()
+ var/static/list/options = list("Yes", "No")
+ var/duration = tgui_input_number(user, "How long should it last in seconds?", "Delusion: Duration", max_value = INFINITY, min_value = 1, default = 30)
+ var/affects_us = (tgui_alert(user, "Should they see themselves as the delusion?", "Delusion: Affects us", options) == "Yes")
+ var/affects_others = (tgui_alert(user, "Should they see everyone else delusion?", "Delusion: Affects others", options) == "Yes")
+ var/skip_nearby = (tgui_alert(user, "Should the delusion only affect people outside of their view?", "Delusion: Skip in view", options) == "Yes")
+ var/play_wabbajack = (tgui_alert(user, "Play the wabbajack sound when it happens?", "Delusion: Wabbajack sound", options) == "Yes")
+
+ delusion_args = list(
+ chosen,
+ "forced delusion",
+ duration = duration * 1 SECONDS,
+ affects_us = affects_us,
+ affects_others = affects_others,
+ skip_nearby = skip_nearby,
+ play_wabbajack = play_wabbajack,
+ )
+
+/* if(ispath(chosen, /datum/hallucination/delusion/custom))
+ var/custom_icon_file = input(user, "Pick file for custom delusion:", "Custom Delusion: File") as null|file
+ if(!custom_icon_file)
+ return
+
+ var/custom_icon_state = tgui_input_text(user, "What icon state do you wanna use from the file?", "Custom Delusion: Icon State")
+ if(!custom_icon_state)
+ return
+
+ var/custom_name = tgui_input_text(user, "What name should it show up as? (Can be empty)", "Custom Delusion: Name")
+
+ delusion_args += list(
+ custom_icon_file = custom_icon_file,
+ custom_icon_state = custom_icon_state,
+ custom_name = custom_name,
+ ) */
+
+ return delusion_args
+
+/// Lines the bubblegum hallucinatoin uses when it pops up
+#define BUBBLEGUM_HALLUCINATION_LINES list( \
+ span_colossus("I AM IMMORTAL."), \
+ span_colossus("I SHALL TAKE YOUR WORLD."), \
+ span_colossus("I SEE YOU."), \
+ span_colossus("YOU CANNOT ESCAPE ME FOREVER."), \
+ span_colossus("NOTHING CAN HOLD ME."), \
+ )
diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm
new file mode 100644
index 000000000000..5b5144e0cc2d
--- /dev/null
+++ b/code/__HELPERS/maths.dm
@@ -0,0 +1,157 @@
+///Calculate the angle between two movables and the west|east coordinate
+/proc/get_angle(atom/movable/start, atom/movable/end)//For beams.
+ if(!start || !end)
+ return 0
+ var/dy =(32 * end.y + end.pixel_y) - (32 * start.y + start.pixel_y)
+ var/dx =(32 * end.x + end.pixel_x) - (32 * start.x + start.pixel_x)
+ if(!dy)
+ return (dx >= 0) ? 90 : 270
+ . = arctan(dx/dy)
+ if(dy < 0)
+ . += 180
+ else if(dx < 0)
+ . += 360
+
+/// Angle between two arbitrary points and horizontal line same as [/proc/get_angle]
+/proc/get_angle_raw(start_x, start_y, start_pixel_x, start_pixel_y, end_x, end_y, end_pixel_x, end_pixel_y)
+ var/dy = (32 * end_y + end_pixel_y) - (32 * start_y + start_pixel_y)
+ var/dx = (32 * end_x + end_pixel_x) - (32 * start_x + start_pixel_x)
+ if(!dy)
+ return (dx >= 0) ? 90 : 270
+ . = arctan(dx/dy)
+ if(dy < 0)
+ . += 180
+ else if(dx < 0)
+ . += 360
+
+///for getting the angle when animating something's pixel_x and pixel_y
+/proc/get_pixel_angle(y, x)
+ if(!y)
+ return (x >= 0) ? 90 : 270
+ . = arctan(x/y)
+ if(y < 0)
+ . += 180
+ else if(x < 0)
+ . += 360
+
+/**
+ * Get a list of turfs in a line from `starting_atom` to `ending_atom`.
+ *
+ * Uses the ultra-fast [Bresenham Line-Drawing Algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm).
+ */
+/proc/get_line(atom/starting_atom, atom/ending_atom)
+ var/current_x_step = starting_atom.x//start at x and y, then add 1 or -1 to these to get every turf from starting_atom to ending_atom
+ var/current_y_step = starting_atom.y
+ var/starting_z = starting_atom.z
+
+ var/list/line = list(get_turf(starting_atom))//get_turf(atom) is faster than locate(x, y, z)
+
+ var/x_distance = ending_atom.x - current_x_step //x distance
+ var/y_distance = ending_atom.y - current_y_step
+
+ var/abs_x_distance = abs(x_distance)//Absolute value of x distance
+ var/abs_y_distance = abs(y_distance)
+
+ var/x_distance_sign = SIGN(x_distance) //Sign of x distance (+ or -)
+ var/y_distance_sign = SIGN(y_distance)
+
+ var/x = abs_x_distance >> 1 //Counters for steps taken, setting to distance/2
+ var/y = abs_y_distance >> 1 //Bit-shifting makes me l33t. It also makes get_line() unnessecarrily fast.
+
+ if(abs_x_distance >= abs_y_distance) //x distance is greater than y
+ for(var/distance_counter in 0 to (abs_x_distance - 1))//It'll take abs_x_distance steps to get there
+ y += abs_y_distance
+
+ if(y >= abs_x_distance) //Every abs_y_distance steps, step once in y direction
+ y -= abs_x_distance
+ current_y_step += y_distance_sign
+
+ current_x_step += x_distance_sign //Step on in x direction
+ line += locate(current_x_step, current_y_step, starting_z)//Add the turf to the list
+ else
+ for(var/distance_counter in 0 to (abs_y_distance - 1))
+ x += abs_x_distance
+
+ if(x >= abs_y_distance)
+ x -= abs_y_distance
+ current_x_step += x_distance_sign
+
+ current_y_step += y_distance_sign
+ line += locate(current_x_step, current_y_step, starting_z)
+ return line
+
+///Format a power value in W, kW, MW, or GW.
+/proc/display_power(powerused)
+ if(powerused < 1000) //Less than a kW
+ return "[powerused] W"
+ else if(powerused < 1000000) //Less than a MW
+ return "[round((powerused * 0.001),0.01)] kW"
+ else if(powerused < 1000000000) //Less than a GW
+ return "[round((powerused * 0.000001),0.001)] MW"
+ return "[round((powerused * 0.000000001),0.0001)] GW"
+
+///Format an energy value in J, kJ, MJ, or GJ. 1W = 1J/s.
+/proc/display_joules(units)
+ if (units < 1000) // Less than a kJ
+ return "[round(units, 0.1)] J"
+ else if (units < 1000000) // Less than a MJ
+ return "[round(units * 0.001, 0.01)] kJ"
+ else if (units < 1000000000) // Less than a GJ
+ return "[round(units * 0.000001, 0.001)] MJ"
+ return "[round(units * 0.000000001, 0.0001)] GJ"
+
+/proc/joules_to_energy(joules)
+ return joules * (1 SECONDS) / SSmachines.wait
+
+/proc/energy_to_joules(energy_units)
+ return energy_units * SSmachines.wait / (1 SECONDS)
+
+///Format an energy value measured in Power Cell units.
+/proc/display_energy(units)
+ // APCs process every (SSmachines.wait * 0.1) seconds, and turn 1 W of
+ // excess power into watts when charging cells.
+ // With the current configuration of wait=20 and CELLRATE=0.002, this
+ // means that one unit is 1 kJ.
+ return display_joules(energy_to_joules(units) WATTS)
+
+///chances are 1:value. anyprob(1) will always return true
+/proc/anyprob(value)
+ return (rand(1,value) == value)
+
+///counts the number of bits in Byond's 16-bit width field, in constant time and memory!
+/proc/bit_count(bit_field)
+ var/temp = bit_field - ((bit_field >> 1) & 46811) - ((bit_field >> 2) & 37449) //0133333 and 0111111 respectively
+ temp = ((temp + (temp >> 3)) & 29127) % 63 //070707
+ return temp
+
+/// Returns the name of the mathematical tuple of same length as the number arg (rounded down).
+/proc/make_tuple(number)
+ var/static/list/units_prefix = list("", "un", "duo", "tre", "quattuor", "quin", "sex", "septen", "octo", "novem")
+ var/static/list/tens_prefix = list("", "decem", "vigin", "trigin", "quadragin", "quinquagin", "sexagin", "septuagin", "octogin", "nongen")
+ var/static/list/one_to_nine = list("monuple", "double", "triple", "quadruple", "quintuple", "sextuple", "septuple", "octuple", "nonuple")
+ number = round(number)
+ switch(number)
+ if(0)
+ return "empty tuple"
+ if(1 to 9)
+ return one_to_nine[number]
+ if(10 to 19)
+ return "[units_prefix[(number%10)+1]]decuple"
+ if(20 to 99)
+ return "[units_prefix[(number%10)+1]][tens_prefix[round((number % 100)/10)+1]]tuple"
+ if(100)
+ return "centuple"
+ else //It gets too tedious to use latin prefixes from here.
+ return "[number]-tuple"
+
+/// Takes a value, and a threshold it has to at least match
+/// returns the correctly signed value max'd to the threshold
+/proc/at_least(new_value, threshold)
+ var/sign = SIGN(new_value)
+ // SIGN will return 0 if the value is 0, so we just go to the positive threshold
+ if(!sign)
+ return threshold
+ if(sign == 1)
+ return max(new_value, threshold)
+ if(sign == -1)
+ return min(new_value, threshold * -1)
diff --git a/code/__HELPERS/radiation.dm b/code/__HELPERS/radiation.dm
index 33d5138390c6..baa82c36acf8 100644
--- a/code/__HELPERS/radiation.dm
+++ b/code/__HELPERS/radiation.dm
@@ -1,4 +1,4 @@
-// A special GetAllContents that doesn't search past things with rad insulation
+// A special get_all_contents that doesn't search past things with rad insulation
// Components which return COMPONENT_BLOCK_RADIATION prevent further searching into that object's contents. The object itself will get returned still.
// The ignore list makes those objects never return at all
/proc/get_rad_contents(atom/location)
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index 3043e9f19315..02e3b38b3f19 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -189,10 +189,9 @@
CHECK_TICK
// Add AntagHUD to everyone, see who was really evil the whole time!
- for(var/datum/atom_hud/antag/H in GLOB.huds)
- for(var/m in GLOB.player_list)
- var/mob/M = m
- H.add_hud_to(M)
+ for(var/datum/atom_hud/alternate_appearance/basic/antagonist_hud/antagonist_hud in GLOB.active_alternate_appearances)
+ for(var/mob/player as anything in GLOB.player_list)
+ antagonist_hud.show_to(player)
CHECK_TICK
@@ -586,12 +585,13 @@
/datum/action/report
name = "Show roundend report"
button_icon_state = "round_end"
+ show_to_observers = FALSE
/datum/action/report/Trigger()
if(owner && GLOB.common_report && SSticker.current_state == GAME_STATE_FINISHED)
SSticker.show_roundend_report(owner.client, FALSE)
-/datum/action/report/IsAvailable()
+/datum/action/report/IsAvailable(feedback = FALSE)
return 1
/datum/action/report/Topic(href,href_list)
diff --git a/code/__HELPERS/screen_objs.dm b/code/__HELPERS/screen_objs.dm
new file mode 100644
index 000000000000..aa86b6d57956
--- /dev/null
+++ b/code/__HELPERS/screen_objs.dm
@@ -0,0 +1,91 @@
+/// Takes a screen loc string in the format
+/// "+-left-offset:+-pixel,+-bottom-offset:+-pixel"
+/// Where the :pixel is optional, and returns
+/// A list in the format (x_offset, y_offset)
+/// We require context to get info out of screen locs that contain relative info, so NORTH, SOUTH, etc
+/proc/screen_loc_to_offset(screen_loc, view)
+ if(!screen_loc)
+ return list(64, 64)
+ var/list/view_size = view_to_pixels(view)
+ var/x = 0
+ var/y = 0
+ // Time to parse for directional relative offsets
+ if(findtext(screen_loc, "EAST")) // If you're starting from the east, we start from the east too
+ x += view_size[1]
+ if(findtext(screen_loc, "WEST")) // HHHHHHHHHHHHHHHHHHHHHH WEST is technically a 1 tile offset from the start. Shoot me please
+ x += world.icon_size
+ if(findtext(screen_loc, "NORTH"))
+ y += view_size[2]
+ if(findtext(screen_loc, "SOUTH"))
+ y += world.icon_size
+ // Cut out everything we just parsed
+ screen_loc = cut_relative_direction(screen_loc)
+
+ var/list/x_and_y = splittext(screen_loc, ",")
+ var/list/x_pack = splittext(x_and_y[1], ":")
+ var/list/y_pack = splittext(x_and_y[2], ":")
+ x += text2num(x_pack[1]) * world.icon_size
+ y += text2num(y_pack[1]) * world.icon_size
+
+ if(length(x_pack) > 1)
+ x += text2num(x_pack[2])
+ if(length(y_pack) > 1)
+ y += text2num(y_pack[2])
+ return list(x, y)
+
+/// Takes a list in the form (x_offset, y_offset)
+/// And converts it to a screen loc string
+/// Accepts an optional view string/size to force the screen_loc around, so it can't go out of scope
+/proc/offset_to_screen_loc(x_offset, y_offset, view = null)
+ if(view)
+ var/list/view_bounds = view_to_pixels(view)
+ x_offset = clamp(x_offset, world.icon_size, view_bounds[1])
+ y_offset = clamp(y_offset, world.icon_size, view_bounds[2])
+
+ // Round with no argument is floor, so we get the non pixel offset here
+ var/x = round(x_offset / world.icon_size)
+ var/pixel_x = x_offset % world.icon_size
+ var/y = round(y_offset / world.icon_size)
+ var/pixel_y = y_offset % world.icon_size
+
+ var/list/generated_loc = list()
+ generated_loc += "[x]"
+ if(pixel_x)
+ generated_loc += ":[pixel_x]"
+ generated_loc += ",[y]"
+ if(pixel_y)
+ generated_loc += ":[pixel_y]"
+ return jointext(generated_loc, "")
+
+/**
+ * Returns a valid location to place a screen object without overflowing the viewport
+ *
+ * * target: The target location as a purely number based screen_loc string "+-left-offset:+-pixel,+-bottom-offset:+-pixel"
+ * * target_offset: The amount we want to offset the target location by. We explictly don't care about direction here, we will try all 4
+ * * view: The view variable of the client we're doing this for. We use this to get the size of the screen
+ *
+ * Returns a screen loc representing the valid location
+**/
+/proc/get_valid_screen_location(target_loc, target_offset, view)
+ var/list/offsets = screen_loc_to_offset(target_loc)
+ var/base_x = offsets[1]
+ var/base_y = offsets[2]
+
+ var/list/view_size = view_to_pixels(view)
+
+ // Bias to the right, down, left, and then finally up
+ if(base_x + target_offset < view_size[1])
+ return offset_to_screen_loc(base_x + target_offset, base_y, view)
+ if(base_y - target_offset > world.icon_size)
+ return offset_to_screen_loc(base_x, base_y - target_offset, view)
+ if(base_x - target_offset > world.icon_size)
+ return offset_to_screen_loc(base_x - target_offset, base_y, view)
+ if(base_y + target_offset < view_size[2])
+ return offset_to_screen_loc(base_x, base_y + target_offset, view)
+ stack_trace("You passed in a scren location {[target_loc]} and offset {[target_offset]} that can't be fit in the viewport Width {[view_size[1]]}, Height {[view_size[2]]}. what did you do lad")
+ return null // The fuck did you do lad
+
+/// Takes a screen_loc string and cut out any directions like NORTH or SOUTH
+/proc/cut_relative_direction(fragment)
+ var/static/regex/regex = regex(@"([A-Z])\w+", "g")
+ return regex.Replace(fragment, "")
diff --git a/code/__HELPERS/traits.dm b/code/__HELPERS/traits.dm
new file mode 100644
index 000000000000..ba99b2e1e7ff
--- /dev/null
+++ b/code/__HELPERS/traits.dm
@@ -0,0 +1,43 @@
+#define TRAIT_CALLBACK_ADD(target, trait, source) CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(___TraitAdd), ##target, ##trait, ##source)
+#define TRAIT_CALLBACK_REMOVE(target, trait, source) CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(___TraitRemove), ##target, ##trait, ##source)
+
+///DO NOT USE ___TraitAdd OR ___TraitRemove as a replacement for ADD_TRAIT / REMOVE_TRAIT defines. To be used explicitly for callback.
+/proc/___TraitAdd(target,trait,source)
+ if(!target || !trait || !source)
+ return
+ if(islist(target))
+ for(var/i in target)
+ if(!isatom(i))
+ continue
+ var/atom/the_atom = i
+ ADD_TRAIT(the_atom,trait,source)
+ else if(isatom(target))
+ var/atom/the_atom2 = target
+ ADD_TRAIT(the_atom2,trait,source)
+
+///DO NOT USE ___TraitAdd OR ___TraitRemove as a replacement for ADD_TRAIT / REMOVE_TRAIT defines. To be used explicitly for callback.
+/proc/___TraitRemove(target,trait,source)
+ if(!target || !trait || !source)
+ return
+ if(islist(target))
+ for(var/i in target)
+ if(!isatom(i))
+ continue
+ var/atom/the_atom = i
+ REMOVE_TRAIT(the_atom,trait,source)
+ else if(isatom(target))
+ var/atom/the_atom2 = target
+ REMOVE_TRAIT(the_atom2,trait,source)
+
+
+/// Proc that handles adding multiple traits to a target via a list. Must have a common source and target.
+/datum/proc/add_traits(list/list_of_traits, source)
+ ASSERT(islist(list_of_traits), "Invalid arguments passed to add_traits! Invoked on [src] with [list_of_traits], source being [source].")
+ for(var/trait in list_of_traits)
+ ADD_TRAIT(src, trait, source)
+
+/// Proc that handles removing multiple traits from a target via a list. Must have a common source and target.
+/datum/proc/remove_traits(list/list_of_traits, source)
+ ASSERT(islist(list_of_traits), "Invalid arguments passed to remove_traits! Invoked on [src] with [list_of_traits], source being [source].")
+ for(var/trait in list_of_traits)
+ REMOVE_TRAIT(src, trait, source)
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 317008337d0e..3600fb33ef40 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -440,7 +440,7 @@ Turf and target are separate in case you want to teleport some distance from a t
Gets all contents of contents and returns them all in a list.
*/
-/atom/proc/GetAllContents(T, ignore_flag_1)
+/atom/proc/get_all_contents(T, ignore_flag_1)
var/list/processing_list = list(src)
if(T)
. = list()
@@ -461,9 +461,9 @@ Turf and target are separate in case you want to teleport some distance from a t
processing_list += A.contents
return processing_list
-/atom/proc/GetAllContentsIgnoring(list/ignore_typecache)
+/atom/proc/get_all_contentsIgnoring(list/ignore_typecache)
if(!length(ignore_typecache))
- return GetAllContents()
+ return get_all_contents()
var/list/processing = list(src)
. = list()
var/i = 0
@@ -579,10 +579,6 @@ Turf and target are separate in case you want to teleport some distance from a t
var/dy = abs(B.y - A.y)
return get_dir(A, B) & (rand() * (dx+dy) < dy ? 3 : 12)
-//chances are 1:value. anyprob(1) will always return true
-/proc/anyprob(value)
- return (rand(1,value)==value)
-
/proc/view_or_range(distance = world.view , center = usr , type)
switch(type)
if("view")
@@ -1497,49 +1493,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
)
return pick(subtypesof(new /mob/living/simple_animal/hostile/retaliate/goat) - blocked)
-//For these two procs refs MUST be ref = TRUE format like typecaches!
-/proc/weakref_filter_list(list/things, list/refs)
- if(!islist(things) || !islist(refs))
- return
- if(!refs.len)
- return things
- if(things.len > refs.len)
- var/list/f = list()
- for(var/i in refs)
- var/datum/weakref/r = i
- var/datum/d = r.resolve()
- if(d)
- f |= d
- return things & f
-
- else
- . = list()
- for(var/i in things)
- if(!refs[WEAKREF(i)])
- continue
- . |= i
-
-/proc/weakref_filter_list_reverse(list/things, list/refs)
- if(!islist(things) || !islist(refs))
- return
- if(!refs.len)
- return things
- if(things.len > refs.len)
- var/list/f = list()
- for(var/i in refs)
- var/datum/weakref/r = i
- var/datum/d = r.resolve()
- if(d)
- f |= d
-
- return things - f
- else
- . = list()
- for(var/i in things)
- if(refs[WEAKREF(i)])
- continue
- . |= i
-
/proc/special_list_filter(list/L, datum/callback/condition)
if(!islist(L) || !length(L) || !istype(condition))
return list()
diff --git a/code/__HELPERS/view.dm b/code/__HELPERS/view.dm
index 1c92971802ea..6a4210bc5350 100644
--- a/code/__HELPERS/view.dm
+++ b/code/__HELPERS/view.dm
@@ -1,15 +1,19 @@
/proc/getviewsize(view)
- var/viewX
- var/viewY
if(isnum(view))
var/totalviewrange = (view < 0 ? -1 : 1) + 2 * view
- viewX = totalviewrange
- viewY = totalviewrange
+ return list(totalviewrange, totalviewrange)
else
var/list/viewrangelist = splittext(view,"x")
- viewX = text2num(viewrangelist[1])
- viewY = text2num(viewrangelist[2])
- return list(viewX, viewY)
+ return list(text2num(viewrangelist[1]), text2num(viewrangelist[2]))
+
+/// Takes a string or num view, and converts it to pixel width/height in a list(pixel_width, pixel_height)
+/proc/view_to_pixels(view)
+ if(!view)
+ return list(0, 0)
+ var/list/view_info = getviewsize(view)
+ view_info[1] *= world.icon_size
+ view_info[2] *= world.icon_size
+ return view_info
/proc/in_view_range(mob/user, atom/A)
var/list/view_range = getviewsize(user.client.view)
diff --git a/code/__HELPERS/weakref.dm b/code/__HELPERS/weakref.dm
new file mode 100644
index 000000000000..7e2f82f45aab
--- /dev/null
+++ b/code/__HELPERS/weakref.dm
@@ -0,0 +1,45 @@
+// Checks if potential_weakref is a weakref of thing.
+#define IS_WEAKREF_OF(thing, potential_weakref) (isdatum(thing) && !isnull(potential_weakref) && thing.weak_reference == potential_weakref)
+
+//For these two procs refs MUST be ref = TRUE format like typecaches!
+/proc/weakref_filter_list(list/things, list/refs)
+ if(!islist(things) || !islist(refs))
+ return
+ if(!refs.len)
+ return things
+ if(things.len > refs.len)
+ var/list/f = list()
+ for(var/i in refs)
+ var/datum/weakref/r = i
+ var/datum/d = r.resolve()
+ if(d)
+ f |= d
+ return things & f
+
+ else
+ . = list()
+ for(var/i in things)
+ if(!refs[WEAKREF(i)])
+ continue
+ . |= i
+
+/proc/weakref_filter_list_reverse(list/things, list/refs)
+ if(!islist(things) || !islist(refs))
+ return
+ if(!refs.len)
+ return things
+ if(things.len > refs.len)
+ var/list/f = list()
+ for(var/i in refs)
+ var/datum/weakref/r = i
+ var/datum/d = r.resolve()
+ if(d)
+ f |= d
+
+ return things - f
+ else
+ . = list()
+ for(var/i in things)
+ if(refs[WEAKREF(i)])
+ continue
+ . |= i
diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm
index e76479d0d5d4..3c320fa7aa96 100644
--- a/code/_globalvars/logging.dm
+++ b/code/_globalvars/logging.dm
@@ -24,6 +24,10 @@ GLOBAL_VAR(world_pda_log)
GLOBAL_PROTECT(world_pda_log)
GLOBAL_VAR(world_telecomms_log)
GLOBAL_PROTECT(world_telecomms_log)
+
+GLOBAL_VAR(world_uplink_log)
+GLOBAL_PROTECT(world_uplink_log)
+
GLOBAL_VAR(world_ntsl_log)
GLOBAL_PROTECT(world_ntsl_log)
GLOBAL_VAR(world_manifest_log)
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 19667982d69f..5c532fdbda1e 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -72,7 +72,7 @@
return
next_click = world.time + 1
- if(check_click_intercept(params,A))
+ if(check_click_intercept(params, A))
return
if(notransform)
@@ -116,9 +116,9 @@
var/obj/mecha/M = loc
return M.click_action(A,src,params)
- if(restrained())
+ if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow
- RestrainedClickOn(A)
+ UnarmedAttack(A, FALSE)
return
if(in_throw_mode)
@@ -216,7 +216,7 @@
return ..() + contents
/mob/living/DirectAccess(atom/target)
- return ..() + GetAllContents()
+ return ..() + get_all_contents()
/atom/proc/AllowClick()
return FALSE
@@ -503,7 +503,7 @@
/mob/proc/check_click_intercept(params,A)
//Client level intercept
- if(client && client.click_intercept)
+ if(client?.click_intercept)
if(call(client.click_intercept, "InterceptClickOn")(src, params, A))
return TRUE
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index bf3595d0ae77..607f4a532891 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -63,8 +63,16 @@
return
if(W)
- // buckled cannot prevent machine interlinking but stops arm movement
- if( buckled || incapacitated())
+ if(incapacitated())
+ return
+
+ //while buckled, you can still connect to and control things like doors, but you can't use your modules
+ if(buckled)
+ to_chat(src, span_warning("You can't use modules while buckled to [buckled]!"))
+ return
+
+ //if your "hands" are blocked you shouldn't be able to use modules
+ if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
return
if(W == A)
@@ -170,7 +178,10 @@
change attack_robot() above to the proper function
*/
/mob/living/silicon/robot/UnarmedAttack(atom/A)
+ if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
+ return
A.attack_robot(src)
+
/mob/living/silicon/robot/RangedAttack(atom/A)
A.attack_robot(src)
diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm
index 5408b6992173..df3b9cb5279c 100644
--- a/code/_onclick/drag_drop.dm
+++ b/code/_onclick/drag_drop.dm
@@ -8,31 +8,124 @@
/atom/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params)
if(!usr || !over)
return
- if(SEND_SIGNAL(src, COMSIG_MOUSEDROP_ONTO, over, usr) & COMPONENT_NO_MOUSEDROP) //Whatever is receiving will verify themselves for adjacency.
+ if(SEND_SIGNAL(src, COMSIG_MOUSEDROP_ONTO, over, usr) & COMPONENT_NO_MOUSEDROP) //Whatever is receiving will verify themselves for adjacency.
return
- if(over == src)
- return usr.client.Click(src, src_location, src_control, params)
+ var/proximity_check = usr.client.check_drag_proximity(src, over, src_location, over_location, src_control, over_control, params)
+ if(proximity_check)
+ return proximity_check
+
if(!Adjacent(usr) || !over.Adjacent(usr))
return // should stop you from dragging through windows
- over.MouseDrop_T(src,usr)
+ over.MouseDrop_T(src,usr, params)
return
+/// Handles treating drags as clicks if they're within some conditions
+/// Does some other stuff adjacent to trying to figure out what the user actually "wanted" to click
+/// Returns TRUE if it caused a click, FALSE otherwise
+/client/proc/check_drag_proximity(atom/dragging, atom/over, src_location, over_location, src_control, over_control, params)
+ // We will swap which thing we're trying to check for clickability based off the type
+ // Assertion is if you drag a turf to anything else, you really just wanted to click the anything else
+ // And slightly misseed. I'm not interested in making this game pixel percise, so if it fits our other requirements
+ // Lets just let that through yeah?
+ var/atom/attempt_click = dragging
+ var/atom/click_from = over
+ var/location_to_use = src_location
+ var/control_to_use = src_control
+ if(isturf(attempt_click) && !isturf(over))
+ // swapppp
+ attempt_click = over
+ click_from = dragging
+ location_to_use = over_location
+ control_to_use = over_control
+
+ if(is_drag_clickable(attempt_click, click_from, params))
+ Click(attempt_click, location_to_use, control_to_use, params)
+ return TRUE
+ return FALSE
+
+/// Distance in pixels that we consider "acceptable" from the initial click to the release
+/// Note: this does not account for the position of the object, just where it is on the screen
+#define LENIENCY_DISTANCE 16
+/// Accepted time in seconds between the initial click and drag release
+/// Go higher then this and we just don't care anymore
+#define LENIENCY_TIME (0.1 SECONDS)
+
+/// Does the logic for checking if a drag counts as a click or not
+/// Returns true if it does, false otherwise
+/client/proc/is_drag_clickable(atom/dragging, atom/over, params)
+ if(dragging == over)
+ return TRUE
+ if(world.time - drag_start > LENIENCY_TIME) // Time's up bestie
+ return FALSE
+ if(!get_turf(dragging)) // If it isn't in the world, drop it. This is for things that can move, and we assume hud elements will not have this problem
+ return FALSE
+ // Basically, are you trying to buckle someone down, or drag them onto you?
+ // If so, we know you must be right about what you want
+ if(ismovable(over))
+ var/atom/movable/over_movable = over
+ // The buckle bit will cover most mobs, for stupid reasons. still useful here tho
+ if(over_movable.can_buckle || over_movable == eye)
+ return FALSE
+
+ var/list/modifiers = params2list(params)
+ var/list/old_offsets = screen_loc_to_offset(LAZYACCESS(drag_details, SCREEN_LOC), view)
+ var/list/new_offsets = screen_loc_to_offset(LAZYACCESS(modifiers, SCREEN_LOC), view)
+
+ var/distance = sqrt(((old_offsets[1] - new_offsets[1]) ** 2) + ((old_offsets[2] - new_offsets[2]) ** 2))
+ if(distance > LENIENCY_DISTANCE)
+ return FALSE
+
+ return TRUE
+
// receive a mousedrop
/atom/proc/MouseDrop_T(atom/dropping, mob/user)
SEND_SIGNAL(src, COMSIG_MOUSEDROPPED_ONTO, dropping, user)
- return
+/client/MouseDown(datum/object, location, control, params)
+ if(QDELETED(object)) //Yep, you can click on qdeleted things before they have time to nullspace. Fun.
+ return
+ SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEDOWN, object, location, control, params)
+ if(mouse_down_icon)
+ mouse_pointer_icon = mouse_down_icon
+ var/delay = mob.CanMobAutoclick(object, location, params)
+ if(delay)
+ selected_target[1] = object
+ selected_target[2] = params
+ while(selected_target[1])
+ Click(selected_target[1], location, control, selected_target[2])
+ sleep(delay)
+ active_mousedown_item = mob.canMobMousedown(object, location, params)
+ if(active_mousedown_item)
+ active_mousedown_item.onMouseDown(object, location, params, mob)
+
+/client/MouseUp(object, location, control, params)
+ if(SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEUP, object, location, control, params) & COMPONENT_CLIENT_MOUSEUP_INTERCEPT)
+ click_intercept_time = world.time
+ if(mouse_up_icon)
+ mouse_pointer_icon = mouse_up_icon
+ selected_target[1] = null
+ if(active_mousedown_item)
+ active_mousedown_item.onMouseUp(object, location, params, mob)
+ active_mousedown_item = null
+
/client
var/list/atom/selected_target[2]
var/obj/item/active_mousedown_item = null
var/mouseParams = ""
- var/mouseLocation = null
- var/mouseObject = null
+ ///Used in MouseDrag to preserve the last mouse-entered location. Weakref
+ var/datum/weakref/mouse_location_ref = null
+ ///Used in MouseDrag to preserve the last mouse-entered object. Weakref
+ var/datum/weakref/mouse_object_ref
var/mouseControlObject = null
var/middragtime = 0
- var/atom/middragatom
+ //Middle-mouse-button clicked object control for aimbot exploit detection. Weakref
+ var/datum/weakref/middle_drag_atom_ref
+ //When we started the currently active drag
+ var/drag_start = 0
+ //The params we were passed at the start of the drag, in list form
+ var/list/drag_details
/client/MouseDown(object, location, control, params)
if (mouse_down_icon)
@@ -95,19 +188,19 @@
. = automatic
/atom/proc/IsAutoclickable()
- . = 1
+ return TRUE
/atom/movable/screen/IsAutoclickable()
- . = 0
+ return FALSE
/atom/movable/screen/click_catcher/IsAutoclickable()
- . = 1
+ return TRUE
//Please don't roast me too hard
/client/MouseMove(object,location,control,params)
mouseParams = params
- mouseLocation = location
- mouseObject = object
+ mouse_location_ref = WEAKREF(location)
+ mouse_object_ref = WEAKREF(object)
mouseControlObject = control
if(mob && LAZYLEN(mob.mousemove_intercept_objects))
for(var/datum/D in mob.mousemove_intercept_objects)
@@ -118,30 +211,36 @@
return
/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params)
- var/list/L = params2list(params)
- if (L["middle"])
+ var/list/modifiers = params2list(params)
+ if (LAZYACCESS(modifiers, MIDDLE_CLICK))
if (src_object && src_location != over_location)
middragtime = world.time
- middragatom = src_object
+ middle_drag_atom_ref = WEAKREF(src_object)
else
middragtime = 0
- middragatom = null
+ middle_drag_atom_ref = null
+ if(!drag_start) // If we're just starting to drag
+ drag_start = world.time
+ drag_details = modifiers.Copy()
mouseParams = params
- mouseLocation = over_location
- mouseObject = over_object
- mouseControlObject = over_control
- if(selected_target[1] && over_object && over_object.IsAutoclickable())
+ mouse_location_ref = WEAKREF(over_location)
+ mouse_object_ref = WEAKREF(over_object)
+ if(selected_target[1] && over_object?.IsAutoclickable())
selected_target[1] = over_object
selected_target[2] = params
if(active_mousedown_item)
active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
+ SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEDRAG, src_object, over_object, src_location, over_location, src_control, over_control, params)
+ return ..()
/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
return
-/client/MouseDrop(src_object, over_object, src_location, over_location, src_control, over_control, params)
- if (middragatom == src_object)
+/client/MouseDrop(atom/src_object, atom/over_object, atom/src_location, atom/over_location, src_control, over_control, params)
+ if (IS_WEAKREF_OF(src_object, middle_drag_atom_ref))
middragtime = 0
- middragatom = null
- ..()
\ No newline at end of file
+ middle_drag_atom_ref = null
+ ..()
+ drag_start = 0
+ drag_details = null
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
deleted file mode 100644
index 1c5632b74e01..000000000000
--- a/code/_onclick/hud/_defines.dm
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var.
-
- The short version:
-
- Everything is encoded as strings because apparently that's how Byond rolls.
-
- "1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid.
- "1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid.
- Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy.
-
- In addition, the keywords NORTH, SOUTH, EAST, WEST and CENTER can be used to represent their respective
- screen borders. NORTH-1, for example, is the row just below the upper edge. Useful if you want your
- UI to scale with screen size.
-
- The size of the user's screen is defined by client.view (indirectly by world.view), in our case "15x15".
- Therefore, the top right corner (except during admin shenanigans) is at "15,15"
-*/
-
-//Lower left, persistent menu
-#define ui_inventory "WEST:6,SOUTH:5"
-
-//Middle left indicators
-#define ui_lingchemdisplay "WEST,CENTER-1:15"
-#define ui_lingstingdisplay "WEST:6,CENTER-3:11"
-
-#define ui_devilsouldisplay "WEST:6,CENTER-1:15"
-
-//Lower center, persistent menu
-#define ui_sstore1 "CENTER-5:10,SOUTH:5"
-#define ui_id "CENTER-4:12,SOUTH:5"
-#define ui_belt "CENTER-3:14,SOUTH:5"
-#define ui_back "CENTER-2:14,SOUTH:5"
-
-/proc/ui_hand_position(i) //values based on old hand ui positions (CENTER:-/+16,SOUTH:5)
- var/x_off = -(!(i % 2))
- var/y_off = round((i-1) / 2)
- return"CENTER+[x_off]:16,SOUTH+[y_off]:5"
-
-/proc/ui_equip_position(mob/M)
- var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5)
- return "CENTER:-16,SOUTH+[y_off+1]:5"
-
-/proc/ui_swaphand_position(mob/M, which = 1) //values based on old swaphand ui positions (CENTER: +/-16,SOUTH+1:5)
- var/x_off = which == 1 ? -1 : 0
- var/y_off = round((M.held_items.len-1) / 2)
- return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5"
-
-#define ui_storage1 "CENTER+1:18,SOUTH:5"
-#define ui_storage2 "CENTER+2:20,SOUTH:5"
-
-#define ui_borg_sensor "CENTER-3:16, SOUTH:5" //borgs
-#define ui_borg_lamp "CENTER-4:16, SOUTH:5" //borgs
-#define ui_borg_thrusters "CENTER-5:16, SOUTH:5" //borgs
-#define ui_inv1 "CENTER-2:16,SOUTH:5" //borgs
-#define ui_inv2 "CENTER-1 :16,SOUTH:5" //borgs
-#define ui_inv3 "CENTER :16,SOUTH:5" //borgs
-#define ui_borg_module "CENTER+1:16,SOUTH:5" //borgs
-#define ui_borg_store "CENTER+2:16,SOUTH:5" //borgs
-#define ui_borg_camera "CENTER+3:21,SOUTH:5" //borgs
-#define ui_borg_tablet "CENTER+4:21,SOUTH:5" //borgs
-#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5" //borgs
-
-#define ui_monkey_head "CENTER-5:13,SOUTH:5" //monkey
-#define ui_monkey_mask "CENTER-4:14,SOUTH:5" //monkey
-#define ui_monkey_neck "CENTER-3:15,SOUTH:5" //monkey
-#define ui_monkey_back "CENTER-2:16,SOUTH:5" //monkey
-
-//#define ui_alien_storage_l "CENTER-2:14,SOUTH:5"//alien
-#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"//alien
-#define ui_alien_language_menu "EAST-3:26,SOUTH:5" //alien
-
-#define ui_drone_drop "CENTER+1:18,SOUTH:5" //maintenance drones
-#define ui_drone_pull "CENTER+2:2,SOUTH:5" //maintenance drones
-#define ui_drone_storage "CENTER-2:14,SOUTH:5" //maintenance drones
-#define ui_drone_head "CENTER-3:14,SOUTH:5" //maintenance drones
-
-//Lower right, persistent menu
-#define ui_drop_throw "EAST-1:28,SOUTH+1:7"
-#define ui_above_movement "EAST-2:26,SOUTH+1:7"
-#define ui_above_intent "EAST-3:24, SOUTH+1:7"
-#define ui_movi "EAST-2:26,SOUTH:5"
-#define ui_acti "EAST-3:24,SOUTH:5"
-#define ui_zonesel "EAST-1:28,SOUTH:5"
-#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12)
-#define ui_crafting "EAST-4:22,SOUTH:5"
-#define ui_building "EAST-4:22,SOUTH:21"
-#define ui_borg_pull "EAST-2:26,SOUTH+1:7"
-#define ui_borg_radio "EAST-1:28,SOUTH+1:7"
-#define ui_borg_intents "EAST-2:26,SOUTH:5"
-#define ui_language_menu "EAST-4:6,SOUTH:21"
-
-
-//Upper-middle right (alerts)
-#define ui_alert1 "EAST-1:28,CENTER+5:27"
-#define ui_alert2 "EAST-1:28,CENTER+4:25"
-#define ui_alert3 "EAST-1:28,CENTER+3:23"
-#define ui_alert4 "EAST-1:28,CENTER+2:21"
-#define ui_alert5 "EAST-1:28,CENTER+1:19"
-
-
-//Middle right (status indicators)
-#define ui_healthdoll "EAST-1:28,CENTER-2:13"
-#define ui_health "EAST-1:28,CENTER-1:15"
-#define ui_internal "EAST-1:28,CENTER:17"
-#define ui_mood "EAST-1:28,CENTER-4:10"
-#define ui_stamina "EAST-1:28,CENTER-3:10"
-
-//living
-#define ui_living_pull "EAST-1:28,CENTER-3:15"
-#define ui_living_health "EAST-1:28,CENTER:15"
-#define ui_living_healthdoll "EAST-1:28,CENTER-1:15"
-
-//borgs
-#define ui_borg_health "EAST-1:28,CENTER-1:15" //borgs have the health display where humans have the pressure damage indicator.
-
-//aliens
-#define ui_alien_health "EAST,CENTER-1:15" //aliens have the health display where humans have the pressure damage indicator.
-#define ui_alienplasmadisplay "EAST,CENTER-2:15"
-#define ui_alien_queen_finder "EAST,CENTER-3:15"
-
-//constructs
-#define ui_construct_pull "EAST,CENTER-2:15"
-#define ui_construct_health "EAST,CENTER:15" //same as humans and slimes
-
-//slimes
-#define ui_slime_health "EAST,CENTER:15" //same as humans and constructs
-
-//Blobbernauts
-#define ui_blobbernaut_overmind_health "EAST-1:28,CENTER+0:19"
-
-// AI
-
-#define ui_ai_core "CENTER-6:-32,SOUTH:6"
-#define ui_ai_dashboard "CENTER-7,SOUTH+1:6"
-#define ui_ai_camera_list "CENTER-5:-32,SOUTH:6"
-#define ui_ai_track_with_camera "CENTER-4:-32,SOUTH:6"
-#define ui_ai_camera_light "CENTER-3:-32,SOUTH:6"
-#define ui_ai_crew_monitor "CENTER-2:-32,SOUTH:6"
-#define ui_ai_crew_manifest "CENTER-1:-32,SOUTH:6"
-#define ui_ai_alerts "CENTER:-32,SOUTH:6"
-#define ui_ai_announcement "CENTER+1:-32,SOUTH:6"
-#define ui_ai_shuttle "CENTER+2:-32,SOUTH:6"
-#define ui_ai_state_laws "CENTER+3:-32,SOUTH:6"
-#define ui_ai_pda_send "CENTER+4:-32,SOUTH:6"
-#define ui_ai_pda_log "CENTER+5:-32,SOUTH:6"
-#define ui_ai_take_picture "CENTER+6:-32,SOUTH:6"
-#define ui_ai_view_images "CENTER+7:-32,SOUTH:6"
-#define ui_ai_sensor "CENTER+7,SOUTH:6"
-#define ui_ai_multicam "CENTER+6,SOUTH+1:6"
-#define ui_ai_add_multicam "CENTER+7,SOUTH+1:6"
-#define ui_ai_language_menu "CENTER+5,SOUTH+1:9"
-#define ui_ai_dashboard_widescreen "CENTER-8,SOUTH:6"
-#define ui_ai_add_multicam_widescreen "CENTER+9,SOUTH:6"
-#define ui_ai_multicam_widescreen "CENTER+8,SOUTH:6"
-#define ui_ai_language_menu_widescreen "CENTER-9,SOUTH:10"
-
-// pAI
-
-#define ui_pai_software "SOUTH:6,WEST"
-#define ui_pai_shell "SOUTH:6,WEST+1"
-#define ui_pai_chassis "SOUTH:6,WEST+2"
-#define ui_pai_rest "SOUTH:6,WEST+3"
-#define ui_pai_light "SOUTH:6,WEST+4"
-#define ui_pai_newscaster "SOUTH:6,WEST+5"
-#define ui_pai_host_monitor "SOUTH:6,WEST+6"
-#define ui_pai_crew_manifest "SOUTH:6,WEST+7"
-#define ui_pai_state_laws "SOUTH:6,WEST+8"
-#define ui_pai_pda_send "SOUTH:6,WEST+9"
-#define ui_pai_pda_log "SOUTH:6,WEST+10"
-#define ui_pai_take_picture "SOUTH:6,WEST+12"
-#define ui_pai_view_images "SOUTH:6,WEST+13"
-
-//Pop-up inventory
-#define ui_shoes "WEST+1:8,SOUTH:5"
-
-#define ui_iclothing "WEST:6,SOUTH+1:7"
-#define ui_oclothing "WEST+1:8,SOUTH+1:7"
-#define ui_gloves "WEST+2:10,SOUTH+1:7"
-
-#define ui_glasses "WEST:6,SOUTH+3:11"
-#define ui_mask "WEST+1:8,SOUTH+2:9"
-#define ui_ears "WEST+2:10,SOUTH+2:9"
-#define ui_neck "WEST:6,SOUTH+2:9"
-#define ui_head "WEST+1:8,SOUTH+3:11"
-
-//Ghosts
-
-#define ui_ghost_jumptomob "SOUTH:6,CENTER-3:24"
-#define ui_ghost_orbit "SOUTH:6,CENTER-2:24"
-#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24"
-#define ui_ghost_teleport "SOUTH:6,CENTER:24"
-#define ui_ghost_spawners "SOUTH: 6, CENTER+1:24"
-#define ui_ghost_language_menu "SOUTH: 22,CENTER+2:8"
-#define ui_ghost_pai "SOUTH: 6,CENTER+2:8"
-#define ui_ghost_med "SOUTH: 6,CENTER+3:-8"
-#define ui_ghost_chem "SOUTH: 22,CENTER+3:-8"
-#define ui_ghost_nanite "SOUTH: 6,CENTER+3:8"
-#define ui_ghost_wound "SOUTH: 22,CENTER+3:8"
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index 239bfb99cc62..d5d1c77e2677 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -1,249 +1,473 @@
-#define ACTION_BUTTON_DEFAULT_BACKGROUND "default"
-
/atom/movable/screen/movable/action_button
var/datum/action/linked_action
+ var/datum/hud/our_hud
var/actiontooltipstyle = ""
screen_loc = null
- var/button_icon_state
- var/appearance_cache
+ /// The icon state of our active overlay, used to prevent re-applying identical overlays
+ var/active_overlay_icon_state
+ /// The icon state of our active underlay, used to prevent re-applying identical underlays
+ var/active_underlay_icon_state
+ /// The overlay we have overtop our button
+ var/mutable_appearance/button_overlay
+ /// Where we are currently placed on the hud. SCRN_OBJ_DEFAULT asks the linked action what it thinks
+ var/location = SCRN_OBJ_DEFAULT
+ /// A unique bitflag, combined with the name of our linked action this lets us persistently remember any user changes to our position
var/id
- var/ordered = TRUE //If the button gets placed into the default bar
+ /// A weakref of the last thing we hovered over
+ /// God I hate how dragging works
+ var/datum/weakref/last_hovored_ref
+
+/atom/movable/screen/movable/action_button/Destroy()
+ if(our_hud)
+ var/mob/viewer = our_hud.mymob
+ our_hud.hide_action(src)
+ viewer?.client?.screen -= src
+ linked_action.viewers -= our_hud
+ viewer.update_action_buttons()
+ our_hud = null
+ linked_action = null
+ return ..()
/atom/movable/screen/movable/action_button/proc/can_use(mob/user)
if(HAS_TRAIT(user, TRAIT_NOINTERACT)) // INTERCEPTED
to_chat(user, span_danger("You can't interact with anything right now!"))
return FALSE
- if (linked_action)
- return linked_action.owner == user
- else if (isobserver(user))
- var/mob/dead/observer/O = user
- return !O.observetarget
- else
- return TRUE
+ if(isobserver(user))
+ var/mob/dead/observer/dead_mob = user
+ if(dead_mob.observetarget) // Observers can only click on action buttons if they're not observing something
+ return FALSE
-/atom/movable/screen/movable/action_button/MouseDrop(over_object)
- if(!can_use(usr))
- return
- if((istype(over_object, /atom/movable/screen/movable/action_button) && !istype(over_object, /atom/movable/screen/movable/action_button/hide_toggle)))
- if(locked)
- to_chat(usr, span_warning("Action button \"[name]\" is locked, unlock it first."))
- return
- var/atom/movable/screen/movable/action_button/B = over_object
- var/list/actions = usr.actions
- actions.Swap(actions.Find(src.linked_action), actions.Find(B.linked_action))
- moved = FALSE
- ordered = TRUE
- B.moved = FALSE
- B.ordered = TRUE
- usr.update_action_buttons()
- else
- return ..()
+ if(linked_action)
+ if(linked_action.viewers[user.hud_used])
+ return TRUE
+ return FALSE
+
+ return TRUE
/atom/movable/screen/movable/action_button/Click(location,control,params)
if (!can_use(usr))
- return
+ return FALSE
var/list/modifiers = params2list(params)
- if(modifiers["shift"])
- if(locked)
- to_chat(usr, span_warning("Action button \"[name]\" is locked, unlock it first."))
- return TRUE
- moved = 0
- usr.update_action_buttons() //redraw buttons that are no longer considered "moved"
- return TRUE
- if(modifiers["ctrl"])
- locked = !locked
- to_chat(usr, span_notice("Action button \"[name]\" [locked ? "" : "un"]locked."))
- if(id && usr.client) //try to (un)remember position
- usr.client.prefs.action_buttons_screen_locs["[name]_[id]"] = locked ? moved : null
+ if(LAZYACCESS(modifiers, SHIFT_CLICK))
+ var/datum/hud/our_hud = usr.hud_used
+ our_hud.position_action(src, SCRN_OBJ_DEFAULT)
return TRUE
if(usr.next_click > world.time)
return
usr.next_click = world.time + 1
+// var/trigger_flags
+// if(LAZYACCESS(modifiers, RIGHT_CLICK)) FUCK COMBAT MODE!!!!
+// trigger_flags |= TRIGGER_SECONDARY_ACTION
linked_action.Trigger()
SEND_SOUND(usr, get_sfx("terminal_type"))
transform = turn(matrix() * 0.9, pick(-8, 8))
alpha = 200
animate(src, transform = matrix(), time=0.4 SECONDS, alpha=255)
+
return TRUE
-//Hide/Show Action Buttons ... Button
-/atom/movable/screen/movable/action_button/hide_toggle
- name = "Hide Buttons"
- desc = "Shift-click any button to reset its position, and Control-click it to lock it in place. Alt-click this button to reset all buttons to their default positions."
- icon = 'icons/mob/actions.dmi'
- icon_state = "bg_default"
- var/hidden = 0
- var/hide_icon = 'icons/mob/actions.dmi'
- var/hide_state = "hide"
- var/show_state = "show"
-
-/atom/movable/screen/movable/action_button/hide_toggle/Click(location,control,params)
- if (!can_use(usr))
+// Entered and Exited won't fire while you're dragging something, because you're still "holding" it
+// Very much byond logic, but I want nice behavior, so we fake it with drag
+/atom/movable/screen/movable/action_button/MouseDrag(atom/over_object, src_location, over_location, src_control, over_control, params)
+ . = ..()
+ if(!can_use(usr))
return
+ if(IS_WEAKREF_OF(over_object, last_hovored_ref))
+ return
+ var/atom/old_object
+ if(last_hovored_ref)
+ old_object = last_hovored_ref?.resolve()
+ else // If there's no current ref, we assume it was us. We also treat this as our "first go" location
+ old_object = src
+ var/datum/hud/our_hud = usr.hud_used
+ our_hud?.generate_landings(src)
+
+ if(old_object)
+ old_object.MouseExited(over_location, over_control, params)
+
+ last_hovored_ref = WEAKREF(over_object)
+ over_object.MouseEntered(over_location, over_control, params)
+
+/atom/movable/screen/movable/action_button/MouseEntered(location, control, params)
+ . = ..()
+ if(!QDELETED(src))
+ openToolTip(usr, src, params, title = name, content = desc, theme = actiontooltipstyle)
- var/list/modifiers = params2list(params)
- if(modifiers["shift"])
- if(locked)
- to_chat(usr, span_warning("Action button \"[name]\" is locked, unlock it first."))
- return TRUE
- moved = FALSE
- usr.update_action_buttons(TRUE)
- return TRUE
- if(modifiers["ctrl"])
- locked = !locked
- to_chat(usr, span_notice("Action button \"[name]\" [locked ? "" : "un"]locked."))
- if(id && usr.client) //try to (un)remember position
- usr.client.prefs.action_buttons_screen_locs["[name]_[id]"] = locked ? moved : null
- return TRUE
- if(modifiers["alt"])
- var/buttons_locked = usr.client.prefs.read_preference(/datum/preference/toggle/buttons_locked)
- for(var/V in usr.actions)
- var/datum/action/A = V
- if(A.owner != usr)
- continue // This isnt your button fuck off
- var/atom/movable/screen/movable/action_button/B = A.button
- B.moved = FALSE
- if(B.id && usr.client)
- usr.client.prefs.action_buttons_screen_locs["[B.name]_[B.id]"] = null
- B.locked = buttons_locked
- locked = buttons_locked
- moved = FALSE
- if(id && usr.client)
- usr.client.prefs.action_buttons_screen_locs["[name]_[id]"] = null
- usr.update_action_buttons(TRUE)
- to_chat(usr, span_notice("Action button positions have been reset."))
- return TRUE
- usr.hud_used.action_buttons_hidden = !usr.hud_used.action_buttons_hidden
-
- hidden = usr.hud_used.action_buttons_hidden
- if(hidden)
- name = "Show Buttons"
- else
- name = "Hide Buttons"
- update_icon()
- usr.update_action_buttons()
-
-/atom/movable/screen/movable/action_button/hide_toggle/AltClick(mob/user)
- for(var/V in user.actions)
- var/datum/action/A = V
- if(A.owner != user)
- continue // This isnt your button fuck off
- var/atom/movable/screen/movable/action_button/B = A.button
- B.moved = FALSE
- if(moved)
- moved = FALSE
- user.update_action_buttons(TRUE)
- to_chat(user, span_notice("Action button positions have been reset."))
-
-
-/atom/movable/screen/movable/action_button/hide_toggle/proc/InitialiseIcon(datum/hud/owner_hud)
- var/settings = owner_hud.get_action_buttons_icons()
- icon = settings["bg_icon"]
- icon_state = settings["bg_state"]
- hide_icon = settings["toggle_icon"]
- hide_state = settings["toggle_hide"]
- show_state = settings["toggle_show"]
- update_icon()
-
-/atom/movable/screen/movable/action_button/hide_toggle/update_icon()
- cut_overlays()
- add_overlay(mutable_appearance(hide_icon, hidden ? show_state : hide_state))
+/atom/movable/screen/movable/action_button/MouseExited(location, control, params)
+ closeToolTip(usr)
+ return ..()
+/atom/movable/screen/movable/action_button/MouseDrop(over_object)
+ last_hovored_ref = null
+ if(!can_use(usr))
+ return
+ var/datum/hud/our_hud = usr.hud_used
+ if(over_object == src)
+ our_hud.hide_landings()
+ return
+ if(istype(over_object, /atom/movable/screen/action_landing))
+ var/atom/movable/screen/action_landing/reserve = over_object
+ reserve.hit_by(src)
+ our_hud.hide_landings()
+ save_position()
+ return
-/atom/movable/screen/movable/action_button/MouseEntered(location,control,params)
- if(!QDELETED(src))
- openToolTip(usr,src,params,title = name,content = desc,theme = actiontooltipstyle)
+ our_hud.hide_landings()
+ if(istype(over_object, /atom/movable/screen/button_palette) || istype(over_object, /atom/movable/screen/palette_scroll))
+ our_hud.position_action(src, SCRN_OBJ_IN_PALETTE)
+ save_position()
+ return
+ if(istype(over_object, /atom/movable/screen/movable/action_button))
+ var/atom/movable/screen/movable/action_button/button = over_object
+ our_hud.position_action_relative(src, button)
+ save_position()
+ return
+ . = ..()
+ our_hud.position_action(src, screen_loc)
+ save_position()
+/atom/movable/screen/movable/action_button/proc/save_position()
+ var/mob/user = our_hud.mymob
+ if(!user?.client)
+ return
+ var/position_info = ""
+ switch(location)
+ if(SCRN_OBJ_FLOATING)
+ position_info = screen_loc
+ if(SCRN_OBJ_IN_LIST)
+ position_info = SCRN_OBJ_IN_LIST
+ if(SCRN_OBJ_IN_PALETTE)
+ position_info = SCRN_OBJ_IN_PALETTE
+
+ user.client.prefs.action_buttons_screen_locs["[name]_[id]"] = position_info
+
+/atom/movable/screen/movable/action_button/proc/load_position()
+ var/mob/user = our_hud.mymob
+ if(!user)
+ return
+ var/position_info = user.client?.prefs?.action_buttons_screen_locs["[name]_[id]"] || SCRN_OBJ_DEFAULT
+ user.hud_used.position_action(src, position_info)
-/atom/movable/screen/movable/action_button/MouseExited()
- closeToolTip(usr)
+/atom/movable/screen/movable/action_button/proc/dump_save()
+ var/mob/user = our_hud.mymob
+ if(!user?.client)
+ return
+ user.client.prefs.action_buttons_screen_locs -= "[name]_[id]"
/datum/hud/proc/get_action_buttons_icons()
. = list()
.["bg_icon"] = ui_style
.["bg_state"] = "template"
+ .["bg_state_active"] = "template_active"
+
+/**
+ * Updates all action buttons this mob has.
+ *
+ * Arguments:
+ * * update_flags - Which flags of the action should we update
+ * * force - Force buttons update even if the given button icon state has not changed
+ */
+/mob/proc/update_mob_action_buttons(update_flags = ALL, force = FALSE)
+ for(var/datum/action/current_action as anything in actions)
+ current_action.build_all_button_icons(update_flags, force)
+
+/**
+ * This proc handles adding all of the mob's actions to their screen
+ *
+ * If you just need to update existing buttons, use [/mob/proc/update_mob_action_buttons]!
+ *
+ * Arguments:
+ * * update_flags - reload_screen - bool, if TRUE, this proc will add the button to the screen of the passed mob as well
+ */
+/mob/proc/update_action_buttons(reload_screen = FALSE)
+ if(!hud_used || !client)
+ return
- //TODO : Make these fit theme
- .["toggle_icon"] = 'icons/mob/actions.dmi'
- .["toggle_hide"] = "hide"
- .["toggle_show"] = "show"
-
-//see human and alien hud for specific implementations.
+ if(hud_used.hud_shown != HUD_STYLE_STANDARD)
+ return
-/mob/proc/update_action_buttons_icon(status_only = FALSE)
- for(var/X in actions)
- var/datum/action/A = X
- A.UpdateButtonIcon(status_only)
+ for(var/datum/action/action as anything in actions)
+ var/atom/movable/screen/movable/action_button/button = action.viewers[hud_used]
+ action.build_all_button_icons()
+ if(reload_screen)
+ client.screen += button
-//This is the proc used to update all the action buttons.
-/mob/proc/update_action_buttons(reload_screen, skip_observers=FALSE)
+ if(reload_screen)
+ hud_used.update_our_owner()
+ // This holds the logic for the palette buttons
+ hud_used.palette_actions.refresh_actions()
+
+/**
+ * Show (most) of the another mob's action buttons to this mob
+ *
+ * Used for observers viewing another mob's screen
+ */
+/mob/proc/show_other_mob_action_buttons(mob/take_from)
if(!hud_used || !client)
return
- if(hud_used.hud_shown != HUD_STYLE_STANDARD)
+ for(var/datum/action/action as anything in take_from.actions)
+ if(!action.show_to_observers)
+ continue
+ action.GiveAction(src)
+ RegisterSignal(take_from, COMSIG_MOB_GRANTED_ACTION, PROC_REF(on_observing_action_granted))
+ RegisterSignal(take_from, COMSIG_MOB_REMOVED_ACTION, PROC_REF(on_observing_action_removed))
+
+/**
+ * Hide another mob's action buttons from this mob
+ *
+ * Used for observers viewing another mob's screen
+ */
+/mob/proc/hide_other_mob_action_buttons(mob/take_from)
+ for(var/datum/action/action as anything in take_from.actions)
+ action.HideFrom(src)
+ UnregisterSignal(take_from, list(COMSIG_MOB_GRANTED_ACTION, COMSIG_MOB_REMOVED_ACTION))
+
+/// Signal proc for [COMSIG_MOB_GRANTED_ACTION] - If we're viewing another mob's action buttons,
+/// we need to update with any newly added buttons granted to the mob.
+/mob/proc/on_observing_action_granted(mob/living/source, datum/action/action)
+ SIGNAL_HANDLER
+
+ if(!action.show_to_observers)
return
+ action.GiveAction(src)
+
+/// Signal proc for [COMSIG_MOB_REMOVED_ACTION] - If we're viewing another mob's action buttons,
+/// we need to update with any removed buttons from the mob.
+/mob/proc/on_observing_action_removed(mob/living/source, datum/action/action)
+ SIGNAL_HANDLER
+
+ action.HideFrom(src)
+
+/atom/movable/screen/button_palette
+ desc = "Drag buttons to move them
Shift-click any button to reset it
Alt-click this to reset all buttons"
+ icon = 'icons/hud/64x16_actions.dmi'
+ icon_state = "screen_gen_palette"
+ screen_loc = ui_action_palette
+ var/datum/hud/our_hud
+ var/expanded = FALSE
+ /// Id of any currently running timers that set our color matrix
+ var/color_timer_id
+
+/atom/movable/screen/button_palette/Destroy()
+ if(our_hud)
+ our_hud.mymob?.client?.screen -= src
+ our_hud.toggle_palette = null
+ our_hud = null
+ return ..()
+
+/atom/movable/screen/button_palette/Initialize(mapload)
+ . = ..()
+ update_icon()
- var/button_number = 0
- if(hud_used.action_buttons_hidden)
- for(var/datum/action/A in actions)
- A.button.screen_loc = null
- if(reload_screen)
- client.screen += A.button
+/atom/movable/screen/button_palette/proc/set_hud(datum/hud/our_hud)
+ src.our_hud = our_hud
+ refresh_owner()
+
+/atom/movable/screen/button_palette/proc/update_name(updates)
+ if(expanded)
+ name = "Hide Buttons"
else
- for(var/datum/action/A in actions)
- var/is_owner = (A.owner == src)
- A.UpdateButtonIcon()
- var/atom/movable/screen/movable/action_button/B = A.button
- if(B.ordered)
- button_number++
- if(B.moved && is_owner)
- B.screen_loc = B.moved
- else if(is_owner) // Prevent buttons from shifting around to the original owner
- B.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number)
- if(reload_screen)
- client.screen += B
-
- if(!button_number)
- hud_used.hide_actions_toggle.screen_loc = null
- return
-
- if(!hud_used.hide_actions_toggle.moved)
- hud_used.hide_actions_toggle.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number+1)
+ name = "Show Buttons"
+
+/atom/movable/screen/button_palette/proc/refresh_owner()
+ var/mob/viewer = our_hud.mymob
+ if(viewer.client)
+ viewer.client.screen |= src
+
+ var/list/settings = our_hud.get_action_buttons_icons()
+ var/ui_icon = "[settings["bg_icon"]]"
+ var/list/ui_segments = splittext(ui_icon, ".")
+ var/list/ui_paths = splittext(ui_segments[1], "/")
+ var/ui_name = ui_paths[length(ui_paths)]
+
+ icon_state = "[ui_name]_palette"
+
+/atom/movable/screen/button_palette/MouseEntered(location, control, params)
+ . = ..()
+ if(QDELETED(src))
+ return
+ show_tooltip(params)
+
+/atom/movable/screen/button_palette/MouseExited()
+ closeToolTip(usr)
+ return ..()
+
+/atom/movable/screen/button_palette/proc/show_tooltip(params)
+ openToolTip(usr, src, params, title = name, content = desc)
+
+GLOBAL_LIST_INIT(palette_added_matrix, list(0.4,0.5,0.2,0, 0,1.4,0,0, 0,0.4,0.6,0, 0,0,0,1, 0,0,0,0))
+GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6,0, 0,0,0,1, 0,0,0,0))
+
+/atom/movable/screen/button_palette/proc/play_item_added()
+ color_for_now(GLOB.palette_added_matrix)
+
+/atom/movable/screen/button_palette/proc/play_item_removed()
+ color_for_now(GLOB.palette_removed_matrix)
+
+/atom/movable/screen/button_palette/proc/color_for_now(list/color)
+ if(color_timer_id)
+ return
+ add_atom_colour(color, TEMPORARY_COLOUR_PRIORITY) //We unfortunately cannot animate matrix colors. Curse you lummy it would be ~~non~~trivial to interpolate between the two valuessssssssss
+ color_timer_id = addtimer(CALLBACK(src, PROC_REF(remove_color), color), 2 SECONDS)
+
+/atom/movable/screen/button_palette/proc/remove_color(list/to_remove)
+ color_timer_id = null
+ remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, to_remove)
+
+/atom/movable/screen/button_palette/proc/can_use(mob/user)
+ if (isobserver(user))
+ var/mob/dead/observer/O = user
+ return !O.observetarget
+ return TRUE
+
+/atom/movable/screen/button_palette/Click(location, control, params)
+ if(!can_use(usr))
+ return
+
+ var/list/modifiers = params2list(params)
+
+ if(LAZYACCESS(modifiers, ALT_CLICK))
+ for(var/datum/action/action as anything in usr.actions) // Reset action positions to default
+ for(var/datum/hud/hud as anything in action.viewers)
+ var/atom/movable/screen/movable/action_button/button = action.viewers[hud]
+ hud.position_action(button, SCRN_OBJ_DEFAULT)
+ to_chat(usr, span_notice("Action button positions have been reset."))
+ return TRUE
+
+ set_expanded(!expanded)
+
+/atom/movable/screen/button_palette/proc/clicked_while_open(datum/source, atom/target, atom/location, control, params, mob/user)
+ if(istype(target, /atom/movable/screen/movable/action_button) || istype(target, /atom/movable/screen/palette_scroll) || target == src) // If you're clicking on an action button, or us, you can live
+ return
+ set_expanded(FALSE)
+ if(source)
+ UnregisterSignal(source, COMSIG_CLIENT_CLICK)
+
+/atom/movable/screen/button_palette/proc/set_expanded(new_expanded)
+ var/datum/action_group/our_group = our_hud.palette_actions
+ if(!length(our_group.actions)) //Looks dumb, trust me lad
+ new_expanded = FALSE
+ if(expanded == new_expanded)
+ return
+
+ expanded = new_expanded
+ our_group.refresh_actions()
+ update_icon()
+
+ if(!usr.client)
+ return
+
+ if(expanded)
+ RegisterSignal(usr.client, COMSIG_CLIENT_CLICK, PROC_REF(clicked_while_open))
else
- hud_used.hide_actions_toggle.screen_loc = hud_used.hide_actions_toggle.moved
- if(reload_screen)
- client.screen += hud_used.hide_actions_toggle
+ UnregisterSignal(usr.client, COMSIG_CLIENT_CLICK)
- if(!skip_observers)
- for(var/mob/dead/observer/O in observers) // This is usually always called instead of Grant() or Remove()
- O.actions = actions + O.originalactions
- O.update_action_buttons(skip_observers=TRUE)
+ closeToolTip(usr) //Our tooltips are now invalid, can't seem to update them in one frame, so here, just close them
+/atom/movable/screen/palette_scroll
+ icon = 'icons/mob/screen_gen.dmi'
+ screen_loc = ui_palette_scroll
+ /// How should we move the palette's actions?
+ /// Positive scrolls down the list, negative scrolls back
+ var/scroll_direction = 0
+ var/datum/hud/our_hud
+/atom/movable/screen/palette_scroll/proc/can_use(mob/user)
+ if (isobserver(user))
+ var/mob/dead/observer/O = user
+ return !O.observetarget
+ return TRUE
-#define AB_MAX_COLUMNS 10
+/atom/movable/screen/palette_scroll/proc/set_hud(datum/hud/our_hud)
+ src.our_hud = our_hud
+ refresh_owner()
-/datum/hud/proc/ButtonNumberToScreenCoords(number) // TODO : Make this zero-indexed for readabilty
- var/row = round((number - 1)/AB_MAX_COLUMNS)
- var/col = ((number - 1)%(AB_MAX_COLUMNS)) + 1
+/atom/movable/screen/palette_scroll/proc/refresh_owner()
+ var/mob/viewer = our_hud.mymob
+ if(viewer.client)
+ viewer.client.screen |= src
- var/coord_col = "+[col-1]"
- var/coord_col_offset = 4 + 2 * col
+ var/list/settings = our_hud.get_action_buttons_icons()
+ icon = settings["bg_icon"]
- var/coord_row = "[row ? -row : "+0"]"
+/atom/movable/screen/palette_scroll/Click(location, control, params)
+ if(!can_use(usr))
+ return
+ our_hud.palette_actions.scroll(scroll_direction)
- return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-6"
+/atom/movable/screen/palette_scroll/MouseEntered(location, control, params)
+ . = ..()
+ if(QDELETED(src))
+ return
+ openToolTip(usr, src, params, title = name, content = desc)
-/datum/hud/proc/SetButtonCoords(atom/movable/screen/button,number)
- var/row = round((number-1)/AB_MAX_COLUMNS)
- var/col = ((number - 1)%(AB_MAX_COLUMNS)) + 1
- var/x_offset = 32*(col-1) + 4 + 2*col
- var/y_offset = -32*(row+1) + 26
+/atom/movable/screen/palette_scroll/MouseExited()
+ closeToolTip(usr)
+ return ..()
+
+/atom/movable/screen/palette_scroll/down
+ name = "Scroll Down"
+ desc = "Click on this to scroll the actions above down"
+ icon_state = "scroll_down"
+ scroll_direction = 1
+
+/atom/movable/screen/palette_scroll/down/Destroy()
+ if(our_hud)
+ our_hud.mymob?.client?.screen -= src
+ our_hud.palette_down = null
+ our_hud = null
+ return ..()
+
+/atom/movable/screen/palette_scroll/up
+ name = "Scroll Up"
+ desc = "Click on this to scroll the actions above up"
+ icon_state = "scroll_up"
+ scroll_direction = -1
+
+/atom/movable/screen/palette_scroll/up/Destroy()
+ if(our_hud)
+ our_hud.mymob?.client?.screen -= src
+ our_hud.palette_up = null
+ our_hud = null
+ return ..()
+
+/// Exists so you have a place to put your buttons when you move them around
+/atom/movable/screen/action_landing
+ name = "Button Space"
+ desc = "Drag and drop a button into this spot
to add it to the group"
+ icon = 'icons/mob/screen_gen.dmi'
+ icon_state = "reserved"
+ // We want our whole 32x32 space to be clickable, so dropping's forgiving
+ mouse_opacity = MOUSE_OPACITY_OPAQUE
+ var/datum/action_group/owner
+
+/atom/movable/screen/action_landing/Destroy()
+ if(owner)
+ owner.landing = null
+ owner?.owner?.mymob?.client?.screen -= src
+ owner.refresh_actions()
+ owner = null
+ return ..()
+
+/atom/movable/screen/action_landing/proc/set_owner(datum/action_group/owner)
+ src.owner = owner
+ refresh_owner()
+
+/atom/movable/screen/action_landing/proc/refresh_owner()
+ var/datum/hud/our_hud = owner.owner
+ var/mob/viewer = our_hud.mymob
+ if(viewer.client)
+ viewer.client.screen |= src
+
+ var/list/settings = our_hud.get_action_buttons_icons()
+ icon = settings["bg_icon"]
- var/matrix/M = matrix()
- M.Translate(x_offset,y_offset)
- button.transform = M
+/// Reacts to having a button dropped on it
+/atom/movable/screen/action_landing/proc/hit_by(atom/movable/screen/movable/action_button/button)
+ var/datum/hud/our_hud = owner.owner
+ our_hud.position_action(button, owner.location)
diff --git a/code/_onclick/hud/devil.dm b/code/_onclick/hud/devil.dm
index 5ef6c0b2afdb..3e4b39832322 100644
--- a/code/_onclick/hud/devil.dm
+++ b/code/_onclick/hud/devil.dm
@@ -41,7 +41,6 @@
zone_select.icon = ui_style
zone_select.update_icon(mymob)
- lingchemdisplay = new /atom/movable/screen/ling/chems()
devilsouldisplay = new /atom/movable/screen/devil/soul_counter
infodisplay += devilsouldisplay
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 57e4bb785f4a..9c305e79af83 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -26,13 +26,6 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
var/inventory_shown = FALSE //Equipped item inventory
var/hotkey_ui_hidden = FALSE //This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons)
- var/atom/movable/screen/ling/chems/lingchemdisplay
- var/atom/movable/screen/ling/sting/lingstingdisplay
-
- var/atom/movable/screen/bloodsucker/blood_counter/blood_display
- var/atom/movable/screen/bloodsucker/rank_counter/vamprank_display
- var/atom/movable/screen/bloodsucker/sunlight_counter/sunlight_display
-
var/atom/movable/screen/blobpwrdisplay
var/atom/movable/screen/alien_plasma_display
@@ -51,13 +44,20 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
var/list/toggleable_inventory = list() //the screen objects which can be hidden
var/list/atom/movable/screen/hotkeybuttons = list() //the buttons that can be used via hotkeys
var/list/infodisplay = list() //the screen objects that display mob info (health, alien plasma, etc...)
+ /// Screen objects that never exit view.
+ var/list/always_visible_inventory = list()
var/list/screenoverlays = list() //the screen objects used as whole screen overlays (flash, damageoverlay, etc...)
var/list/inv_slots[SLOTS_AMT] // /atom/movable/screen/inventory objects, ordered by their slot ID.
var/list/hand_slots // /atom/movable/screen/inventory/hand objects, assoc list of "[held_index]" = object
var/list/atom/movable/screen/plane_master/plane_masters = list() // see "appearance_flags" in the ref, assoc list of "[plane]" = object
- var/atom/movable/screen/movable/action_button/hide_toggle/hide_actions_toggle
- var/action_buttons_hidden = FALSE
+ var/atom/movable/screen/button_palette/toggle_palette
+ var/atom/movable/screen/palette_scroll/down/palette_down
+ var/atom/movable/screen/palette_scroll/up/palette_up
+
+ var/datum/action_group/palette/palette_actions
+ var/datum/action_group/listed/listed_actions
+ var/list/floating_actions
var/atom/movable/screen/healths
var/atom/movable/screen/healthdoll
@@ -74,10 +74,12 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
// will fall back to the default if any of these are null
ui_style = ui_style2icon(owner.client?.prefs?.read_preference(/datum/preference/choiced/ui_style))
- hide_actions_toggle = new
- hide_actions_toggle.InitialiseIcon(src)
- if(mymob.client)
- hide_actions_toggle.locked = mymob.client.prefs.read_preference(/datum/preference/toggle/buttons_locked)
+ toggle_palette = new()
+ toggle_palette.set_hud(src)
+ palette_down = new()
+ palette_down.set_hud(src)
+ palette_up = new()
+ palette_up.set_hud(src)
hand_slots = list()
@@ -90,7 +92,13 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
if(mymob.hud_used == src)
mymob.hud_used = null
- QDEL_NULL(hide_actions_toggle)
+ QDEL_NULL(toggle_palette)
+ QDEL_NULL(palette_down)
+ QDEL_NULL(palette_up)
+ QDEL_NULL(palette_actions)
+ QDEL_NULL(listed_actions)
+ QDEL_LIST(floating_actions)
+
QDEL_NULL(module_store_icon)
QDEL_LIST(static_inventory)
@@ -108,15 +116,14 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
stamina = null
healthdoll = null
internals = null
- lingchemdisplay = null
devilsouldisplay = null
- lingstingdisplay = null
blobpwrdisplay = null
alien_plasma_display = null
alien_queen_finder = null
QDEL_LIST_ASSOC_VAL(plane_masters)
QDEL_LIST(screenoverlays)
+ QDEL_LIST(always_visible_inventory)
mymob = null
return ..()
@@ -127,11 +134,21 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
/mob/proc/create_mob_hud()
if(!client || hud_used)
return
- hud_used = new hud_type(src)
+ set_hud_used(new hud_type(src))
update_sight()
SEND_SIGNAL(src, COMSIG_MOB_HUD_CREATED)
-//Version denotes which style should be displayed. blank or 0 means "next version"
+/mob/proc/set_hud_used(datum/hud/new_hud)
+ hud_used = new_hud
+ new_hud.build_action_groups()
+
+/**
+ * Shows this hud's hud to some mob
+ *
+ * Arguments
+ * * version - denotes which style should be displayed. blank or 0 means "next version"
+ * * viewmob - what mob to show the hud to. Can be this hud's mob, can be another mob, can be null (will use this hud's mob if so)
+ */
/datum/hud/proc/show_hud(version = 0, mob/viewmob)
if(!ismob(mymob))
return FALSE
@@ -139,18 +156,20 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
if(!screenmob.client)
return FALSE
+ // This code is the absolute fucking worst, I want it to go die in a fire
+ // Seriously, why
screenmob.client.screen = list()
screenmob.client.apply_clickcatcher()
var/display_hud_version = version
- if(!display_hud_version) //If 0 or blank, display the next hud version
+ if(!display_hud_version) //If 0 or blank, display the next hud version
display_hud_version = hud_version + 1
- if(display_hud_version > HUD_VERSIONS) //If the requested version number is greater than the available versions, reset back to the first version
+ if(display_hud_version > HUD_VERSIONS) //If the requested version number is greater than the available versions, reset back to the first version
display_hud_version = 1
switch(display_hud_version)
- if(HUD_STYLE_STANDARD) //Default HUD
- hud_shown = TRUE //Governs behavior of other procs
+ if(HUD_STYLE_STANDARD) //Default HUD
+ hud_shown = TRUE //Governs behavior of other procs
if(static_inventory.len)
screenmob.client.screen += static_inventory
if(toggleable_inventory.len && screenmob.hud_used && screenmob.hud_used.inventory_shown)
@@ -159,14 +178,16 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
screenmob.client.screen += hotkeybuttons
if(infodisplay.len)
screenmob.client.screen += infodisplay
+ if(always_visible_inventory.len)
+ screenmob.client.screen += always_visible_inventory
- screenmob.client.screen += hide_actions_toggle
+ screenmob.client.screen += toggle_palette
if(action_intent)
action_intent.screen_loc = initial(action_intent.screen_loc) //Restore intent selection to the original position
- if(HUD_STYLE_REDUCED) //Reduced HUD
- hud_shown = FALSE //Governs behavior of other procs
+ if(HUD_STYLE_REDUCED) //Reduced HUD
+ hud_shown = FALSE //Governs behavior of other procs
if(static_inventory.len)
screenmob.client.screen -= static_inventory
if(toggleable_inventory.len)
@@ -175,6 +196,8 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
screenmob.client.screen -= hotkeybuttons
if(infodisplay.len)
screenmob.client.screen += infodisplay
+ if(always_visible_inventory.len)
+ screenmob.client.screen += always_visible_inventory
//These ones are a part of 'static_inventory', 'toggleable_inventory' or 'hotkeybuttons' but we want them to stay
for(var/h in hand_slots)
@@ -182,11 +205,11 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
if(hand)
screenmob.client.screen += hand
if(action_intent)
- screenmob.client.screen += action_intent //we want the intent switcher visible
- action_intent.screen_loc = ui_acti_alt //move this to the alternative position, where zone_select usually is.
+ screenmob.client.screen += action_intent //we want the intent switcher visible
+ action_intent.screen_loc = ui_acti_alt //move this to the alternative position, where zone_select usually is.
- if(HUD_STYLE_NOHUD) //No HUD
- hud_shown = FALSE //Governs behavior of other procs
+ if(HUD_STYLE_NOHUD) //No HUD
+ hud_shown = FALSE //Governs behavior of other procs
if(static_inventory.len)
screenmob.client.screen -= static_inventory
if(toggleable_inventory.len)
@@ -195,11 +218,15 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
screenmob.client.screen -= hotkeybuttons
if(infodisplay.len)
screenmob.client.screen -= infodisplay
+ if(always_visible_inventory.len)
+ screenmob.client.screen += always_visible_inventory
hud_version = display_hud_version
persistent_inventory_update(screenmob)
- screenmob.update_action_buttons(1)
- reorganize_alerts()
+ // Gives all of the actions the screenmob owes to their hud
+ screenmob.update_action_buttons(TRUE)
+ // Handles alerts - the things on the right side of the screen
+ reorganize_alerts(screenmob)
screenmob.reload_fullscreen()
update_parallax_pref(screenmob)
@@ -210,8 +237,9 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
show_hud(hud_version, M)
else if (viewmob.hud_used)
viewmob.hud_used.plane_masters_update()
- viewmob.client.screen -= hide_actions_toggle // Hide actions works a bit funny so the observer cant ever use it
+ viewmob.show_other_mob_action_buttons(mymob)
+ SEND_SIGNAL(screenmob, COMSIG_MOB_HUD_REFRESHED, src)
return TRUE
/datum/hud/proc/plane_masters_update()
@@ -246,13 +274,12 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
if (initial(ui_style) || ui_style == new_ui_style)
return
- for(var/atom/item in static_inventory + toggleable_inventory + hotkeybuttons + infodisplay + screenoverlays + inv_slots)
+ for(var/atom/item in static_inventory + toggleable_inventory + hotkeybuttons + infodisplay + screenoverlays + always_visible_inventory + inv_slots)
if (item.icon == ui_style)
item.icon = new_ui_style
ui_style = new_ui_style
build_hand_slots()
- hide_actions_toggle.InitialiseIcon(src)
//Triggered when F12 is pressed (Unless someone changed something in the DMF)
/mob/verb/button_pressed_F12()
@@ -300,3 +327,315 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
/datum/hud/proc/update_locked_slots()
return
+
+
+/datum/hud/proc/position_action(atom/movable/screen/movable/action_button/button, position)
+ // This is kinda a hack, I'm sorry.
+ // Basically, FLOATING is never a valid position to pass into this proc. It exists as a generic marker for manually positioned buttons
+ // Not as a position to target
+ if(position == SCRN_OBJ_FLOATING)
+ return
+ if(button.location != SCRN_OBJ_DEFAULT)
+ hide_action(button)
+ switch(position)
+ if(SCRN_OBJ_DEFAULT) // Reset to the default
+ button.dump_save() // Nuke any existing saves
+ position_action(button, button.linked_action.default_button_position)
+ return
+ if(SCRN_OBJ_IN_LIST)
+ listed_actions.insert_action(button)
+ if(SCRN_OBJ_IN_PALETTE)
+ palette_actions.insert_action(button)
+ if(SCRN_OBJ_INSERT_FIRST)
+ listed_actions.insert_action(button, index = 1)
+ position = SCRN_OBJ_IN_LIST
+ else // If we don't have it as a define, this is a screen_loc, and we should be floating
+ floating_actions += button
+ button.screen_loc = position
+ position = SCRN_OBJ_FLOATING
+
+ button.location = position
+
+/datum/hud/proc/position_action_relative(atom/movable/screen/movable/action_button/button, atom/movable/screen/movable/action_button/relative_to)
+ if(button.location != SCRN_OBJ_DEFAULT)
+ hide_action(button)
+ switch(relative_to.location)
+ if(SCRN_OBJ_IN_LIST)
+ listed_actions.insert_action(button, listed_actions.index_of(relative_to))
+ if(SCRN_OBJ_IN_PALETTE)
+ palette_actions.insert_action(button, palette_actions.index_of(relative_to))
+ if(SCRN_OBJ_FLOATING) // If we don't have it as a define, this is a screen_loc, and we should be floating
+ floating_actions += button
+ var/client/our_client = mymob.client
+ if(!our_client)
+ position_action(button, button.linked_action.default_button_position)
+ return
+ button.screen_loc = get_valid_screen_location(relative_to.screen_loc, world.icon_size, our_client.view_size.getView()) // Asks for a location adjacent to our button that won't overflow the map
+
+ button.location = relative_to.location
+
+/// Removes the passed in action from its current position on the screen
+/datum/hud/proc/hide_action(atom/movable/screen/movable/action_button/button)
+ switch(button.location)
+ if(SCRN_OBJ_DEFAULT) // Invalid
+ CRASH("We just tried to hide an action buttion that somehow has the default position as its location, you done fucked up")
+ if(SCRN_OBJ_FLOATING)
+ floating_actions -= button
+ if(SCRN_OBJ_IN_LIST)
+ listed_actions.remove_action(button)
+ if(SCRN_OBJ_IN_PALETTE)
+ palette_actions.remove_action(button)
+ button.screen_loc = null
+
+/// Generates visual landings for all groups that the button is not a memeber of
+/datum/hud/proc/generate_landings(atom/movable/screen/movable/action_button/button)
+ listed_actions.generate_landing()
+ palette_actions.generate_landing()
+
+/// Clears all currently visible landings
+/datum/hud/proc/hide_landings()
+ listed_actions.clear_landing()
+ palette_actions.clear_landing()
+
+// Updates any existing "owned" visuals, ensures they continue to be visible
+/datum/hud/proc/update_our_owner()
+ toggle_palette.refresh_owner()
+ palette_down.refresh_owner()
+ palette_up.refresh_owner()
+ listed_actions.update_landing()
+ palette_actions.update_landing()
+
+/// Ensures all of our buttons are properly within the bounds of our client's view, moves them if they're not
+/datum/hud/proc/view_audit_buttons()
+ var/our_view = mymob?.client?.view
+ if(!our_view)
+ return
+ listed_actions.check_against_view()
+ palette_actions.check_against_view()
+ for(var/atom/movable/screen/movable/action_button/floating_button as anything in floating_actions)
+ var/list/current_offsets = screen_loc_to_offset(floating_button.screen_loc)
+ // We set the view arg here, so the output will be properly hemm'd in by our new view
+ floating_button.screen_loc = offset_to_screen_loc(current_offsets[1], current_offsets[2], view = our_view)
+
+/// Generates and fills new action groups with our mob's current actions
+/datum/hud/proc/build_action_groups()
+ listed_actions = new(src)
+ palette_actions = new(src)
+ floating_actions = list()
+ for(var/datum/action/action as anything in mymob.actions)
+ var/atom/movable/screen/movable/action_button/button = action.viewers[src]
+ if(!button)
+ action.ShowTo(mymob)
+ else
+ position_action(button, button.location)
+
+/datum/action_group
+ /// The hud we're owned by
+ var/datum/hud/owner
+ /// The actions we're managing
+ var/list/atom/movable/screen/movable/action_button/actions
+ /// The initial vertical offset of our action buttons
+ var/north_offset = 0
+ /// The pixel vertical offset of our action buttons
+ var/pixel_north_offset = 0
+ /// Max amount of buttons we can have per row
+ /// Indexes at 1
+ var/column_max = 0
+ /// How far "ahead" of the first row we start. Lets us "scroll" our rows
+ /// Indexes at 1
+ var/row_offset = 0
+ /// How many rows of actions we can have at max before we just stop hiding
+ /// Indexes at 1
+ var/max_rows = INFINITY
+ /// The screen location we go by
+ var/location
+ /// Our landing screen object
+ var/atom/movable/screen/action_landing/landing
+
+/datum/action_group/New(datum/hud/owner)
+ ..()
+ actions = list()
+ src.owner = owner
+
+/datum/action_group/Destroy()
+ owner = null
+ QDEL_NULL(landing)
+ QDEL_LIST(actions)
+ return ..()
+
+/datum/action_group/proc/insert_action(atom/movable/screen/action, index)
+ if(action in actions)
+ if(actions[index] == action)
+ return
+ actions -= action // Don't dupe, come on
+ if(!index)
+ index = length(actions) + 1
+ index = min(length(actions) + 1, index)
+ actions.Insert(index, action)
+ refresh_actions()
+
+/datum/action_group/proc/remove_action(atom/movable/screen/action)
+ actions -= action
+ refresh_actions()
+
+/datum/action_group/proc/refresh_actions()
+
+ // We don't use size() here because landings are not canon
+ var/total_rows = ROUND_UP(length(actions) / column_max)
+ total_rows -= max_rows // Lets get the amount of rows we're off from our max
+ row_offset = clamp(row_offset, 0, total_rows) // You're not allowed to offset so far that we have a row of blank space
+
+ var/button_number = 0
+ for(var/atom/movable/screen/button as anything in actions)
+ var/postion = ButtonNumberToScreenCoords(button_number )
+ button.screen_loc = postion
+ button_number++
+
+ if(landing)
+ var/postion = ButtonNumberToScreenCoords(button_number, landing = TRUE) // Need a good way to count buttons off screen, but allow this to display in the right place if it's being placed with no concern for dropdown
+ landing.screen_loc = postion
+ button_number++
+
+/// Accepts a number represeting our position in the group, indexes at 0 to make the math nicer
+/datum/action_group/proc/ButtonNumberToScreenCoords(number, landing = FALSE)
+ var/row = round(number / column_max)
+ row -= row_offset // If you're less then 0, you don't get to render, this lets us "scroll" rows ya feel?
+ if(row < 0)
+ return null
+
+ // Could use >= here, but I think it's worth noting that the two start at different places, since row is based on number here
+ if(row > max_rows - 1)
+ if(!landing) // If you're not a landing, go away please. thx
+ return null
+ // We always want to render landings, even if their action button can't be displayed.
+ // So we set a row equal to the max amount of rows + 1. Willing to overrun that max slightly to properly display the landing spot
+ row = max_rows // Remembering that max_rows indexes at 1, and row indexes at 0
+
+ // We're going to need to set our column to match the first item in the last row, so let's set number properly now
+ number = row * column_max
+
+ var/visual_row = row + north_offset
+ var/coord_row = visual_row ? "-[visual_row]" : "+0"
+
+ var/visual_column = number % column_max
+ var/coord_col = "+[visual_column]"
+ var/coord_col_offset = 4 + 2 * (visual_column + 1)
+ return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-[pixel_north_offset]"
+
+/datum/action_group/proc/check_against_view()
+ var/owner_view = owner?.mymob?.client?.view
+ if(!owner_view)
+ return
+ // Unlikey as it is, we may have been changed. Want to start from our target position and fail down
+ column_max = initial(column_max)
+ // Convert our viewer's view var into a workable offset
+ var/list/view_size = view_to_pixels(owner_view)
+
+ // We're primarially concerned about width here, if someone makes us 1x2000 I wish them a swift and watery death
+ var/furthest_screen_loc = ButtonNumberToScreenCoords(column_max - 1)
+ var/list/offsets = screen_loc_to_offset(furthest_screen_loc, owner_view)
+ if(offsets[1] > world.icon_size && offsets[1] < view_size[1] && offsets[2] > world.icon_size && offsets[2] < view_size[2]) // We're all good
+ return
+
+ for(column_max in column_max - 1 to 1 step -1) // Yes I could do this by unwrapping ButtonNumberToScreenCoords, but I don't feel like it
+ var/tested_screen_loc = ButtonNumberToScreenCoords(column_max)
+ offsets = screen_loc_to_offset(tested_screen_loc, owner_view)
+ // We've found a valid max length, pack it in
+ if(offsets[1] > world.icon_size && offsets[1] < view_size[1] && offsets[2] > world.icon_size && offsets[2] < view_size[2])
+ break
+ // Use our newly resized column max
+ refresh_actions()
+
+/// Returns the amount of objects we're storing at the moment
+/datum/action_group/proc/size()
+ var/amount = length(actions)
+ if(landing)
+ amount += 1
+ return amount
+
+/datum/action_group/proc/index_of(atom/movable/screen/get_location)
+ return actions.Find(get_location)
+
+/// Generates a landing object that can be dropped on to join this group
+/datum/action_group/proc/generate_landing()
+ if(landing)
+ return
+ landing = new()
+ landing.set_owner(src)
+ refresh_actions()
+
+/// Clears any landing objects we may currently have
+/datum/action_group/proc/clear_landing()
+ QDEL_NULL(landing)
+
+/datum/action_group/proc/update_landing()
+ if(!landing)
+ return
+ landing.refresh_owner()
+
+/datum/action_group/proc/scroll(amount)
+ row_offset += amount
+ refresh_actions()
+
+/datum/action_group/palette
+ north_offset = 2
+ column_max = 3
+ max_rows = 3
+ location = SCRN_OBJ_IN_PALETTE
+
+/datum/action_group/palette/insert_action(atom/movable/screen/action, index)
+ . = ..()
+ var/atom/movable/screen/button_palette/palette = owner.toggle_palette
+ palette.play_item_added()
+
+/datum/action_group/palette/remove_action(atom/movable/screen/action)
+ . = ..()
+ var/atom/movable/screen/button_palette/palette = owner.toggle_palette
+ palette.play_item_removed()
+ if(!length(actions))
+ palette.set_expanded(FALSE)
+
+/datum/action_group/palette/refresh_actions()
+ var/atom/movable/screen/button_palette/palette = owner.toggle_palette
+ var/atom/movable/screen/palette_scroll/scroll_down = owner.palette_down
+ var/atom/movable/screen/palette_scroll/scroll_up = owner.palette_up
+
+ var/actions_above = round((owner.listed_actions.size() - 1) / owner.listed_actions.column_max)
+ north_offset = initial(north_offset) + actions_above
+
+ palette.screen_loc = ui_action_palette_offset(actions_above)
+ var/action_count = length(owner?.mymob?.actions)
+ var/our_row_count = round((length(actions) - 1) / column_max)
+ if(!action_count)
+ palette.screen_loc = null
+
+ if(palette.expanded && action_count && our_row_count >= max_rows)
+ scroll_down.screen_loc = ui_palette_scroll_offset(actions_above)
+ scroll_up.screen_loc = ui_palette_scroll_offset(actions_above)
+ else
+ scroll_down.screen_loc = null
+ scroll_up.screen_loc = null
+
+ return ..()
+
+/datum/action_group/palette/ButtonNumberToScreenCoords(number, landing)
+ var/atom/movable/screen/button_palette/palette = owner.toggle_palette
+ if(palette.expanded)
+ return ..()
+
+ if(!landing)
+ return null
+
+ // We only render the landing in this case, so we force it to be the second item displayed (Second rather then first since it looks nicer)
+ // Remember the number var indexes at 0
+ return ..(1 + (row_offset * column_max), landing)
+
+
+/datum/action_group/listed
+ pixel_north_offset = 6
+ column_max = 10
+ location = SCRN_OBJ_IN_LIST
+
+/datum/action_group/listed/refresh_actions()
+ . = ..()
+ owner.palette_actions.refresh_actions() // We effect them, so we gotta refresh em
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index ea233cc41b85..f340dedd5ade 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -42,11 +42,11 @@
icon_state = "Devil-6"
screen_loc = ui_devilsouldisplay
-/atom/movable/screen/devil/soul_counter/proc/update_counter(souls = 0)
+/atom/movable/screen/devil/soul_counter/proc/update_counter(souls)
invisibility = 0
- maptext = "[souls]
"
+ maptext = ANTAG_MAPTEXT(souls, COLOR_RED)
switch(souls)
- if(0,null)
+ if(0, null)
icon_state = "Devil-1"
if(1,2)
icon_state = "Devil-2"
@@ -62,12 +62,10 @@
/atom/movable/screen/devil/soul_counter/proc/clear()
invisibility = INVISIBILITY_ABSTRACT
-/atom/movable/screen/ling
- invisibility = INVISIBILITY_ABSTRACT
-
/atom/movable/screen/ling/sting
name = "current sting"
screen_loc = ui_lingstingdisplay
+ invisibility = INVISIBILITY_ABSTRACT
/atom/movable/screen/ling/sting/Click()
if(isobserver(usr))
@@ -80,31 +78,6 @@
icon_state = "power_display"
screen_loc = ui_lingchemdisplay
-/atom/movable/screen/bloodsucker
- icon = 'icons/mob/actions/actions_bloodsucker.dmi'
- invisibility = INVISIBILITY_ABSTRACT
-
-/atom/movable/screen/bloodsucker/proc/clear()
- invisibility = INVISIBILITY_ABSTRACT
-
-/atom/movable/screen/bloodsucker/proc/update_counter()
- invisibility = 0
-
-/atom/movable/screen/bloodsucker/blood_counter
- name = "Blood Consumed"
- icon_state = "blood_display"
- screen_loc = ui_blood_display
-
-/atom/movable/screen/bloodsucker/rank_counter
- name = "Bloodsucker Rank"
- icon_state = "rank"
- screen_loc = ui_vamprank_display
-
-/atom/movable/screen/bloodsucker/sunlight_counter
- name = "Solar Flare Timer"
- icon_state = "sunlight_night"
- screen_loc = ui_sunlight_display
-
/datum/hud/human/New(mob/living/carbon/human/owner)
..()
owner.overlay_fullscreen("see_through_darkness", /atom/movable/screen/fullscreen/see_through_darkness)
@@ -226,7 +199,7 @@
inv_box.icon = ui_style
inv_box.icon_state = "suit_storage"
inv_box.screen_loc = ui_sstore1
- inv_box.slot_id = SLOT_S_STORE
+ inv_box.slot_id = SLOT_SUIT_STORE
static_inventory += inv_box
using = new /atom/movable/screen/resist()
@@ -322,23 +295,9 @@
pull_icon.screen_loc = ui_above_intent
static_inventory += pull_icon
- lingchemdisplay = new /atom/movable/screen/ling/chems()
- infodisplay += lingchemdisplay
-
- lingstingdisplay = new /atom/movable/screen/ling/sting()
- infodisplay += lingstingdisplay
-
devilsouldisplay = new /atom/movable/screen/devil/soul_counter
infodisplay += devilsouldisplay
- //bloodsuckers
- blood_display = new /atom/movable/screen/bloodsucker/blood_counter
- infodisplay += blood_display
- vamprank_display = new /atom/movable/screen/bloodsucker/rank_counter
- infodisplay += vamprank_display
- sunlight_display = new /atom/movable/screen/bloodsucker/sunlight_counter
- infodisplay += sunlight_display
-
zone_select = new /atom/movable/screen/zone_sel()
zone_select.icon = ui_style
zone_select.update_icon(mymob)
diff --git a/code/_onclick/hud/monkey.dm b/code/_onclick/hud/monkey.dm
index f9a73ed66bdf..ca06be2a8013 100644
--- a/code/_onclick/hud/monkey.dm
+++ b/code/_onclick/hud/monkey.dm
@@ -90,13 +90,6 @@
pull_icon.screen_loc = ui_above_movement
static_inventory += pull_icon
- lingchemdisplay = new /atom/movable/screen/ling/chems()
- infodisplay += lingchemdisplay
-
- lingstingdisplay = new /atom/movable/screen/ling/sting()
- infodisplay += lingstingdisplay
-
-
zone_select = new /atom/movable/screen/zone_sel()
zone_select.icon = ui_style
zone_select.update_icon(mymob)
diff --git a/code/_onclick/hud/movable_screen_objects.dm b/code/_onclick/hud/movable_screen_objects.dm
index c96aef237712..6addfd540f07 100644
--- a/code/_onclick/hud/movable_screen_objects.dm
+++ b/code/_onclick/hud/movable_screen_objects.dm
@@ -9,9 +9,8 @@
//Not tied to the grid, places it's center where the cursor is
/atom/movable/screen/movable
+ mouse_drag_pointer = 'icons/effects/mouse_pointers/screen_drag.dmi'
var/snap2grid = FALSE
- var/moved = FALSE
- var/locked = FALSE
var/x_off = -16
var/y_off = -16
@@ -23,33 +22,30 @@
/atom/movable/screen/movable/MouseDrop(over_object, src_location, over_location, src_control, over_control, params)
- if(locked) //no! I am locked! begone!
+ var/position = mouse_params_to_position(params)
+ if(!position)
return
- var/list/PM = params2list(params)
- //No screen-loc information? abort.
- if(!PM || !PM["screen-loc"])
- return
+ screen_loc = position
- //Split screen-loc up into X+Pixel_X and Y+Pixel_Y
- var/list/screen_loc_params = splittext(PM["screen-loc"], ",")
+/// Takes mouse parmas as input, returns a string representing the appropriate mouse position
+/atom/movable/screen/movable/proc/mouse_params_to_position(params)
+ var/list/modifiers = params2list(params)
- //Split X+Pixel_X up into list(X, Pixel_X)
- var/list/screen_loc_X = splittext(screen_loc_params[1],":")
+ //No screen-loc information? abort.
+ if(!LAZYACCESS(modifiers, SCREEN_LOC))
+ return
- //Split Y+Pixel_Y up into list(Y, Pixel_Y)
- var/list/screen_loc_Y = splittext(screen_loc_params[2],":")
+ var/client/our_client = usr.client
+ var/list/offset = screen_loc_to_offset(LAZYACCESS(modifiers, SCREEN_LOC))
if(snap2grid) //Discard Pixel Values
- screen_loc = "[screen_loc_X[1]],[screen_loc_Y[1]]"
-
+ offset[1] = FLOOR(offset[1], world.icon_size) // drops any pixel offset
+ offset[2] = FLOOR(offset[2], world.icon_size) // drops any pixel offset
else //Normalise Pixel Values (So the object drops at the center of the mouse, not 16 pixels off)
- var/pix_X = text2num(screen_loc_X[2]) + x_off
- var/pix_Y = text2num(screen_loc_Y[2]) + y_off
- screen_loc = "[screen_loc_X[1]]:[pix_X],[screen_loc_Y[1]]:[pix_Y]"
-
- moved = screen_loc
-
+ offset[1] += x_off
+ offset[2] += y_off
+ return offset_to_screen_loc(offset[1], offset[2], our_client?.view)
//Debug procs
/client/proc/test_movable_UI()
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 63252f4aa02d..8c8b9fdaa603 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -5,10 +5,21 @@ GLOBAL_LIST_EMPTY(radial_menus)
/atom/movable/screen/radial
icon = 'icons/mob/radial.dmi'
- layer = ABOVE_HUD_LAYER
plane = ABOVE_HUD_PLANE
+ vis_flags = VIS_INHERIT_PLANE
var/datum/radial_menu/parent
+/atom/movable/screen/radial/proc/set_parent(new_value)
+ if(parent)
+ UnregisterSignal(parent, COMSIG_PARENT_QDELETING)
+ parent = new_value
+ if(parent)
+ RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(handle_parent_del))
+
+/atom/movable/screen/radial/proc/handle_parent_del()
+ SIGNAL_HANDLER
+ set_parent(null)
+
/atom/movable/screen/radial/slice
icon_state = "radial_slice"
var/choice
@@ -16,15 +27,27 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/tooltips = FALSE
var/active = FALSE
+/atom/movable/screen/radial/slice/set_parent(new_value)
+ . = ..()
+ if(parent)
+ icon_state = parent.radial_slice_icon
+
/atom/movable/screen/radial/slice/MouseEntered(location, control, params)
. = ..()
+ if(next_page || !parent)
+ icon_state = "radial_slice[active ? "_active" : ""]_focus"
+ else
+ icon_state = "[parent.radial_slice_icon]_focus"
icon_state = "radial_slice[active ? "_active" : ""]_focus"
if(tooltips)
- openToolTip(usr, src, params, title = name, content = desc)
+ openToolTip(usr, src, params, title = name)
/atom/movable/screen/radial/slice/MouseExited(location, control, params)
. = ..()
- icon_state = "radial_slice[active ? "_active" : ""]"
+ if(next_page || !parent)
+ icon_state = "radial_slice[active ? "_active" : ""]"
+ else
+ icon_state = parent.radial_slice_icon
if(tooltips)
closeToolTip(usr)
@@ -33,7 +56,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
if(next_page)
parent.next_page()
else
- parent.element_chosen(choice,usr)
+ parent.element_chosen(choice, usr, params)
/atom/movable/screen/radial/center
name = "Close Menu"
@@ -54,14 +77,17 @@ GLOBAL_LIST_EMPTY(radial_menus)
/datum/radial_menu
/// List of choice IDs
var/list/choices = list()
+
/// choice_id -> icon
var/list/choices_icons = list()
+
/// choice_id -> choice
var/list/choices_values = list()
+
/// choice_id -> /datum/radial_menu_choice
var/list/choice_datums = list()
- ///list of choices per page
- var/list/page_data = list()
+
+ var/list/page_data = list() //list of choices per page
var/selected_choice
@@ -88,6 +114,9 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/py_shift = 0
var/entry_animation = TRUE
+ ///A replacement icon state for the generic radial slice bg icon. Doesn't affect the next page nor the center buttons
+ var/radial_slice_icon
+
//If we swap to vis_contens inventory these will need a redo
/datum/radial_menu/proc/check_screen_border(mob/user)
var/atom/movable/AM = anchor
@@ -99,6 +128,8 @@ GLOBAL_LIST_EMPTY(radial_menus)
else
py_shift = 32
restrict_to_dir(NORTH) //I was going to parse screen loc here but that's more effort than it's worth.
+ else if(hudfix_method && AM.loc)
+ anchor = get_atom_on_turf(anchor)
//Sets defaults
//These assume 45 deg min_angle
@@ -117,7 +148,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
starting_angle = 180
ending_angle = 45
-/datum/radial_menu/proc/setup_menu(use_tooltips)
+/datum/radial_menu/proc/setup_menu(use_tooltips, set_page = 1)
if(ending_angle > starting_angle)
zone = ending_angle - starting_angle
else
@@ -153,7 +184,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
page_data[page] = current
pages = page
- current_page = 1
+ current_page = clamp(set_page, 1, pages)
update_screen_objects(anim = entry_animation)
/datum/radial_menu/proc/update_screen_objects(anim = FALSE)
@@ -169,9 +200,9 @@ GLOBAL_LIST_EMPTY(radial_menus)
/datum/radial_menu/proc/HideElement(atom/movable/screen/radial/slice/E)
E.cut_overlays()
+ E.vis_contents.Cut()
E.alpha = 0
E.name = "None"
- E.desc = null
E.maptext = null
E.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
E.choice = null
@@ -199,11 +230,15 @@ GLOBAL_LIST_EMPTY(radial_menus)
E.vis_contents.Cut()
if(choice_id == NEXT_PAGE_ID)
E.name = "Next Page"
- E.desc = null
E.next_page = TRUE
+ E.icon_state = "radial_slice" // Resets the bg icon state to the default for next page buttons.
E.add_overlay("radial_next")
else
- if(istext(choices_values[choice_id]))
+ //This isn't granted to exist, so use the ?. operator for conditionals that use it.
+ var/datum/radial_menu_choice/choice_datum = choice_datums[choice_id]
+ if(choice_datum?.name)
+ E.name = choice_datum.name
+ else if(istext(choices_values[choice_id]))
E.name = choices_values[choice_id]
else if(ispath(choices_values[choice_id],/atom))
var/atom/A = choices_values[choice_id]
@@ -215,25 +250,32 @@ GLOBAL_LIST_EMPTY(radial_menus)
if(choices_icons[choice_id])
E.add_overlay(choices_icons[choice_id])
- var/datum/radial_menu_choice/choice_datum = choice_datums[choice_id]
if(choice_datum && istext(choice_datum.info))
E.desc = choice_datum.info
if(choice_datum.active)
E.active = TRUE
- E.icon_state = "radial_slice_active"
+ E.icon_state = "radial_slice_active" //i really hope this doesn't cause any issues
E.choice = choice_id
E.maptext = null
E.next_page = FALSE
+ if(choices_icons[choice_id])
+ E.add_overlay(choices_icons[choice_id])
+ if (choice_datum?.info)
+ var/obj/effect/abstract/info/info_button = new(E, choice_datum.info)
+ info_button.plane = ABOVE_HUD_PLANE
+ info_button.layer = RADIAL_CONTENT_LAYER
+ E.vis_contents += info_button
/datum/radial_menu/New()
close_button = new
- close_button.parent = src
+ close_button.set_parent(src)
/datum/radial_menu/proc/Reset()
choices.Cut()
choices_icons.Cut()
choices_values.Cut()
+ choice_datums.Cut()
current_page = 1
/datum/radial_menu/proc/element_chosen(choice_id,mob/user)
@@ -242,7 +284,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
/datum/radial_menu/proc/get_next_id()
return "c_[choices.len]"
-/datum/radial_menu/proc/set_choices(list/new_choices, use_tooltips)
+/datum/radial_menu/proc/set_choices(list/new_choices, use_tooltips, set_page = 1)
if(choices.len)
Reset()
for(var/E in new_choices)
@@ -256,7 +298,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
if (istype(new_choices[E], /datum/radial_menu_choice))
choice_datums[id] = new_choices[E]
- setup_menu(use_tooltips)
+ setup_menu(use_tooltips, set_page)
/datum/radial_menu/proc/extract_image(to_extract_from)
@@ -266,7 +308,8 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/mutable_appearance/MA = new /mutable_appearance(to_extract_from)
if(MA)
- MA.layer = ABOVE_HUD_LAYER
+ MA.plane = ABOVE_HUD_PLANE
+ MA.layer = RADIAL_CONTENT_LAYER
MA.appearance_flags |= RESET_TRANSFORM
return MA
@@ -315,13 +358,16 @@ GLOBAL_LIST_EMPTY(radial_menus)
Choices should be a list where list keys are movables or text used for element names and return value
and list values are movables/icons/images used for element icons
*/
-/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE)
+/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice")
if(!user || !anchor || !length(choices))
return
if(!uniqueid)
uniqueid = "defmenu_[REF(user)]_[REF(anchor)]"
if(GLOB.radial_menus[uniqueid])
+ if(!no_repeat_close)
+ var/datum/radial_menu/menu = GLOB.radial_menus[uniqueid]
+ menu.finished = TRUE
return
var/datum/radial_menu/menu = new
@@ -331,6 +377,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
if(istype(custom_check))
menu.custom_check_callback = custom_check
menu.anchor = anchor
+ menu.radial_slice_icon = radial_slice_icon
menu.check_screen_border(user) //Do what's needed to make it look good near borders or on hud
menu.set_choices(choices, tooltips)
menu.show_to(user)
@@ -340,8 +387,8 @@ GLOBAL_LIST_EMPTY(radial_menus)
GLOB.radial_menus -= uniqueid
if(require_near && !in_range(anchor, user))
return
- if(menu.custom_check_callback)
- if(!menu.custom_check_callback.Invoke())
+ if(istype(custom_check))
+ if(!custom_check.Invoke())
return
return answer
@@ -350,6 +397,9 @@ GLOBAL_LIST_EMPTY(radial_menus)
/// Required -- what to display for this button
var/image
+ /// If provided, this will be the name the radial slice hud button. This has priority over everything else.
+ var/name
+
/// If provided, will display an info button that will put this text in your chat
var/info
@@ -359,3 +409,6 @@ GLOBAL_LIST_EMPTY(radial_menus)
/datum/radial_menu_choice/Destroy(force, ...)
. = ..()
QDEL_NULL(image)
+
+#undef NEXT_PAGE_ID
+#undef DEFAULT_CHECK_DELAY
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index 22a2495ad347..fd90a5c787d5 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -375,7 +375,7 @@
else
to_chat(C, span_warning("You don't have a suitable tank!"))
return
- C.update_action_buttons_icon()
+ C.update_mob_action_buttons()
/atom/movable/screen/mov_intent
name = "run/walk toggle"
diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm
index 20a26acf9d29..45aa05ea2801 100644
--- a/code/_onclick/other_mobs.dm
+++ b/code/_onclick/other_mobs.dm
@@ -5,6 +5,10 @@
Otherwise pretty standard.
*/
/mob/living/carbon/human/UnarmedAttack(atom/A, proximity)
+ if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
+ if(src == A)
+ check_self_for_injuries()
+ return
if(HAS_TRAIT(A, TRAIT_NOINTERACT))
to_chat(A, span_notice("You can't touch things!"))
return
@@ -87,14 +91,6 @@
return ui_interact(user)
return FALSE
-/*
-/mob/living/carbon/human/RestrainedClickOn(var/atom/A) ---carbons will handle this
- return
-*/
-
-/mob/living/carbon/RestrainedClickOn(atom/A)
- return 0
-
/mob/living/carbon/human/RangedAttack(atom/A, mouseparams)
. = ..()
if(gloves)
@@ -177,9 +173,6 @@
attack_paw(user)
return
-/mob/living/carbon/alien/RestrainedClickOn(atom/A)
- return
-
// Babby aliens
/mob/living/carbon/alien/larva/UnarmedAttack(atom/A)
A.attack_larva(src)
@@ -199,9 +192,6 @@
/atom/proc/attack_slime(mob/user)
return
-/mob/living/simple_animal/slime/RestrainedClickOn(atom/A)
- return
-
/*
Drones
@@ -212,10 +202,6 @@
/atom/proc/attack_drone(mob/living/simple_animal/drone/user)
attack_hand(user) //defaults to attack_hand. Override it when you don't want drones to do same stuff as humans.
-/mob/living/simple_animal/slime/RestrainedClickOn(atom/A)
- return
-
-
/*
True Devil
*/
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index e5ae94c0e2f2..e483e15bdc1f 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -183,7 +183,13 @@
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
-/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes.
+/datum/config_entry/flag/no_summon_guns //No
+
+/datum/config_entry/flag/no_summon_magic //Fun
+
+/datum/config_entry/flag/no_summon_events //Allowed
+
+/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes.
/datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station
config_entry_value = 55
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 5e6f1ba87a9f..f6059717fbc3 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -80,6 +80,9 @@
/datum/config_entry/flag/log_pda // log pda messages
+/// log uplink/spellbook/codex ciatrix purchases and refunds
+/datum/config_entry/flag/log_uplink
+
/datum/config_entry/flag/log_telecomms // log telecomms messages
/datum/config_entry/flag/log_ntsl // log NTSL compilation
diff --git a/code/controllers/subsystem/augury.dm b/code/controllers/subsystem/augury.dm
index 6cbebef1f908..22d5003a7ec5 100644
--- a/code/controllers/subsystem/augury.dm
+++ b/code/controllers/subsystem/augury.dm
@@ -1,43 +1,78 @@
SUBSYSTEM_DEF(augury)
name = "Augury"
- flags = SS_NO_INIT | SS_NO_FIRE
+ flags = SS_NO_INIT
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
+
+ var/list/watchers = list()
var/list/doombringers = list()
var/list/observers_given_action = list()
/datum/controller/subsystem/augury/stat_entry(msg)
- msg = "D:[length(doombringers)]"
+ msg = "W:[watchers.len]|D:[length(doombringers)]"
return ..()
/datum/controller/subsystem/augury/proc/register_doom(atom/A, severity)
doombringers[A] = severity
- if(doombringers.len == 1) // New debris show button
- for(var/i in GLOB.player_list)
- if(isobserver(i) && (!(observers_given_action[i])))
- var/datum/action/innate/augury/Action = new
- Action.Grant(i)
- observers_given_action[i] = TRUE
+ RegisterSignal(A, COMSIG_PARENT_QDELETING, PROC_REF(unregister_doom))
/datum/controller/subsystem/augury/proc/unregister_doom(atom/A)
+ SIGNAL_HANDLER
+ UnregisterSignal(A, COMSIG_PARENT_QDELETING)
doombringers -= A
- if(!doombringers.len)
+
+/datum/controller/subsystem/augury/fire()
+ var/biggest_doom = null
+ var/biggest_threat = null
+
+ for(var/db in doombringers)
+ var/datum/d = db
+ if(!d || QDELETED(d))
+ doombringers -= d
+ continue
+ var/threat = doombringers[d]
+ if((biggest_threat == null) || (biggest_threat < threat))
+ biggest_doom = d
+ biggest_threat = threat
+
+ if(doombringers.len)
+ for(var/i in GLOB.player_list)
+ if(isobserver(i) && (!(observers_given_action[i])))
+ var/datum/action/innate/augury/A = new
+ A.Grant(i)
+ observers_given_action[i] = TRUE
+ else
for(var/i in observers_given_action)
if(observers_given_action[i] && isobserver(i))
var/mob/dead/observer/O = i
- for(var/datum/action/innate/augury/Action in O.actions)
- qdel(Action)
+ for(var/datum/action/innate/augury/A in O.actions)
+ qdel(A)
observers_given_action -= i
+ for(var/w in watchers)
+ if(!w)
+ watchers -= w
+ continue
+ var/mob/dead/observer/O = w
+ if(biggest_doom && (!O.orbiting || O.orbiting.parent != biggest_doom))
+ O.ManualFollow(biggest_doom)
+
/datum/action/innate/augury
- name = "Follow Debris"
- icon_icon = 'icons/obj/meteor.dmi'
+ name = "Auto Follow Debris"
+ button_icon = 'icons/obj/meteor.dmi'
button_icon_state = "flaming"
- background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND
-/datum/action/innate/augury/Trigger()
- var/tofollow = pick(SSaugury.doombringers)
- if(tofollow && isobserver(owner)) // nullcheck
- var/mob/dead/observer/O = owner
- O.ManualFollow(tofollow)
+/datum/action/innate/augury/Destroy()
+ if(owner)
+ SSaugury.watchers -= owner
+ return ..()
+
+/datum/action/innate/augury/Activate()
+ SSaugury.watchers += owner
+ to_chat(owner, span_notice("You are now auto-following debris."))
+ active = TRUE
+/datum/action/innate/augury/Deactivate()
+ SSaugury.watchers -= owner
+ to_chat(owner, span_notice("You are no longer auto-following debris."))
+ active = FALSE
diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm
index 155d533bbd88..c8df4f3c8bf9 100644
--- a/code/controllers/subsystem/explosions.dm
+++ b/code/controllers/subsystem/explosions.dm
@@ -343,7 +343,7 @@ SUBSYSTEM_DEF(explosions)
for(var/I in T)
var/atom/A = I
if (length(A.contents) && !(A.flags_1 & PREVENT_CONTENTS_EXPLOSION_1)) //The atom/contents_explosion() proc returns null if the contents ex_acting has been handled by the atom, and TRUE if it hasn't.
- items += A.GetAllContents()
+ items += A.get_all_contents()
if(istype(A, /mob/living))
items -= A //So we don't do double damage to mobs for balance raisins
for(var/thing in items)
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index 6ea489cda13d..099fd2d619d7 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -595,7 +595,7 @@ SUBSYSTEM_DEF(job)
job.give_donor_stuff(living_mob, M) // yogs - Donor Features
job.give_cape(living_mob, M)
job.give_map_flare(living_mob, M)
- var/obj/item/modular_computer/RPDA = locate(/obj/item/modular_computer/tablet) in living_mob.GetAllContents()
+ var/obj/item/modular_computer/RPDA = locate(/obj/item/modular_computer/tablet) in living_mob.get_all_contents()
if(istype(RPDA))
RPDA.device_theme = GLOB.pda_themes[M.client?.prefs.read_preference(/datum/preference/choiced/pda_theme)]
var/obj/item/computer_hardware/hard_drive/hard_drive = RPDA.all_components[MC_HDD]
diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm
index 9967e49b5be6..21c5c7d8e4dc 100644
--- a/code/controllers/subsystem/pai.dm
+++ b/code/controllers/subsystem/pai.dm
@@ -6,12 +6,12 @@ SUBSYSTEM_DEF(pai)
var/list/candidates = list()
var/ghost_spam = FALSE
var/spam_delay = 100
- var/list/pai_card_list = list()
+ var/list/paicard_list = list()
/datum/controller/subsystem/pai/Topic(href, href_list[])
if(href_list["download"])
var/datum/paiCandidate/candidate = locate(href_list["candidate"]) in candidates
- var/obj/item/paicard/card = locate(href_list["device"]) in pai_card_list
+ var/obj/item/paicard/card = locate(href_list["device"]) in paicard_list
if(card.pai)
return
if(istype(card, /obj/item/paicard) && istype(candidate, /datum/paiCandidate))
@@ -27,8 +27,6 @@ SUBSYSTEM_DEF(pai)
card.setPersonality(pai)
- SSticker.mode.update_cult_icons_removed(card.pai.mind)
-
candidates -= candidate
usr << browse(null, "window=findPai")
@@ -71,7 +69,7 @@ SUBSYSTEM_DEF(pai)
if("submit")
if(candidate)
candidate.ready = 1
- for(var/obj/item/paicard/p in pai_card_list)
+ for(var/obj/item/paicard/p in paicard_list)
if(!p.pai)
p.alertUpdate()
usr << browse(null, "window=paiRecruit")
diff --git a/code/controllers/subsystem/processing/antag_hud.dm b/code/controllers/subsystem/processing/antag_hud.dm
new file mode 100644
index 000000000000..aaf10a5e2ecb
--- /dev/null
+++ b/code/controllers/subsystem/processing/antag_hud.dm
@@ -0,0 +1,4 @@
+PROCESSING_SUBSYSTEM_DEF(antag_hud)
+ name = "Antag HUDs"
+ flags = SS_NO_INIT
+ wait = 2 SECONDS
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index a491f673f053..9c849e8704ee 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -68,9 +68,22 @@ SUBSYSTEM_DEF(statpanels)
set_SDQL2_tab(target)
if(target.mob)
var/mob/target_mob = target.mob
- if((target.stat_tab in target.spell_tabs) || !length(target.spell_tabs) && (length(target_mob.mob_spell_list) || length(target_mob.mind?.spell_list)))
- if(num_fires % default_wait == 0)
- set_spells_tab(target, target_mob)
+
+ // Handle the action panels of the stat panel
+ var/update_actions = FALSE
+ // We're on a spell tab, update the tab so we can see cooldowns progressing and such
+ if(target.stat_tab in target.spell_tabs)
+ update_actions = TRUE
+ // We're not on a spell tab per se, but we have cooldown actions, and we've yet to
+ // set up our spell tabs at all
+ if(!length(target.spell_tabs) && locate(/datum/action/cooldown) in target_mob.actions)
+ update_actions = TRUE
+
+ if(update_actions && num_fires % default_wait == 0)
+ set_action_tabs(target, target_mob)
+
+ // Handle the examined turf of the stat panel
+
if(target_mob?.listed_turf && num_fires % default_wait == 0)
if(!target_mob.TurfAdjacent(target_mob.listed_turf))
target << output("", "statbrowser:remove_listedturf")
@@ -105,18 +118,15 @@ SUBSYSTEM_DEF(statpanels)
sdql2A += sdql2B
target << output(url_encode(json_encode(sdql2A)), "statbrowser:update_sdql2")
-/datum/controller/subsystem/statpanels/proc/set_spells_tab(client/target, mob/target_mob)
- var/list/proc_holders = target_mob.get_proc_holders()
+/// Set up the various action tabs.
+/datum/controller/subsystem/statpanels/proc/set_action_tabs(client/target, mob/target_mob)
+ var/list/actions = target_mob.get_actions_for_statpanel()
target.spell_tabs.Cut()
- for(var/proc_holder_list as anything in proc_holders)
- target.spell_tabs |= proc_holder_list[1]
-
- var/proc_holders_encoded = ""
- if(length(proc_holders))
- proc_holders_encoded = url_encode(json_encode(proc_holders))
+ for(var/action_data in actions)
+ target.spell_tabs |= action_data[1]
- target << output("[url_encode(json_encode(target.spell_tabs))];[proc_holders_encoded]", "statbrowser:update_spells")
+ target << output("[url_encode(json_encode(target.spell_tabs))];[actions]", "statbrowser:update_spells")
/datum/controller/subsystem/statpanels/proc/set_turf_examine_tab(client/target, mob/target_mob)
var/list/overrides = list()
@@ -183,10 +193,22 @@ SUBSYSTEM_DEF(statpanels)
return TRUE
var/mob/target_mob = target.mob
- if((target.stat_tab in target.spell_tabs) || !length(target.spell_tabs) && (length(target_mob.mob_spell_list) || length(target_mob.mind?.spell_list)))
- set_spells_tab(target, target_mob)
+
+ // Handle actions
+
+ var/update_actions = FALSE
+ if(target.stat_tab in target.spell_tabs)
+ update_actions = TRUE
+
+ if(!length(target.spell_tabs) && locate(/datum/action/cooldown) in target_mob.actions)
+ update_actions = TRUE
+
+ if(update_actions)
+ set_action_tabs(target, target_mob)
return TRUE
+ // Handle turfs
+
if(target_mob?.listed_turf)
if(!target_mob.TurfAdjacent(target_mob.listed_turf))
target << output("", "statbrowser:remove_listedturf")
@@ -209,6 +231,9 @@ SUBSYSTEM_DEF(statpanels)
else if(length(GLOB.sdql2_queries) && target.stat_tab == "SDQL2")
set_SDQL2_tab(target)
+/// Stat panel window declaration
+/client/var/datum/tgui_window/stat_panel
+
/atom/proc/remove_from_cache()
SSstatpanels.cached_images -= REF(src)
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index 44f005f3ade1..50090137f5e7 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -336,6 +336,7 @@ SUBSYSTEM_DEF(vote)
/datum/action/vote
name = "Vote!"
button_icon_state = "vote"
+ show_to_observers = FALSE
/datum/action/vote/Trigger()
if(owner)
@@ -343,7 +344,7 @@ SUBSYSTEM_DEF(vote)
remove_from_client()
Remove(owner)
-/datum/action/vote/IsAvailable()
+/datum/action/vote/IsAvailable(feedback = FALSE)
return TRUE
/datum/action/vote/proc/remove_from_client()
diff --git a/code/datums/action.dm b/code/datums/action.dm
deleted file mode 100644
index 8f0e4135534e..000000000000
--- a/code/datums/action.dm
+++ /dev/null
@@ -1,883 +0,0 @@
-#define AB_CHECK_RESTRAINED 1
-#define AB_CHECK_STUN 2
-#define AB_CHECK_LYING 4
-#define AB_CHECK_CONSCIOUS 8
-
-/datum/action
- var/name = "Generic Action"
- var/desc = null
- var/obj/target = null
- var/check_flags = NONE
- var/processing = FALSE
- var/atom/movable/screen/movable/action_button/button = null
- var/buttontooltipstyle = ""
- var/transparent_when_unavailable = TRUE
-
- var/button_icon = 'icons/mob/actions/backgrounds.dmi' //This is the file for the BACKGROUND icon
- var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND //And this is the state for the background icon
-
- var/icon_icon = 'icons/mob/actions.dmi' //This is the file for the ACTION icon
- var/button_icon_state = "default" //And this is the state for the action icon
- var/mob/owner
- var/syndicate = FALSE // are these buttons only for syndicates?
- var/atom/movable/screen/cooldown_overlay/cooldown_overlay
-
-/datum/action/New(Target)
- link_to(Target)
- button = new
- button.linked_action = src
- button.name = name
- button.actiontooltipstyle = buttontooltipstyle
- if(desc)
- button.desc = desc
-
-/datum/action/proc/link_to(Target)
- target = Target
-
-/datum/action/Destroy()
- if(owner)
- Remove(owner)
- target = null
- qdel(button)
- button = null
- return ..()
-
-/datum/action/proc/Grant(mob/M)
- if(M)
- M.originalactions += src
- if(owner)
- if(owner == M)
- return
- Remove(owner)
- owner = M
- if(syndicate)
- if(!is_syndicate(M) && !is_clockcult(M)) // if a syndicate check is failed; don't generate button on the hud at all unless you are a clock cultist as well. - Hopek
- return
-
- //button id generation
- var/counter = 0
- var/bitfield = 0
- for(var/datum/action/A in M.actions)
- if(A.name == name && A.button.id)
- counter += 1
- bitfield |= A.button.id
- bitfield = ~bitfield
- var/bitflag = 1
- for(var/i in 1 to (counter + 1))
- if(bitfield & bitflag)
- button.id = bitflag
- break
- bitflag *= 2
-
- M.actions += src
- if(M.client)
- M.client.screen += button
- button.locked = M.client.prefs.read_preference(/datum/preference/toggle/buttons_locked) || button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE //even if it's not defaultly locked we should remember we locked it before
- button.moved = button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE
- for(var/mob/dead/observer/O in M.observers)
- O?.client.screen += button
- M.update_action_buttons() // Now push the owners buttons back
- else
- Remove(owner)
-
-/datum/action/proc/Remove(mob/M)
- if(M)
- if(M.client)
- M.client.screen -= button
- for(var/mob/dead/observer/O in M.observers)
- O?.client.screen -= button
- M.originalactions -= src
- M.actions -= src
- M.update_action_buttons()
- owner = null
- if(button)
- button.moved = FALSE //so the button appears in its normal position when given to another owner.
- button.locked = FALSE
- button.id = null
-
-/datum/action/proc/Trigger()
- if(!IsAvailable())
- return FALSE
- if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER)
- return FALSE
- return TRUE
-
-/datum/action/proc/Process()
- return
-
-/datum/action/proc/IsAvailable()
- if(!owner)
- return FALSE
- if(check_flags & AB_CHECK_RESTRAINED)
- if(owner.restrained())
- return FALSE
- if(check_flags & AB_CHECK_STUN)
- if(isliving(owner))
- var/mob/living/L = owner
- if(L.IsParalyzed() || L.IsStun())
- return FALSE
- if(check_flags & AB_CHECK_LYING)
- if(isliving(owner))
- var/mob/living/L = owner
- if(!(L.mobility_flags & MOBILITY_STAND))
- return FALSE
- if(check_flags & AB_CHECK_CONSCIOUS)
- if(owner.stat)
- return FALSE
- return TRUE
-
-/datum/action/proc/UpdateButtonIcon(status_only = FALSE, force = FALSE)
- if(button)
- if(!status_only)
- button.name = name
- button.desc = desc
- if(owner && owner.hud_used && background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND)
- var/list/settings = owner.hud_used.get_action_buttons_icons()
- if(button.icon != settings["bg_icon"])
- button.icon = settings["bg_icon"]
- if(button.icon_state != settings["bg_state"])
- button.icon_state = settings["bg_state"]
- else
- if(button.icon != button_icon)
- button.icon = button_icon
- if(button.icon_state != background_icon_state)
- button.icon_state = background_icon_state
-
- ApplyIcon(button, force)
-
- if(!IsAvailable())
- button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
- else
- button.color = rgb(255,255,255,255)
- return 1
-
-/datum/action/proc/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force = FALSE)
- if(icon_icon && button_icon_state && ((current_button.button_icon_state != button_icon_state) || force))
- current_button.cut_overlays(TRUE)
- current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state))
- current_button.button_icon_state = button_icon_state
-
-
-//Presets for item actions
-/datum/action/item_action
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_LYING|AB_CHECK_CONSCIOUS
- button_icon_state = null
- // If you want to override the normal icon being the item
- // then change this to an icon state
-
-/datum/action/item_action/New(Target)
- ..()
- var/obj/item/I = target
- LAZYINITLIST(I.actions)
- I.actions += src
-
-/datum/action/item_action/Destroy()
- var/obj/item/I = target
- I.actions -= src
- UNSETEMPTY(I.actions)
- return ..()
-
-/datum/action/item_action/Trigger()
- if(!..())
- return 0
- if(target)
- var/obj/item/I = target
- I.ui_action_click(owner, src)
- return 1
-
-/datum/action/item_action/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force)
- if(button_icon && button_icon_state)
- // If set, use the custom icon that we set instead
- // of the item appearence
- ..()
- else if((target && current_button.appearance_cache != target.appearance) || force) //replace with /ref comparison if this is not valid.
- var/obj/item/I = target
- var/old_layer = I.layer
- var/old_plane = I.plane
- I.layer = FLOAT_LAYER //AAAH
- I.plane = FLOAT_PLANE //^ what that guy said
- current_button.cut_overlays()
- current_button.add_overlay(I)
- I.layer = old_layer
- I.plane = old_plane
- current_button.appearance_cache = I.appearance
-
-/datum/action/item_action/toggle_light
- name = "Toggle Light"
-
-/datum/action/item_action/toggle_laser_sight
- name = "Toggle Laser Sight"
- icon_icon = 'icons/obj/guns/attachment.dmi'
- button_icon_state = "laser_sight"
- var/obj/item/attachment/laser_sight/att
-
-/datum/action/item_action/toggle_laser_sight/Trigger()
- if(!att)
- if(istype(target, /obj/item/gun))
- var/obj/item/gun/parent_gun = target
- for(var/obj/item/attachment/A in parent_gun.current_attachments)
- if(istype(A, /obj/item/attachment/laser_sight))
- att = A
- break
- att?.toggle_on()
- UpdateButtonIcon()
-
-/datum/action/item_action/toggle_laser_sight/UpdateButtonIcon(status_only = FALSE, force)
- button_icon_state = "laser_sight[att?.is_on ? "_on" : ""]"
- ..()
-
-/datum/action/item_action/change_laser_sight_color
- name = "Change Laser Sight Color"
- icon_icon = 'icons/obj/guns/attachment.dmi'
- button_icon_state = "laser_sight"
- var/obj/item/attachment/laser_sight/att
-
-/datum/action/item_action/change_laser_sight_color/Trigger()
- if(!att)
- if(istype(target, /obj/item/gun))
- var/obj/item/gun/H = target
- for(var/obj/item/attachment/A in H.current_attachments)
- if(istype(A, /obj/item/attachment/laser_sight))
- att = A
- break
- if(att && owner)
- var/C = input(owner, "Select Laser Color", "Select laser color", att.laser_color) as null|color
- if(!C || QDELETED(att))
- return
- att.laser_color = C
- UpdateButtonIcon()
-
-/datum/action/item_action/change_laser_sight_color/UpdateButtonIcon(status_only = FALSE, force)
- button_icon_state = "laser_sight[att?.is_on ? "_on" : ""]"
- ..()
-
-/datum/action/item_action/toggle_infrared_sight
- name = "Toggle Infrared"
- icon_icon = 'icons/obj/guns/attachment.dmi'
- button_icon_state = "ifr_sight"
- var/obj/item/attachment/scope/infrared/att
-
-/datum/action/item_action/toggle_infrared_sight/Trigger()
- if(!att)
- if(istype(target, /obj/item/gun))
- var/obj/item/gun/parent_gun = target
- for(var/obj/item/attachment/A in parent_gun.current_attachments)
- if(istype(A, /obj/item/attachment/scope/infrared))
- att = A
- break
- att?.toggle_on()
- UpdateButtonIcon()
-
-/datum/action/item_action/toggle_infrared_sight/UpdateButtonIcon(status_only = FALSE, force)
- button_icon_state = "ifr_sight[att?.is_on ? "_on" : ""]"
- ..()
-
-/datum/action/item_action/toggle_hood
- name = "Toggle Hood"
-
-/datum/action/item_action/toggle_firemode
- name = "Toggle Firemode"
-
-/datum/action/item_action/toggle_bodycam
- name = "Toggle Bodycamera"
-
-/datum/action/item_action/rcl_col
- name = "Change Cable Color"
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "rcl_rainbow"
-
-/datum/action/item_action/rcl_gui
- name = "Toggle Fast Wiring Gui"
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "rcl_gui"
-
-/datum/action/item_action/startchainsaw
- name = "Pull The Starting Cord"
-
-/datum/action/item_action/charge_hammer
- name = "Charge the Blast Pads"
-
-/datum/action/item_action/charge_hammer/Trigger()
- var/obj/item/twohanded/vxtvulhammer/V = target
- if(istype(V))
- V.charge_hammer(owner)
-
-/datum/action/item_action/toggle_gunlight
- name = "Toggle Gunlight"
-
-/datum/action/item_action/toggle_mode
- name = "Toggle Mode"
-
-/datum/action/item_action/toggle_barrier_spread
- name = "Toggle Barrier Spread"
-
-/datum/action/item_action/equip_unequip_TED_Gun
- name = "Equip/Unequip TED Gun"
-
-/datum/action/item_action/toggle_paddles
- name = "Toggle Paddles"
-
-/datum/action/item_action/set_internals
- name = "Set Internals"
- check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS
-
-/datum/action/item_action/set_internals/UpdateButtonIcon(status_only = FALSE, force)
- if(..()) //button available
- if(iscarbon(owner))
- var/mob/living/carbon/C = owner
- if(target == C.internal)
- button.icon_state = "template_active"
-
-/datum/action/item_action/pick_color
- name = "Choose A Color"
-
-/datum/action/item_action/toggle_mister
- name = "Toggle Mister"
-
-/datum/action/item_action/activate_injector
- name = "Activate Injector"
-
-/datum/action/item_action/toggle_helmet_light
- name = "Toggle Helmet Light"
-
-/datum/action/item_action/toggle_welding_screen
- name = "Toggle Welding Screen"
-
-/datum/action/item_action/toggle_welding_screen/Trigger()
- var/obj/item/clothing/head/hardhat/weldhat/H = target
- if(istype(H))
- H.toggle_welding_screen(owner)
-
-/datum/action/item_action/toggle_headphones
- name = "Toggle Headphones"
- desc = "UNTZ UNTZ UNTZ"
-
-/datum/action/item_action/toggle_headphones/Trigger()
- var/obj/item/clothing/ears/headphones/H = target
- if(istype(H))
- H.toggle(owner)
-
-/datum/action/item_action/toggle_unfriendly_fire
- name = "Toggle Friendly Fire \[ON\]"
- desc = "Toggles if the club's blasts cause friendly fire."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "vortex_ff_on"
-
-/datum/action/item_action/toggle_unfriendly_fire/Trigger()
- if(..())
- UpdateButtonIcon()
-
-/datum/action/item_action/toggle_unfriendly_fire/UpdateButtonIcon(status_only = FALSE, force)
- if(istype(target, /obj/item/hierophant_club))
- var/obj/item/hierophant_club/H = target
- if(H.friendly_fire_check)
- button_icon_state = "vortex_ff_off"
- name = "Toggle Friendly Fire \[OFF\]"
- else
- button_icon_state = "vortex_ff_on"
- name = "Toggle Friendly Fire \[ON\]"
- ..()
-
-/datum/action/item_action/vortex_recall
- name = "Vortex Recall"
- desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time.
If the beacon is still attached, will detach it."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "vortex_recall"
-
-/datum/action/item_action/vortex_recall/IsAvailable()
- if(istype(target, /obj/item/hierophant_club))
- var/obj/item/hierophant_club/H = target
- if(H.teleporting)
- return 0
- return ..()
-
-/datum/action/item_action/clock
- icon_icon = 'icons/mob/actions/actions_clockcult.dmi'
- background_icon_state = "bg_clock"
- buttontooltipstyle = "clockcult"
-
-/datum/action/item_action/clock/IsAvailable()
- if(!is_servant_of_ratvar(owner))
- return 0
- return ..()
-
-/datum/action/item_action/clock/toggle_visor
- name = "Create Judicial Marker"
- desc = "Allows you to create a stunning Judicial Marker at any location in view. Click again to disable."
-
-/datum/action/item_action/clock/toggle_visor/IsAvailable()
- if(!is_servant_of_ratvar(owner))
- return 0
- if(istype(target, /obj/item/clothing/glasses/judicial_visor))
- var/obj/item/clothing/glasses/judicial_visor/V = target
- if(V.recharging)
- return 0
- return ..()
-
-/datum/action/item_action/clock/hierophant
- name = "Hierophant Network"
- desc = "Lets you discreetly talk with all other servants. Nearby listeners can hear you whispering, so make sure to do this privately."
- button_icon_state = "hierophant_slab"
-
-/datum/action/item_action/clock/quickbind
- name = "Quickbind"
- desc = "If you're seeing this, file a bug report."
- var/scripture_index = 0 //the index of the scripture we're associated with
-
-/datum/action/item_action/toggle_helmet_flashlight
- name = "Toggle Helmet Flashlight"
- check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS
-
-/datum/action/item_action/toggle_helmet_mode
- name = "Toggle Helmet Mode"
- check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS
-
-/datum/action/item_action/toggle
-
-/datum/action/item_action/toggle/New(Target)
- ..()
- name = "Toggle [target.name]"
- button.name = name
-
-/datum/action/item_action/halt
- name = "HALT!"
-
-/datum/action/item_action/toggle_voice_box
- name = "Toggle Voice Box"
-
-/datum/action/item_action/change
- name = "Change"
-
-/datum/action/item_action/hatsky_voiceline
- name = "Press Voice Button"
- desc = "Engage the voice box on your Hatsky to hear a classic line from the real Officer Beepsky!"
-
-/datum/action/item_action/nano_picket_sign
- name = "Retext Nano Picket Sign"
- var/obj/item/picket_sign/S
-
-/datum/action/item_action/nano_picket_sign/New(Target)
- ..()
- if(istype(Target, /obj/item/picket_sign))
- S = Target
-
-/datum/action/item_action/nano_picket_sign/Trigger()
- if(istype(S))
- S.retext(owner)
-
-/datum/action/item_action/adjust
-
-/datum/action/item_action/adjust/New(Target)
- ..()
- name = "Adjust [target.name]"
- button.name = name
-
-/datum/action/item_action/switch_hud
- name = "Switch HUD"
-
-/datum/action/item_action/toggle_wings
- name = "Toggle Wings"
-
-/datum/action/item_action/toggle_human_head
- name = "Toggle Human Head"
-
-/datum/action/item_action/toggle_helmet
- name = "Toggle Helmet"
-
-/datum/action/item_action/toggle_jetpack
- name = "Toggle Jetpack"
-
-/datum/action/item_action/jetpack_stabilization
- name = "Toggle Jetpack Stabilization"
-
-/datum/action/item_action/jetpack_stabilization/IsAvailable()
- var/obj/item/tank/jetpack/J = target
- if(!istype(J) || !J.on)
- return 0
- return ..()
-
-/datum/action/item_action/hands_free
- check_flags = AB_CHECK_CONSCIOUS
-
-/datum/action/item_action/hands_free/activate
- name = "Activate"
-
-/datum/action/item_action/hands_free/shift_nerves
- name = "Shift Nerves"
-
-/datum/action/item_action/explosive_implant
- check_flags = NONE
- name = "Activate Explosive Implant"
-
-/datum/action/item_action/toggle_research_scanner
- name = "Toggle Research Scanner"
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "scan_mode"
- var/active = FALSE
-
-/datum/action/item_action/toggle_research_scanner/Trigger()
- if(IsAvailable())
- active = !active
- if(active)
- owner.research_scanner++
- else
- owner.research_scanner--
- to_chat(owner, span_notice("[target] research scanner has been [active ? "activated" : "deactivated"]."))
- return 1
-
-/datum/action/item_action/toggle_research_scanner/Remove(mob/M)
- if(owner && active)
- owner.research_scanner--
- active = FALSE
- ..()
-
-/datum/action/item_action/instrument
- name = "Use Instrument"
- desc = "Use the instrument specified"
-
-/datum/action/item_action/instrument/Trigger()
- if(istype(target, /obj/item/instrument))
- var/obj/item/instrument/I = target
- I.interact(usr)
- return
- return ..()
-
-/datum/action/item_action/organ_action
- check_flags = AB_CHECK_CONSCIOUS
-
-/datum/action/item_action/organ_action/IsAvailable()
- var/obj/item/organ/I = target
- if(!I.owner)
- return 0
- return ..()
-
-/datum/action/item_action/organ_action/toggle/New(Target)
- ..()
- name = "Toggle [target.name]"
- button.name = name
-
-/datum/action/item_action/organ_action/use/New(Target)
- ..()
- name = "Use [target.name]"
- button.name = name
-
-/datum/action/item_action/cult_dagger
- name = "Draw Blood Rune"
- desc = "Use the ritual dagger to create a powerful blood rune"
- icon_icon = 'icons/mob/actions/actions_cult.dmi'
- button_icon_state = "draw"
- buttontooltipstyle = "cult"
- background_icon_state = "bg_demon"
-
-/datum/action/item_action/cult_dagger/Grant(mob/M)
- if(iscultist(M))
- ..()
- button.screen_loc = "6:157,4:-2"
- button.moved = "6:157,4:-2"
- else
- Remove(owner)
-
-/datum/action/item_action/cult_dagger/Trigger()
- for(var/obj/item/H in owner.held_items) //In case we were already holding another dagger
- if(istype(H, /obj/item/melee/cultblade/dagger))
- H.attack_self(owner)
- return
- var/obj/item/I = target
- if(owner.can_equip(I, SLOT_HANDS))
- owner.temporarilyRemoveItemFromInventory(I)
- owner.put_in_hands(I)
- I.attack_self(owner)
- else
- if (owner.get_num_arms() <= 0)
- to_chat(owner, span_warning("You don't have any usable hands!"))
- else
- to_chat(owner, span_warning("Your hands are full!"))
-
-/datum/action/item_action/agent_box
- name = "Deploy Box"
- desc = "Find inner peace, here, in the box."
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS
- background_icon_state = "bg_agent"
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "deploy_box"
- var/cooldown = 0
- var/obj/structure/closet/cardboard/agent/box
-
-/datum/action/item_action/agent_box/Trigger()
- if(!..())
- return FALSE
- if(QDELETED(box))
- if(cooldown < world.time - 100)
- box = new(owner.drop_location())
- owner.forceMove(box)
- cooldown = world.time
- owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
- else
- owner.forceMove(box.drop_location())
- owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
- QDEL_NULL(box)
-
-/datum/action/item_action/visegrip
- name = "Vise Grip"
- desc = "Remotely detonate marked targets. People become rooted for 1 second. Animals become rooted for 6 seconds and take hefty damage."
- icon_icon = 'icons/effects/effects.dmi'
- button_icon_state = "leghold"
-
-/datum/action/item_action/reach
- name = "Reach"
- desc = "Mark those standing on blood for 10 seconds."
- icon_icon = 'icons/effects/effects.dmi'
- button_icon_state = "rshield"
-
-/datum/action/item_action/band
- name = "Band"
- desc = "Summon all your thralls to your location."
- icon_icon = 'icons/mob/actions/actions_cult.dmi'
- button_icon_state = "horde"
-
-/datum/action/item_action/gambit
- name = "Gambit"
- desc = "Throw out your cane. If the target is weak enough to finish off, teleport to them and do it, recovering your cane in the process."
- icon_icon = 'icons/mob/actions/actions_cult.dmi'
- button_icon_state = "horde"
-
-//Preset for spells
-/datum/action/spell_action
- check_flags = NONE
- background_icon_state = "bg_spell"
-
-/datum/action/spell_action/New(Target)
- ..()
- var/obj/effect/proc_holder/S = target
- S.action = src
- name = S.name
- desc = S.desc
- icon_icon = S.action_icon
- button_icon_state = S.action_icon_state
- background_icon_state = S.action_background_icon_state
- button.name = name
-
-/datum/action/spell_action/Destroy()
- var/obj/effect/proc_holder/S = target
- S.action = null
- return ..()
-
-/datum/action/spell_action/Trigger()
- if(!..())
- return FALSE
- if(target)
- var/obj/effect/proc_holder/S = target
- S.Click()
- return TRUE
-
-/datum/action/spell_action/IsAvailable()
- if(!target)
- return FALSE
- return TRUE
-
-/datum/action/spell_action/spell
-
-/datum/action/spell_action/spell/IsAvailable()
- if(!target)
- return FALSE
- var/obj/effect/proc_holder/spell/S = target
- if(owner)
- return S.can_cast(owner)
- return FALSE
-
-/datum/action/spell_action/alien
-
-/datum/action/spell_action/alien/IsAvailable()
- if(!target)
- return FALSE
- var/obj/effect/proc_holder/alien/ab = target
- if(owner)
- return ab.cost_check(ab.check_turf,owner,1)
- return FALSE
-
-
-
-//Preset for general and toggled actions
-/datum/action/innate
- check_flags = NONE
- var/active = 0
-
-/datum/action/innate/Trigger()
- if(!..())
- return 0
- if(!active)
- Activate()
- else
- Deactivate()
- return 1
-
-/datum/action/innate/proc/Activate()
- return
-
-/datum/action/innate/proc/Deactivate()
- return
-
-//Preset for an action with a cooldown
-
-/datum/action/cooldown
- check_flags = NONE
- transparent_when_unavailable = FALSE
- var/cooldown_time = 0
- var/next_use_time = 0
-
-/datum/action/cooldown/New()
- ..()
- button.maptext = ""
- button.maptext_x = 8
- button.maptext_y = 0
- button.maptext_width = 24
- button.maptext_height = 12
-
-/datum/action/cooldown/IsAvailable()
- if(next_use_time > world.time)
- return FALSE
- return ..()
-
-/datum/action/cooldown/proc/StartCooldown()
- next_use_time = world.time + cooldown_time
- button.maptext = "[round(cooldown_time/10, 0.1)]"
- UpdateButtonIcon()
- START_PROCESSING(SSfastprocess, src)
-
-/datum/action/cooldown/process()
- if(!owner)
- button.maptext = ""
- STOP_PROCESSING(SSfastprocess, src)
- var/timeleft = max(next_use_time - world.time, 0)
- if(timeleft == 0)
- button.maptext = ""
- UpdateButtonIcon()
- STOP_PROCESSING(SSfastprocess, src)
- else
- button.maptext = "[round(timeleft/10, 0.1)]"
-
-/datum/action/cooldown/Grant(mob/M)
- ..()
- if(owner)
- UpdateButtonIcon()
- if(next_use_time > world.time)
- START_PROCESSING(SSfastprocess, src)
-
-
-//Stickmemes
-/datum/action/item_action/stickmen
- name = "Summon Stick Minions"
- desc = "Allows you to summon faithful stickmen allies to aide you in battle."
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
- button_icon_state = "art_summon"
-
-
-/datum/action/item_action/dash
- name = "Dash"
- desc = "Momentarily maximizes the jets of the shoes, allowing the user to dash a short distance."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "thrust"
-
-/datum/action/language_menu
- name = "Language Menu"
- desc = "Open the language menu to review your languages, their keys, and select your default language."
- button_icon_state = "language_menu"
- check_flags = NONE
-
-/datum/action/language_menu/Trigger()
- if(!..())
- return FALSE
- if(ismob(owner))
- var/mob/M = owner
- var/datum/language_holder/H = M.get_language_holder()
- H.open_language_menu(usr)
-
-/datum/action/item_action/wheelys
- name = "Toggle Wheely-Heel's Wheels"
- desc = "Pops out or in your wheely-heel's wheels."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "wheelys"
-
-/datum/action/item_action/airshoes
- name = "Toggle thrust on air shoes."
- desc = "Switch between walking and hovering."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "airshoes_a"
-
-
-/datum/action/item_action/kindleKicks
- name = "Activate Kindle Kicks"
- desc = "Kick you feet together, activating the lights in your Kindle Kicks."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "kindleKicks"
-
-//Small sprites
-/datum/action/small_sprite
- name = "Toggle Giant Sprite"
- desc = "Others will always see you as giant"
- icon_icon = 'icons/mob/actions/actions_xeno.dmi'
- button_icon_state = "smallqueen"
- background_icon_state = "bg_alien"
- var/small = FALSE
- var/small_icon
- var/small_icon_state
-
-/datum/action/small_sprite/queen
- small_icon = 'icons/mob/alien.dmi'
- small_icon_state = "alienq"
-
-/datum/action/small_sprite/megafauna
- icon_icon = 'icons/mob/actions/actions_xeno.dmi'
- button_icon_state = "smallqueen"
- background_icon_state = "bg_alien"
- small_icon = 'icons/mob/lavaland/lavaland_monsters.dmi'
-
-/datum/action/small_sprite/megafauna/drake
- small_icon_state = "ash_whelp"
-
-/datum/action/small_sprite/megafauna/colossus
- small_icon_state = "Basilisk"
-
-/datum/action/small_sprite/megafauna/stalwart
- small_icon_state = "Basilisk"
-
-/datum/action/small_sprite/megafauna/bubblegum
- small_icon_state = "goliath2"
-
-/datum/action/small_sprite/megafauna/legion
- small_icon_state = "dwarf_legion"
-
-/datum/action/small_sprite/megafauna/spacedragon
- small_icon = 'icons/mob/carp.dmi'
- small_icon_state = "carp"
-
-/datum/action/small_sprite/Trigger()
- ..()
- if(!small)
- var/image/I = image(icon = small_icon, icon_state = small_icon_state, loc = owner)
- I.override = TRUE
- I.pixel_x -= owner.pixel_x
- I.pixel_y -= owner.pixel_y
- owner.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic, "smallsprite", I, AA_TARGET_SEE_APPEARANCE | AA_MATCH_TARGET_OVERLAYS)
- small = TRUE
- else
- owner.remove_alt_appearance("smallsprite")
- small = FALSE
-
-/datum/action/item_action/storage_gather_mode
- name = "Switch gathering mode"
- desc = "Switches the gathering mode of a storage object."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
- button_icon_state = "storage_gather_switch"
-
-/datum/action/item_action/storage_gather_mode/ApplyIcon(atom/movable/screen/movable/action_button/current_button)
- . = ..()
- var/old_layer = target.layer
- var/old_plane = target.plane
- target.layer = FLOAT_LAYER //AAAH
- target.plane = FLOAT_PLANE //^ what that guy said
- current_button.cut_overlays()
- current_button.add_overlay(target)
- target.layer = old_layer
- target.plane = old_plane
- current_button.appearance_cache = target.appearance
diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm
new file mode 100644
index 000000000000..a1941e582716
--- /dev/null
+++ b/code/datums/actions/action.dm
@@ -0,0 +1,395 @@
+/**
+ * # Action system
+ *
+ * A simple base for an modular behavior attached to atom or datum.
+ */
+/datum/action
+ /// The name of the action
+ var/name = "Generic Action"
+ /// The description of what the action does
+ var/desc
+ /// The target the action is attached to. If the target datum is deleted, the action is as well.
+ /// Set in New() via the proc link_to(). PLEASE set a target if you're making an action
+ var/datum/target
+ /// Where any buttons we create should be by default. Accepts screen_loc and location defines
+ var/default_button_position = SCRN_OBJ_IN_LIST
+ /// This is who currently owns the action, and most often, this is who is using the action if it is triggered
+ /// This can be the same as "target" but is not ALWAYS the same - this is set and unset with Grant() and Remove()
+ var/mob/owner
+ /// If False, the owner of this action does not get a hud and cannot activate it on their own
+ var/owner_has_control = TRUE
+ /// Flags that will determine of the owner / user of the action can... use the action
+ var/check_flags = NONE
+ /// Whether the button becomes transparent when it can't be used or just reddened
+ var/transparent_when_unavailable = TRUE
+ ///List of all mobs that are viewing our action button -> A unique movable for them to view.
+ var/list/viewers = list()
+ /// If TRUE, this action button will be shown to observers / other mobs who view from this action's owner's eyes.
+ /// Used in [/mob/proc/show_other_mob_action_buttons]
+ var/show_to_observers = TRUE
+
+ /// The style the button's tooltips appear to be
+ var/buttontooltipstyle = ""
+
+ /// This is the file for the BACKGROUND icon of the button
+ var/background_icon = 'icons/mob/actions/backgrounds.dmi'
+ /// This is the icon state state for the BACKGROUND icon of the button
+ var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND
+
+ /// This is the file for the icon that appears OVER the button background
+ var/button_icon = 'icons/mob/actions.dmi'
+ /// This is the icon state for the icon that appears OVER the button background
+ var/button_icon_state = "default"
+
+ /// This is the file for any FOREGROUND overlay icons on the button (such as borders)
+ var/overlay_icon = 'icons/mob/actions/backgrounds.dmi'
+ /// This is the icon state for any FOREGROUND overlay icons on the button (such as borders)
+ var/overlay_icon_state
+
+/datum/action/New(Target)
+ link_to(Target)
+
+/// Links the passed target to our action, registering any relevant signals
+/datum/action/proc/link_to(Target)
+ target = Target
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE)
+
+ if(isatom(target))
+ RegisterSignal(target, COMSIG_ATOM_UPDATED_ICON, PROC_REF(update_status_on_signal))
+
+ if(istype(target, /datum/mind))
+ RegisterSignal(target, COMSIG_MIND_TRANSFERRED, PROC_REF(on_target_mind_swapped))
+
+/datum/action/Destroy()
+ if(owner)
+ Remove(owner)
+ target = null
+ QDEL_LIST_ASSOC_VAL(viewers) // Qdel the buttons in the viewers list **NOT THE HUDS**
+ return ..()
+
+/// Signal proc that clears any references based on the owner or target deleting
+/// If the owner's deleted, we will simply remove from them, but if the target's deleted, we will self-delete
+/datum/action/proc/clear_ref(datum/ref)
+ SIGNAL_HANDLER
+ if(ref == owner)
+ Remove(owner)
+ if(ref == target)
+ qdel(src)
+
+/// Grants the action to the passed mob, making it the owner
+/datum/action/proc/Grant(mob/grant_to)
+ if(!grant_to)
+ Remove(owner)
+ return
+ if(owner)
+ if(owner == grant_to)
+ return
+ Remove(owner)
+ SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, grant_to)
+ SEND_SIGNAL(grant_to, COMSIG_MOB_GRANTED_ACTION, src)
+ owner = grant_to
+ RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE)
+
+ // Register some signals based on our check_flags
+ // so that our button icon updates when relevant
+ if(check_flags & AB_CHECK_CONSCIOUS)
+ RegisterSignal(owner, COMSIG_MOB_STATCHANGE, PROC_REF(update_status_on_signal))
+ if(check_flags & AB_CHECK_INCAPACITATED)
+ RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED), PROC_REF(update_status_on_signal))
+ if(check_flags & AB_CHECK_IMMOBILE)
+ RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED), PROC_REF(update_status_on_signal))
+ if(check_flags & AB_CHECK_HANDS_BLOCKED)
+ RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), PROC_REF(update_status_on_signal))
+ if(check_flags & AB_CHECK_LYING)
+ RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(update_status_on_signal))
+
+ if(owner_has_control)
+ GiveAction(grant_to)
+
+/// Remove the passed mob from being owner of our action
+/datum/action/proc/Remove(mob/remove_from)
+ SHOULD_CALL_PARENT(TRUE)
+
+ for(var/datum/hud/hud in viewers)
+ if(!hud.mymob)
+ continue
+ HideFrom(hud.mymob)
+ LAZYREMOVE(remove_from.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared
+ viewers = list()
+
+ if(owner)
+ SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner)
+ SEND_SIGNAL(owner, COMSIG_MOB_REMOVED_ACTION, src)
+ UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
+
+ // Clean up our check_flag signals
+ UnregisterSignal(owner, list(
+ COMSIG_LIVING_SET_BODY_POSITION,
+ COMSIG_MOB_STATCHANGE,
+ SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED),
+ SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED),
+ SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED),
+ ))
+
+ if(target == owner)
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref))
+ owner = null
+
+/// Actually triggers the effects of the action.
+/// Called when the on-screen button is clicked, for example.
+/datum/action/proc/Trigger(trigger_flags)
+ if(!IsAvailable(feedback = TRUE))
+ return FALSE
+ if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER)
+ return FALSE
+ return TRUE
+
+/**
+ * Whether our action is currently available to use or not
+ * * feedback - If true this is being called to check if we have any messages to show to the owner
+ */
+/datum/action/proc/IsAvailable(feedback = FALSE)
+ if(!owner)
+ return FALSE
+ if((check_flags & AB_CHECK_HANDS_BLOCKED) && HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED))
+ if (feedback)
+ owner.balloon_alert(owner, "hands blocked!")
+ return FALSE
+ if((check_flags & AB_CHECK_IMMOBILE) && HAS_TRAIT(owner, TRAIT_IMMOBILIZED))
+ if (feedback)
+ owner.balloon_alert(owner, "can't move!")
+ return FALSE
+ if((check_flags & AB_CHECK_INCAPACITATED) && HAS_TRAIT(owner, TRAIT_INCAPACITATED))
+ if (feedback)
+ owner.balloon_alert(owner, "incapacitated!")
+ return FALSE
+ if((check_flags & AB_CHECK_LYING) && isliving(owner))
+ var/mob/living/action_user = owner
+ if(!(action_user.mobility_flags & MOBILITY_STAND))
+ if (feedback)
+ owner.balloon_alert(owner, "must stand up!")
+ return FALSE
+ if((check_flags & AB_CHECK_CONSCIOUS) && owner.stat != CONSCIOUS)
+ if (feedback)
+ owner.balloon_alert(owner, "unconscious!")
+ return FALSE
+ return TRUE
+
+/// Builds / updates all buttons we have shared or given out
+/datum/action/proc/build_all_button_icons(update_flags = ALL, force)
+ for(var/datum/hud/hud as anything in viewers)
+ build_button_icon(viewers[hud], update_flags, force)
+
+/**
+ * Builds the icon of the button.
+ *
+ * Concept:
+ * - Underlay (Background icon)
+ * - Icon (button icon)
+ * - Maptext
+ * - Overlay (Background border)
+ *
+ * button - which button we are modifying the icon of
+ * force - whether we're forcing a full update
+ */
+/datum/action/proc/build_button_icon(atom/movable/screen/movable/action_button/button, update_flags = ALL, force = FALSE)
+ if(!button)
+ return
+
+ if(update_flags & UPDATE_BUTTON_NAME)
+ update_button_name(button, force)
+
+ if(update_flags & UPDATE_BUTTON_BACKGROUND)
+ apply_button_background(button, force)
+
+ if(update_flags & UPDATE_BUTTON_ICON)
+ apply_button_icon(button, force)
+
+ if(update_flags & UPDATE_BUTTON_OVERLAY)
+ apply_button_overlay(button, force)
+
+ if(update_flags & UPDATE_BUTTON_STATUS)
+ update_button_status(button, force)
+
+/**
+ * Updates the name and description of the button to match our action name and discription.
+ *
+ * current_button - what button are we editing?
+ * force - whether an update is forced regardless of existing status
+ */
+/datum/action/proc/update_button_name(atom/movable/screen/movable/action_button/button, force = FALSE)
+ button.name = name
+ if(desc)
+ button.desc = desc
+
+/**
+ * Creates the background underlay for the button
+ *
+ * current_button - what button are we editing?
+ * force - whether an update is forced regardless of existing status
+ */
+/datum/action/proc/apply_button_background(atom/movable/screen/movable/action_button/current_button, force = FALSE)
+ if(!background_icon || !background_icon_state || (current_button.active_underlay_icon_state == background_icon_state && !force))
+ return
+
+ // What icons we use for our background
+ var/list/icon_settings = list(
+ // The icon file
+ "bg_icon" = background_icon,
+ // The icon state, if is_action_active() returns FALSE
+ "bg_state" = background_icon_state,
+ // The icon state, if is_action_active() returns TRUE
+ "bg_state_active" = background_icon_state,
+ )
+
+ // If background_icon_state is ACTION_BUTTON_DEFAULT_BACKGROUND instead use our hud's action button scheme
+ if(background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND && owner?.hud_used)
+ icon_settings = owner.hud_used.get_action_buttons_icons()
+
+ // Determine which icon to use
+ var/used_icon_key = is_action_active(current_button) ? "bg_state_active" : "bg_state"
+
+ // Make the underlay
+ current_button.underlays.Cut()
+ current_button.underlays += image(icon = icon_settings["bg_icon"], icon_state = icon_settings[used_icon_key])
+ current_button.active_underlay_icon_state = icon_settings[used_icon_key]
+
+/**
+ * Applies our button icon and icon state to the button
+ *
+ * current_button - what button are we editing?
+ * force - whether an update is forced regardless of existing status
+ */
+/datum/action/proc/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force = FALSE)
+ if(!button_icon || !button_icon_state || (current_button.icon_state == button_icon_state && !force))
+ return
+
+ current_button.icon = button_icon
+ current_button.icon_state = button_icon_state
+ current_button.actiontooltipstyle = buttontooltipstyle
+
+/**
+ * Applies any overlays to our button
+ *
+ * current_button - what button are we editing?
+ * force - whether an update is forced regardless of existing status
+ */
+/datum/action/proc/apply_button_overlay(atom/movable/screen/movable/action_button/current_button, force = FALSE)
+
+ SEND_SIGNAL(src, COMSIG_ACTION_OVERLAY_APPLY, current_button, force)
+
+ if(!overlay_icon || !overlay_icon_state || (current_button.active_overlay_icon_state == overlay_icon_state && !force))
+ return
+
+ current_button.cut_overlay(current_button.button_overlay)
+ current_button.button_overlay = mutable_appearance(icon = overlay_icon, icon_state = overlay_icon_state)
+ current_button.add_overlay(current_button.button_overlay)
+ current_button.active_overlay_icon_state = overlay_icon_state
+
+/**
+ * Any other miscellaneous "status" updates within the action button is handled here,
+ * such as redding out when unavailable or modifying maptext.
+ *
+ * current_button - what button are we editing?
+ * force - whether an update is forced regardless of existing status
+ */
+/datum/action/proc/update_button_status(atom/movable/screen/movable/action_button/current_button, force = FALSE)
+ if(IsAvailable(feedback = FALSE))
+ current_button.color = rgb(255,255,255,255)
+ else
+ current_button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
+
+/// Gives our action to the passed viewer.
+/// Puts our action in their actions list and shows them the button.
+/datum/action/proc/GiveAction(mob/viewer)
+ var/datum/hud/our_hud = viewer.hud_used
+ if(viewers[our_hud]) // Already have a copy of us? go away
+ return
+
+ LAZYOR(viewer.actions, src) // Move this in
+ ShowTo(viewer)
+
+/// Adds our action button to the screen of the passed viewer.
+/datum/action/proc/ShowTo(mob/viewer)
+ var/datum/hud/our_hud = viewer.hud_used
+ if(!our_hud || viewers[our_hud]) // There's no point in this if you have no hud in the first place
+ return
+
+ var/atom/movable/screen/movable/action_button/button = create_button()
+ SetId(button, viewer)
+
+ button.our_hud = our_hud
+ viewers[our_hud] = button
+ if(viewer.client)
+ viewer.client.screen += button
+
+ button.load_position(viewer)
+ viewer.update_action_buttons()
+
+/// Removes our action from the passed viewer.
+/datum/action/proc/HideFrom(mob/viewer)
+ var/datum/hud/our_hud = viewer.hud_used
+ var/atom/movable/screen/movable/action_button/button = viewers[our_hud]
+ LAZYREMOVE(viewer.actions, src)
+ if(button)
+ qdel(button)
+
+/// Creates an action button movable for the passed mob, and returns it.
+/datum/action/proc/create_button()
+ var/atom/movable/screen/movable/action_button/button = new()
+ button.linked_action = src
+ build_button_icon(button, ALL, TRUE)
+ return button
+
+/datum/action/proc/SetId(atom/movable/screen/movable/action_button/our_button, mob/owner)
+ //button id generation
+ var/bitfield = 0
+ for(var/datum/action/action in owner.actions)
+ if(action == src) // This could be us, which is dumb
+ continue
+ var/atom/movable/screen/movable/action_button/button = action.viewers[owner.hud_used]
+ if(action.name == name && button.id)
+ bitfield |= button.id
+
+ bitfield = ~bitfield // Flip our possible ids, so we can check if we've found a unique one
+ for(var/i in 0 to 23) // We get 24 possible bitflags in dm
+ var/bitflag = 1 << i // Shift us over one
+ if(bitfield & bitflag)
+ our_button.id = bitflag
+ return
+
+/// Updates our buttons if our target's icon was updated
+/datum/action/proc/on_target_icon_update(datum/source, updates, updated)
+ SIGNAL_HANDLER
+
+ var/update_flag = ALL
+ var/forced = TRUE
+// if(updates & UPDATE_ICON_STATE)
+// update_flag |= UPDATE_BUTTON_ICON
+// forced = TRUE
+// if(updates & UPDATE_OVERLAYS)
+// update_flag |= UPDATE_BUTTON_OVERLAY
+// forced = TRUE
+// if(updates & (UPDATE_NAME|UPDATE_DESC))
+// update_flag |= UPDATE_BUTTON_NAME
+ // Status is not relevant, and background is not relevant. Neither will change
+
+ // Force the update if an icon state or overlay change was done
+ build_all_button_icons(update_flag, forced)
+
+/// A general use signal proc that reacts to an event and updates JUST our button's status
+/datum/action/proc/update_status_on_signal(datum/source)
+ SIGNAL_HANDLER
+
+ build_all_button_icons(UPDATE_BUTTON_STATUS)
+
+/// Signal proc for COMSIG_MIND_TRANSFERRED - for minds, transfers our action to our new mob on mind transfer
+/datum/action/proc/on_target_mind_swapped(datum/mind/source, mob/old_current)
+ SIGNAL_HANDLER
+
+ // Grant() calls Remove() from the existing owner so we're covered on that
+ Grant(source.current)
+
+/// Checks if our action is actively selected. Used for selecting icons primarily.
+/datum/action/proc/is_action_active(atom/movable/screen/movable/action_button/current_button)
+ return FALSE
diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm
new file mode 100644
index 000000000000..27ae684aefb6
--- /dev/null
+++ b/code/datums/actions/cooldown_action.dm
@@ -0,0 +1,339 @@
+#define COOLDOWN_NO_DISPLAY_TIME (180 SECONDS)
+
+/// Preset for an action that has a cooldown.
+/datum/action/cooldown
+ check_flags = NONE
+ transparent_when_unavailable = FALSE
+
+ /// The actual next time this ability can be used
+ var/next_use_time = 0
+ /// The stat panel this action shows up in the stat panel in. If null, will not show up.
+ var/panel
+ /// The default cooldown applied when StartCooldown() is called
+ var/cooldown_time = 0
+ /// The default melee cooldown applied after the ability ends
+ var/melee_cooldown_time
+ /// The actual next time the owner of this action can melee
+ var/next_melee_use_time = 0
+ /// Whether or not you want the cooldown for the ability to display in text form
+ var/text_cooldown = TRUE
+ /// Significant figures to round cooldown to
+ var/cooldown_rounding = 0.1
+ /// Shares cooldowns with other abiliies, bitflag
+ var/shared_cooldown
+ /// List of prerequisite actions that are used in this sequenced ability, you cannot put other sequenced abilities in this
+ var/list/sequence_actions
+ /// List of prerequisite actions that have been initialized
+ var/list/initialized_actions
+
+ // These are only used for click_to_activate actions
+ /// Setting for intercepting clicks before activating the ability
+ var/click_to_activate = FALSE
+ /// The cooldown added onto the user's next click.
+ var/click_cd_override = CLICK_CD_CLICK_ABILITY
+ /// If TRUE, we will unset after using our click intercept.
+ var/unset_after_click = TRUE
+ /// What icon to replace our mouse cursor with when active. Optional
+ var/ranged_mousepointer
+ /// The base icon_state of this action's background
+ var/base_background_icon_state
+ /// The icon state the background uses when active
+ var/active_background_icon_state
+ /// The base icon_state of the overlay we apply
+ var/base_overlay_icon_state
+ /// The active icon_state of the overlay we apply
+ var/active_overlay_icon_state
+ /// The base icon state of the spell's button icon, used for editing the icon "off"
+ var/base_icon_state
+ /// The active icon state of the spell's button icon, used for editing the icon "on"
+ var/active_icon_state
+
+/datum/action/cooldown/New(Target, original = TRUE)
+ . = ..()
+ if(active_background_icon_state)
+ base_background_icon_state ||= background_icon_state
+ if(active_overlay_icon_state)
+ base_overlay_icon_state ||= overlay_icon_state
+ if(active_icon_state)
+ base_icon_state ||= button_icon_state
+
+ if(isnull(melee_cooldown_time))
+ melee_cooldown_time = cooldown_time
+
+ if(original)
+ create_sequence_actions()
+
+/datum/action/cooldown/create_button()
+ var/atom/movable/screen/movable/action_button/button = ..()
+ button.maptext = ""
+ button.maptext_x = 6
+ button.maptext_y = 2
+ button.maptext_width = 24
+ button.maptext_height = 12
+ return button
+
+/datum/action/cooldown/update_button_status(atom/movable/screen/movable/action_button/button, force = FALSE)
+ . = ..()
+ var/time_left = max(next_use_time - world.time, 0)
+ if(!text_cooldown || !owner || time_left == 0 || time_left >= COOLDOWN_NO_DISPLAY_TIME)
+ button.maptext = ""
+ else
+ if (cooldown_rounding > 0)
+ button.maptext = MAPTEXT("[round(time_left/10, cooldown_rounding)]")
+ else
+ button.maptext = MAPTEXT("[round(time_left/10)]")
+
+ if(!IsAvailable(feedback = FALSE) || !is_action_active(button))
+ return
+ // If we don't change the icon state, or don't apply a special overlay,
+ if(active_background_icon_state || active_icon_state || active_overlay_icon_state)
+ return
+ // ...we need to show it's active somehow. So, make it greeeen
+ button.color = COLOR_GREEN
+
+/datum/action/cooldown/apply_button_background(atom/movable/screen/movable/action_button/current_button, force)
+ if(active_background_icon_state)
+ background_icon_state = is_action_active(current_button) ? active_background_icon_state : base_background_icon_state
+ return ..()
+
+/datum/action/cooldown/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force)
+ if(active_icon_state)
+ button_icon_state = is_action_active(current_button) ? active_icon_state : base_icon_state
+ return ..()
+
+/datum/action/cooldown/apply_button_overlay(atom/movable/screen/movable/action_button/current_button, force)
+ if(active_overlay_icon_state)
+ overlay_icon_state = is_action_active(current_button) ? active_overlay_icon_state : base_overlay_icon_state
+ return ..()
+
+/datum/action/cooldown/is_action_active(atom/movable/screen/movable/action_button/current_button)
+ return click_to_activate && current_button.our_hud?.mymob?.click_intercept == src
+
+/datum/action/cooldown/Destroy()
+ QDEL_LIST(initialized_actions)
+ return ..()
+
+/datum/action/cooldown/Grant(mob/granted_to)
+ . = ..()
+ if(!owner)
+ return
+ build_all_button_icons()
+ if(next_use_time > world.time)
+ START_PROCESSING(SSfastprocess, src)
+// RegisterSignal(granted_to, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(handle_melee_attack))
+ for(var/datum/action/cooldown/ability as anything in initialized_actions)
+ ability.Grant(granted_to)
+
+/datum/action/cooldown/Remove(mob/removed_from)
+// UnregisterSignal(removed_from, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)
+ if(click_to_activate && removed_from.click_intercept == src)
+ unset_click_ability(removed_from, refund_cooldown = FALSE)
+ for(var/datum/action/cooldown/ability as anything in initialized_actions)
+ ability.Remove(removed_from)
+ return ..()
+
+/datum/action/cooldown/IsAvailable(feedback = FALSE)
+ return ..() && (next_use_time <= world.time)
+
+/// Initializes any sequence actions
+/datum/action/cooldown/proc/create_sequence_actions()
+ if(!LAZYLEN(sequence_actions))
+ return
+ // remove existing actions if any
+ QDEL_LIST(initialized_actions)
+ initialized_actions = list()
+ for(var/type_path in sequence_actions)
+ var/datum/action/cooldown/ability = new type_path(target, original = FALSE)
+ // prevents clients from using the individual abilities in sequences (this stops it from being added to mob actions when granted as well)
+ ability.owner_has_control = FALSE
+ // [ability] = delay
+ initialized_actions[ability] = sequence_actions[type_path]
+
+/// Starts a cooldown time to be shared with similar abilities
+/// Will use default cooldown time if an override is not specified
+/datum/action/cooldown/proc/StartCooldown(override_cooldown_time, override_melee_cooldown_time)
+ // "Shared cooldowns" covers actions which are not the same type,
+ // but have the same cooldown group and are on the same mob
+ if(shared_cooldown)
+ StartCooldownOthers(override_cooldown_time)
+
+ StartCooldownSelf(override_cooldown_time)
+
+ if(isnum(override_melee_cooldown_time))
+ next_melee_use_time = world.time + override_melee_cooldown_time
+ else
+ next_melee_use_time = world.time + melee_cooldown_time
+
+/// Starts a cooldown time for this ability only
+/// Will use default cooldown time if an override is not specified
+/datum/action/cooldown/proc/StartCooldownSelf(override_cooldown_time)
+ if(isnum(override_cooldown_time))
+ next_use_time = world.time + override_cooldown_time
+ else
+ next_use_time = world.time + cooldown_time
+ build_all_button_icons(UPDATE_BUTTON_STATUS)
+ START_PROCESSING(SSfastprocess, src)
+
+/// Starts a cooldown time for other abilities that share a cooldown with this. Has some niche usage with more complicated attack ai!
+/// Will use default cooldown time if an override is not specified
+/datum/action/cooldown/proc/StartCooldownOthers(override_cooldown_time)
+ for(var/datum/action/cooldown/shared_ability in owner.actions - src)
+ if(!(shared_cooldown & shared_ability.shared_cooldown))
+ continue
+ if(isnum(override_cooldown_time))
+ shared_ability.StartCooldownSelf(override_cooldown_time)
+ else
+ shared_ability.StartCooldownSelf(cooldown_time)
+
+/datum/action/cooldown/Trigger(trigger_flags, atom/target)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!owner)
+ return FALSE
+
+ var/mob/user = usr || owner
+
+ // If our cooldown action is a click_to_activate action:
+ // The actual action is activated on whatever the user clicks on -
+ // the target is what the action is being used on
+ // In trigger, we handle setting the click intercept
+ if(click_to_activate)
+ if(target)
+ // For automatic / mob handling
+ return InterceptClickOn(user, null, target)
+
+ var/datum/action/cooldown/already_set = user.click_intercept
+ if(already_set == src)
+ // if we clicked ourself and we're already set, unset and return
+ return unset_click_ability(user, refund_cooldown = TRUE)
+
+ else if(istype(already_set))
+ // if we have an active set already, unset it before we set our's
+ already_set.unset_click_ability(user, refund_cooldown = TRUE)
+
+ return set_click_ability(user)
+
+ // If our cooldown action is not a click_to_activate action:
+ // We can just continue on and use the action
+ // the target is the user of the action (often, the owner)
+ return PreActivate(user)
+
+/// Intercepts client owner clicks to activate the ability
+/datum/action/cooldown/proc/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(!IsAvailable(feedback = TRUE))
+ return FALSE
+ if(!target)
+ return FALSE
+ // The actual action begins here
+ if(!PreActivate(target))
+ return FALSE
+
+ // And if we reach here, the action was complete successfully
+ if(unset_after_click)
+ unset_click_ability(caller, refund_cooldown = FALSE)
+ caller.next_click = world.time + click_cd_override
+
+ return TRUE
+
+/// For signal calling
+/datum/action/cooldown/proc/PreActivate(atom/target)
+ if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START)
+ return
+ // Note, that PreActivate handles no cooldowns at all by default.
+ // Be sure to call StartCooldown() in Activate() where necessary.
+ . = Activate(target)
+ // There is a possibility our action (or owner) is qdeleted in Activate().
+ if(!QDELETED(src) && !QDELETED(owner))
+ SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_FINISHED, src)
+
+/// To be implemented by subtypes (if not generic)
+/datum/action/cooldown/proc/Activate(atom/target)
+ var/total_delay = 0
+ for(var/datum/action/cooldown/ability as anything in initialized_actions)
+ if(LAZYLEN(ability.initialized_actions) > 0)
+ ability.initialized_actions = list()
+ addtimer(CALLBACK(ability, PROC_REF(Activate), target), total_delay)
+ total_delay += initialized_actions[ability]
+ StartCooldown()
+
+/// Cancels melee attacks if they are on cooldown.
+/datum/action/cooldown/proc/handle_melee_attack(mob/source, mob/target)
+ SIGNAL_HANDLER
+ if(next_melee_use_time > world.time)
+ return COMPONENT_HOSTILE_NO_ATTACK
+
+/datum/action/cooldown/process()
+ if(!owner || (next_use_time - world.time) <= 0)
+ build_all_button_icons(UPDATE_BUTTON_STATUS)
+ STOP_PROCESSING(SSfastprocess, src)
+ return
+
+ build_all_button_icons(UPDATE_BUTTON_STATUS)
+
+/**
+ * Set our action as the click override on the passed mob.
+ */
+/datum/action/cooldown/proc/set_click_ability(mob/on_who)
+ SHOULD_CALL_PARENT(TRUE)
+
+ on_who.click_intercept = src
+ if(ranged_mousepointer)
+ on_who.client?.mouse_override_icon = ranged_mousepointer
+ on_who.update_mouse_pointer()
+ build_all_button_icons(UPDATE_BUTTON_STATUS)
+ return TRUE
+
+/**
+ * Unset our action as the click override of the passed mob.
+ *
+ * if refund_cooldown is TRUE, we are being unset by the user clicking the action off
+ * if refund_cooldown is FALSE, we are being forcefully unset, likely by someone actually using the action
+ */
+/datum/action/cooldown/proc/unset_click_ability(mob/on_who, refund_cooldown = TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ on_who.click_intercept = null
+ if(ranged_mousepointer)
+ on_who.client?.mouse_override_icon = initial(on_who.client?.mouse_override_icon)
+ on_who.update_mouse_pointer()
+ build_all_button_icons(UPDATE_BUTTON_STATUS)
+ return TRUE
+
+/// Formats the action to be returned to the stat panel.
+/datum/action/cooldown/proc/set_statpanel_format()
+ if(!panel)
+ return null
+
+ var/time_remaining = max(next_use_time - world.time, 0)
+ var/time_remaining_in_seconds = round(time_remaining / 10, 0.1)
+ var/cooldown_time_in_seconds = round(cooldown_time / 10, 0.1)
+
+ var/list/stat_panel_data = list()
+
+ // Pass on what panel we should be displayed in.
+ stat_panel_data[PANEL_DISPLAY_PANEL] = panel
+ // Also pass on the name of the spell, with some spacing
+ stat_panel_data[PANEL_DISPLAY_NAME] = " - [name]"
+
+ // No cooldown time at all, just show the ability
+ if(cooldown_time_in_seconds <= 0)
+ stat_panel_data[PANEL_DISPLAY_STATUS] = ""
+
+ // It's a toggle-active ability, show if it's active
+ else if(click_to_activate && owner.click_intercept == src)
+ stat_panel_data[PANEL_DISPLAY_STATUS] = "ACTIVE"
+
+ // It's on cooldown, show the cooldown
+ else if(time_remaining_in_seconds > 0)
+ stat_panel_data[PANEL_DISPLAY_STATUS] = "CD - [time_remaining_in_seconds]s / [cooldown_time_in_seconds]s"
+
+ // It's not on cooldown, show that it is ready
+ else
+ stat_panel_data[PANEL_DISPLAY_STATUS] = "READY"
+
+ SEND_SIGNAL(src, COMSIG_ACTION_SET_STATPANEL, stat_panel_data)
+
+ return stat_panel_data
+
+#undef COOLDOWN_NO_DISPLAY_TIME
diff --git a/code/datums/actions/innate_action.dm b/code/datums/actions/innate_action.dm
new file mode 100644
index 000000000000..3f4dde5cd4e4
--- /dev/null
+++ b/code/datums/actions/innate_action.dm
@@ -0,0 +1,95 @@
+//Preset for general and toggled actions
+/datum/action/innate
+ check_flags = NONE
+ /// Whether we're active or not, if we're a innate - toggle action.
+ var/active = FALSE
+ /// Whether we're a click action or not, if we're a innate - click action.
+ var/click_action = FALSE
+ /// If we're a click action, the mouse pointer we use
+ var/ranged_mousepointer
+ /// If we're a click action, the text shown on enable
+ var/enable_text
+ /// If we're a click action, the text shown on disable
+ var/disable_text
+
+/datum/action/innate/Trigger(trigger_flags)
+ if(!..())
+ return FALSE
+ // We're a click action, trigger just sets it as active or not
+ if(click_action)
+ if(owner.click_intercept == src)
+ unset_ranged_ability(owner, disable_text)
+ else
+ set_ranged_ability(owner, enable_text)
+ build_all_button_icons(UPDATE_BUTTON_STATUS)
+ return TRUE
+
+ // We're not a click action (we're a toggle or otherwise)
+ else
+ var/active_status = active
+ if(active_status)
+ Deactivate()
+ else
+ Activate()
+
+ if(active != active_status)
+ build_all_button_icons(UPDATE_BUTTON_STATUS)
+
+ return TRUE
+
+/datum/action/innate/is_action_active(atom/movable/screen/movable/action_button/current_button)
+ if(click_action)
+ return current_button.our_hud?.mymob?.click_intercept == src
+ else
+ return active
+
+/datum/action/innate/proc/Activate()
+ return
+
+/datum/action/innate/proc/Deactivate()
+ return
+
+/**
+ * This is gross, but a somewhat-required bit of copy+paste until action code becomes slightly more sane.
+ * Anything that uses these functions should eventually be moved to use cooldown actions.
+ * (Either that, or the click ability of cooldown actions should be moved down a type.)
+ *
+ * If you're adding something that uses these, rethink your choice in subtypes.
+ */
+
+/// Sets this action as the active ability for the passed mob
+/datum/action/innate/proc/set_ranged_ability(mob/living/on_who, text_to_show)
+ if(ranged_mousepointer)
+ on_who.client?.mouse_override_icon = ranged_mousepointer
+ on_who.update_mouse_pointer()
+ if(text_to_show)
+ to_chat(on_who, text_to_show)
+ on_who.click_intercept = src
+
+/// Removes this action as the active ability of the passed mob
+/datum/action/innate/proc/unset_ranged_ability(mob/living/on_who, text_to_show)
+ if(ranged_mousepointer)
+ on_who.client?.mouse_override_icon = initial(owner.client?.mouse_pointer_icon)
+ on_who.update_mouse_pointer()
+ if(text_to_show)
+ to_chat(on_who, text_to_show)
+ on_who.click_intercept = null
+
+/// Handles whenever a mob clicks on something
+/datum/action/innate/proc/InterceptClickOn(mob/living/caller, params, atom/clicked_on)
+ if(!IsAvailable(feedback = TRUE))
+ unset_ranged_ability(caller)
+ return FALSE
+ if(!clicked_on)
+ return FALSE
+
+ return do_ability(caller, params, clicked_on)
+
+/// Actually goes through and does the click ability
+/datum/action/innate/proc/do_ability(mob/living/caller, params, atom/clicked_on)
+ return FALSE
+
+/datum/action/innate/Remove(mob/removed_from)
+ if(removed_from.click_intercept == src)
+ unset_ranged_ability(removed_from)
+ return ..()
diff --git a/code/datums/actions/item_action.dm b/code/datums/actions/item_action.dm
new file mode 100644
index 000000000000..09e34685c4da
--- /dev/null
+++ b/code/datums/actions/item_action.dm
@@ -0,0 +1,33 @@
+//Presets for item actions
+/datum/action/item_action
+ name = "Item Action"
+ check_flags = AB_CHECK_INCAPACITATED|AB_CHECK_HANDS_BLOCKED|AB_CHECK_CONSCIOUS
+ button_icon_state = null
+
+/datum/action/item_action/New(Target)
+ . = ..()
+
+ // If our button state is null, use the target's icon instead
+ if(target && isnull(button_icon_state))
+ AddComponent(/datum/component/action_item_overlay, target)
+
+/datum/action/item_action/vv_edit_var(var_name, var_value)
+ . = ..()
+ if(!. || !target)
+ return
+
+ if(var_name == NAMEOF(src, button_icon_state))
+ // If someone vv's our icon either add or remove the component
+ if(isnull(var_name))
+ AddComponent(/datum/component/action_item_overlay, target)
+ else
+ qdel(GetComponent(/datum/component/action_item_overlay))
+
+/datum/action/item_action/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(target)
+ var/obj/item/I = target
+ I.ui_action_click(owner, src)
+ return TRUE
diff --git a/code/datums/actions/items/adjust.dm b/code/datums/actions/items/adjust.dm
new file mode 100644
index 000000000000..f913bac5ba61
--- /dev/null
+++ b/code/datums/actions/items/adjust.dm
@@ -0,0 +1,7 @@
+/datum/action/item_action/adjust
+ name = "Adjust Item"
+
+/datum/action/item_action/adjust/New(Target)
+ ..()
+ var/obj/item/item_target = target
+ name = "Adjust [item_target.name]"
\ No newline at end of file
diff --git a/code/datums/actions/beam_rifle.dm b/code/datums/actions/items/beam_rifle.dm
similarity index 60%
rename from code/datums/actions/beam_rifle.dm
rename to code/datums/actions/items/beam_rifle.dm
index 3af5d13690d0..a288ca695e2f 100644
--- a/code/datums/actions/beam_rifle.dm
+++ b/code/datums/actions/items/beam_rifle.dm
@@ -1,12 +1,13 @@
-
/datum/action/item_action/zoom_speed_action
name = "Toggle Zooming Speed"
- icon_icon = 'icons/mob/actions/actions_spells.dmi'
+ button_icon = 'icons/mob/actions/actions_spells.dmi'
button_icon_state = "projectile"
background_icon_state = "bg_tech"
+ overlay_icon_state = "bg_tech_border"
/datum/action/item_action/zoom_lock_action
name = "Switch Zoom Mode"
- icon_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "zoom_mode"
background_icon_state = "bg_tech"
+ overlay_icon_state = "bg_tech_border"
diff --git a/code/datums/actions/items/clockcult.dm b/code/datums/actions/items/clockcult.dm
new file mode 100644
index 000000000000..036b94063658
--- /dev/null
+++ b/code/datums/actions/items/clockcult.dm
@@ -0,0 +1,32 @@
+/datum/action/item_action/clock
+ button_icon = 'icons/mob/actions/actions_clockcult.dmi'
+ background_icon_state = "bg_clock"
+ buttontooltipstyle = "clockcult"
+
+/datum/action/item_action/clock/IsAvailable(feedback = FALSE)
+ if(!is_servant_of_ratvar(owner))
+ return FALSE
+ return ..()
+
+/datum/action/item_action/clock/toggle_visor
+ name = "Create Judicial Marker"
+ desc = "Allows you to create a stunning Judicial Marker at any location in view. Click again to disable."
+
+/datum/action/item_action/clock/toggle_visor/IsAvailable(feedback = FALSE)
+ if(!is_servant_of_ratvar(owner))
+ return FALSE
+ if(istype(target, /obj/item/clothing/glasses/judicial_visor))
+ var/obj/item/clothing/glasses/judicial_visor/visor = target
+ if(visor.recharging)
+ return FALSE
+ return ..()
+
+/datum/action/item_action/clock/hierophant
+ name = "Hierophant Network"
+ desc = "Lets you discreetly talk with all other servants. Nearby listeners can hear you whispering, so make sure to do this privately."
+ button_icon_state = "hierophant_slab"
+
+/datum/action/item_action/clock/quickbind
+ name = "Quickbind"
+ desc = "If you're seeing this, file a bug report."
+ var/scripture_index = 0 //the index of the scripture we're associated with
diff --git a/code/datums/actions/items/cult_dagger.dm b/code/datums/actions/items/cult_dagger.dm
new file mode 100644
index 000000000000..986f70677381
--- /dev/null
+++ b/code/datums/actions/items/cult_dagger.dm
@@ -0,0 +1,37 @@
+/datum/action/item_action/cult_dagger
+ name = "Draw Blood Rune"
+ desc = "Use the ritual dagger to create a powerful blood rune"
+ button_icon = 'icons/mob/actions/actions_cult.dmi'
+ button_icon_state = "draw"
+ buttontooltipstyle = "cult"
+ background_icon_state = "bg_demon"
+ overlay_icon_state = "bg_demon_border"
+
+ default_button_position = "6:157,4:-2"
+
+/datum/action/item_action/cult_dagger/Grant(mob/M)
+ if(iscultist(M))
+ return ..()
+ Remove(owner)
+
+/datum/action/item_action/cult_dagger/Trigger(trigger_flags)
+ for(var/obj/item/held_item as anything in owner.held_items) // In case we were already holding a dagger
+ if(istype(held_item, /obj/item/melee/cultblade/dagger))
+ held_item.attack_self(owner)
+ return
+ var/obj/item/target_item = target
+ if(owner.can_equip(target_item, ITEM_SLOT_HANDS))
+ owner.temporarilyRemoveItemFromInventory(target_item)
+ owner.put_in_hands(target_item)
+ target_item.attack_self(owner)
+ return
+
+ if(!isliving(owner))
+ to_chat(owner, span_warning("You lack the necessary living force for this action."))
+ return
+
+ var/mob/living/living_owner = owner
+ if (living_owner.get_num_arms() <= 0)
+ to_chat(living_owner, span_warning("You don't have any usable hands!"))
+ else
+ to_chat(living_owner, span_warning("Your hands are full!"))
diff --git a/code/datums/actions/items/hands_free.dm b/code/datums/actions/items/hands_free.dm
new file mode 100644
index 000000000000..a9cc3ff30b20
--- /dev/null
+++ b/code/datums/actions/items/hands_free.dm
@@ -0,0 +1,8 @@
+/datum/action/item_action/hands_free
+ check_flags = AB_CHECK_CONSCIOUS
+
+/datum/action/item_action/hands_free/activate
+ name = "Activate"
+
+/datum/action/item_action/hands_free/shift_nerves
+ name = "Shift Nerves"
\ No newline at end of file
diff --git a/code/datums/actions/items/organ_action.dm b/code/datums/actions/items/organ_action.dm
new file mode 100644
index 000000000000..367c00d656a9
--- /dev/null
+++ b/code/datums/actions/items/organ_action.dm
@@ -0,0 +1,25 @@
+/datum/action/item_action/organ_action
+ name = "Organ Action"
+ check_flags = AB_CHECK_CONSCIOUS
+
+/datum/action/item_action/organ_action/IsAvailable(feedback = FALSE)
+ var/obj/item/organ/attached_organ = target
+ if(!attached_organ.owner)
+ return FALSE
+ return ..()
+
+/datum/action/item_action/organ_action/toggle
+ name = "Toggle Organ"
+
+/datum/action/item_action/organ_action/toggle/New(Target)
+ ..()
+ var/obj/item/organ/organ_target = target
+ name = "Toggle [organ_target.name]"
+
+/datum/action/item_action/organ_action/use
+ name = "Use Organ"
+
+/datum/action/item_action/organ_action/use/New(Target)
+ ..()
+ var/obj/item/organ/organ_target = target
+ name = "Use [organ_target.name]"
diff --git a/code/datums/actions/items/set_internals.dm b/code/datums/actions/items/set_internals.dm
new file mode 100644
index 000000000000..dd1395a6e51a
--- /dev/null
+++ b/code/datums/actions/items/set_internals.dm
@@ -0,0 +1,8 @@
+/datum/action/item_action/set_internals
+ name = "Set Internals"
+ default_button_position = SCRN_OBJ_INSERT_FIRST
+ overlay_icon_state = "ab_goldborder"
+
+/datum/action/item_action/set_internals/is_action_active(atom/movable/screen/movable/action_button/current_button)
+ var/mob/living/carbon/carbon_owner = owner
+ return istype(carbon_owner) && target == carbon_owner.internal
diff --git a/code/datums/actions/items/stealth_box.dm b/code/datums/actions/items/stealth_box.dm
new file mode 100644
index 000000000000..a0e363d56bfc
--- /dev/null
+++ b/code/datums/actions/items/stealth_box.dm
@@ -0,0 +1,56 @@
+///MGS BOX!
+/datum/action/item_action/agent_box
+ name = "Deploy Box"
+ desc = "Find inner peace, here, in the box."
+ check_flags = AB_CHECK_INCAPACITATED|AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS
+ background_icon_state = "bg_agent"
+ overlay_icon_state = "bg_agent_border"
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "deploy_box"
+ ///The type of closet this action spawns.
+ var/boxtype = /obj/structure/closet/cardboard/agent
+ COOLDOWN_DECLARE(box_cooldown)
+
+///Handles opening and closing the box.
+/datum/action/item_action/agent_box/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(istype(owner.loc, /obj/structure/closet/cardboard/agent))
+ var/obj/structure/closet/cardboard/agent/box = owner.loc
+ if(box.open())
+ owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
+ return
+ //Box closing from here on out.
+ if(!isturf(owner.loc)) //Don't let the player use this to escape mechs/welded closets.
+ to_chat(owner, span_warning("You need more space to activate this implant!"))
+ return
+ if(!COOLDOWN_FINISHED(src, box_cooldown))
+ return
+ COOLDOWN_START(src, box_cooldown, 10 SECONDS)
+ var/box = new boxtype(owner.drop_location())
+ owner.forceMove(box)
+ owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
+
+/datum/action/item_action/agent_box/Grant(mob/grant_to)
+ . = ..()
+ if(owner)
+ RegisterSignal(owner, COMSIG_HUMAN_SUICIDE_ACT, PROC_REF(suicide_act))
+
+/datum/action/item_action/agent_box/Remove(mob/M)
+ if(owner)
+ UnregisterSignal(owner, COMSIG_HUMAN_SUICIDE_ACT)
+ return ..()
+
+/datum/action/item_action/agent_box/proc/suicide_act(datum/source)
+ SIGNAL_HANDLER
+
+ if(!istype(owner.loc, /obj/structure/closet/cardboard/agent))
+ return
+
+ var/obj/structure/closet/cardboard/agent/box = owner.loc
+ owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE)
+ INVOKE_ASYNC(box, TYPE_PROC_REF(/obj/structure/closet/, open))
+ owner.visible_message(span_suicide("[owner] falls out of [box]! It looks like [owner.p_they()] committed suicide!"))
+ INVOKE_ASYNC(owner, TYPE_PROC_REF(/atom/movable/, throw_at), get_turf(owner))
+ return OXYLOSS
diff --git a/code/datums/actions/items/toggles.dm b/code/datums/actions/items/toggles.dm
new file mode 100644
index 000000000000..cb7e5b42e3a8
--- /dev/null
+++ b/code/datums/actions/items/toggles.dm
@@ -0,0 +1,145 @@
+/datum/action/item_action/toggle
+
+/datum/action/item_action/toggle/New(Target)
+ ..()
+ var/obj/item/item_target = target
+ name = "Toggle [item_target.name]"
+
+/datum/action/item_action/toggle_light
+ name = "Toggle Light"
+
+/datum/action/item_action/toggle_hood
+ name = "Toggle Hood"
+
+/datum/action/item_action/toggle_firemode
+ name = "Toggle Firemode"
+
+/datum/action/item_action/toggle_bodycam
+ name = "Toggle Bodycamera"
+
+/datum/action/item_action/toggle_gunlight
+ name = "Toggle Gunlight"
+
+/datum/action/item_action/toggle_mode
+ name = "Toggle Mode"
+
+/datum/action/item_action/toggle_barrier_spread
+ name = "Toggle Barrier Spread"
+
+/datum/action/item_action/toggle_mister
+ name = "Toggle Mister"
+
+/datum/action/item_action/toggle_paddles
+ name = "Toggle Paddles"
+
+/datum/action/item_action/toggle_helmet_light
+ name = "Toggle Helmet Light"
+
+/datum/action/item_action/toggle_welding_screen
+ name = "Toggle Welding Screen"
+
+/datum/action/item_action/toggle_welding_screen/Trigger()
+ var/obj/item/clothing/head/hardhat/weldhat/H = target
+ if(istype(H))
+ H.toggle_welding_screen(owner)
+
+/datum/action/item_action/toggle_headphones
+ name = "Toggle Headphones"
+ desc = "UNTZ UNTZ UNTZ"
+
+/datum/action/item_action/toggle_headphones/Trigger()
+ var/obj/item/clothing/ears/headphones/H = target
+ if(istype(H))
+ H.toggle(owner)
+
+/datum/action/item_action/toggle_unfriendly_fire
+ name = "Toggle Friendly Fire \[ON\]"
+ desc = "Toggles if the club's blasts cause friendly fire."
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "vortex_ff_on"
+
+/datum/action/item_action/toggle_unfriendly_fire/Trigger()
+ if(..())
+ build_all_button_icons()
+
+/datum/action/item_action/toggle_unfriendly_fire/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force)
+ if(!istype(target, /obj/item/hierophant_club))
+ return
+ var/obj/item/hierophant_club/H = target
+ button_icon_state = H.friendly_fire_check ? "vortex_ff_off" : "vortex_ff_on"
+ name = H.friendly_fire_check ? "Toggle Friendly Fire \[OFF\]" : "Toggle Friendly Fire \[ON\]"
+ return ..()
+
+/datum/action/item_action/toggle_research_scanner
+ name = "Toggle Research Scanner"
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "scan_mode"
+ var/active = FALSE
+
+/datum/action/item_action/toggle_research_scanner/Trigger()
+ if(IsAvailable(feedback = FALSE))
+ active = !active
+ if(active)
+ owner.research_scanner++
+ else
+ owner.research_scanner--
+ to_chat(owner, span_notice("[target] research scanner has been [active ? "activated" : "deactivated"]."))
+ return 1
+
+/datum/action/item_action/toggle_research_scanner/Remove(mob/M)
+ if(owner && active)
+ owner.research_scanner--
+ active = FALSE
+ ..()
+
+/datum/action/item_action/jetpack_stabilization
+ name = "Toggle Jetpack Stabilization"
+
+/datum/action/item_action/jetpack_stabilization/IsAvailable(feedback = FALSE)
+ var/obj/item/tank/jetpack/linked_jetpack = target
+ if(!istype(linked_jetpack) || !linked_jetpack.on)
+ return FALSE
+ return ..()
+
+/datum/action/item_action/toggle_helmet_flashlight
+ name = "Toggle Helmet Flashlight"
+
+/datum/action/item_action/toggle_helmet_mode
+ name = "Toggle Helmet Mode"
+
+/datum/action/item_action/toggle_wings
+ name = "Toggle Wings"
+
+/datum/action/item_action/toggle_voice_box
+ name = "Toggle Voice Box"
+
+/datum/action/item_action/toggle_human_head
+ name = "Toggle Human Head"
+
+/datum/action/item_action/toggle_helmet
+ name = "Toggle Helmet"
+
+/datum/action/item_action/toggle_jetpack
+ name = "Toggle Jetpack"
+
+/datum/action/item_action/wheelys
+ name = "Toggle Wheely-Heel's Wheels"
+ desc = "Pops out or in your wheely-heel's wheels."
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "wheelys"
+
+/datum/action/item_action/kindle_kicks
+ name = "Activate Kindle Kicks"
+ desc = "Kick you feet together, activating the lights in your Kindle Kicks."
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "kindleKicks"
+
+/datum/action/item_action/storage_gather_mode
+ name = "Switch gathering mode"
+ desc = "Switches the gathering mode of a storage object."
+ background_icon = 'icons/mob/actions/actions_items.dmi'
+ background_icon_state = "storage_gather_switch"
+ overlay_icon_state = "bg_tech_border"
+
+/datum/action/item_action/equip_unequip_TED_Gun
+ name = "Equip/Unequip TED Gun"
diff --git a/code/datums/actions/items/vortex_recall.dm b/code/datums/actions/items/vortex_recall.dm
new file mode 100644
index 000000000000..c768340bf4d1
--- /dev/null
+++ b/code/datums/actions/items/vortex_recall.dm
@@ -0,0 +1,12 @@
+/datum/action/item_action/vortex_recall
+ name = "Vortex Recall"
+ desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time.
If the beacon is still attached, will detach it."
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "vortex_recall"
+
+/datum/action/item_action/vortex_recall/IsAvailable(feedback = FALSE)
+ if(istype(target, /obj/item/hierophant_club))
+ var/obj/item/hierophant_club/hierophant_club = target
+ if(hierophant_club.teleporting)
+ return FALSE
+ return ..()
diff --git a/code/datums/actions/mobs/language_menu.dm b/code/datums/actions/mobs/language_menu.dm
new file mode 100644
index 000000000000..e952d6f6b1e3
--- /dev/null
+++ b/code/datums/actions/mobs/language_menu.dm
@@ -0,0 +1,13 @@
+/datum/action/language_menu
+ name = "Language Menu"
+ desc = "Open the language menu to review your languages, their keys, and select your default language."
+ button_icon_state = "language_menu"
+ check_flags = NONE
+
+/datum/action/language_menu/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return
+
+ var/datum/language_holder/owner_holder = owner.get_language_holder()
+ owner_holder.open_language_menu(usr)
\ No newline at end of file
diff --git a/code/datums/actions/ninja.dm b/code/datums/actions/mobs/ninja.dm
similarity index 78%
rename from code/datums/actions/ninja.dm
rename to code/datums/actions/mobs/ninja.dm
index ab82de8aa399..275df224df20 100644
--- a/code/datums/actions/ninja.dm
+++ b/code/datums/actions/mobs/ninja.dm
@@ -5,47 +5,47 @@
name = "Smoke Bomb"
desc = "Blind your enemies momentarily with a well-placed smoke bomb."
button_icon_state = "smoke"
- icon_icon = 'icons/mob/actions/actions_spells.dmi'
+ button_icon = 'icons/mob/actions/actions_spells.dmi'
/datum/action/item_action/ninjaboost
check_flags = NONE
name = "Adrenaline Boost"
desc = "Inject a secret chemical that will counteract all movement-impairing effect."
button_icon_state = "repulse"
- icon_icon = 'icons/mob/actions/actions_spells.dmi'
+ button_icon = 'icons/mob/actions/actions_spells.dmi'
/datum/action/item_action/ninjapulse
name = "EM Burst (25E)"
desc = "Disable any nearby technology with an electro-magnetic pulse."
button_icon_state = "emp"
- icon_icon = 'icons/mob/actions/actions_spells.dmi'
+ button_icon = 'icons/mob/actions/actions_spells.dmi'
/datum/action/item_action/ninjastar
name = "Create Throwing Stars (3E)"
desc = "Creates some throwing stars"
button_icon_state = "throwingstar"
- icon_icon = 'icons/obj/weapons/misc.dmi'
+ button_icon = 'icons/obj/weapons/misc.dmi'
/datum/action/item_action/ninjanet
name = "Energy Net (25E)"
desc = "Captures a fallen opponent in a net of energy. Will teleport them to a holding facility after 30 seconds."
button_icon_state = "energynet"
- icon_icon = 'icons/effects/effects.dmi'
+ button_icon = 'icons/effects/effects.dmi'
/datum/action/item_action/ninja_sword_recall
name = "Recall Energy Katana (Variable Cost)"
desc = "Teleports the Energy Katana linked to this suit to its wearer, cost based on distance."
button_icon_state = "energy_katana"
- icon_icon = 'icons/obj/weapons/swords.dmi'
+ button_icon = 'icons/obj/weapons/swords.dmi'
/datum/action/item_action/ninja_stealth
name = "Toggle Stealth"
desc = "Toggles stealth mode on and off."
button_icon_state = "ninja_cloak"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
/datum/action/item_action/toggle_glove
name = "Toggle interaction"
desc = "Switch between normal interaction and drain mode."
button_icon_state = "s-ninjan"
- icon_icon = 'icons/obj/clothing/gloves.dmi'
+ button_icon = 'icons/obj/clothing/gloves.dmi'
diff --git a/code/datums/actions/mobs/small_sprite.dm b/code/datums/actions/mobs/small_sprite.dm
new file mode 100644
index 000000000000..9a64087387b2
--- /dev/null
+++ b/code/datums/actions/mobs/small_sprite.dm
@@ -0,0 +1,53 @@
+//Small sprites
+/datum/action/small_sprite
+ name = "Toggle Giant Sprite"
+ desc = "Others will always see you as giant"
+ button_icon = 'icons/mob/actions/actions_xeno.dmi'
+ button_icon_state = "smallqueen"
+ background_icon_state = "bg_alien"
+ overlay_icon_state = "bg_alien_border"
+ var/small = FALSE
+ var/small_icon
+ var/small_icon_state
+
+/datum/action/small_sprite/queen
+ small_icon = 'icons/mob/alien.dmi'
+ small_icon_state = "alienq"
+
+/datum/action/small_sprite/megafauna
+ button_icon = 'icons/mob/actions/actions_xeno.dmi'
+ small_icon = 'icons/mob/lavaland/lavaland_monsters.dmi'
+
+/datum/action/small_sprite/megafauna/drake
+ small_icon_state = "ash_whelp"
+
+/datum/action/small_sprite/megafauna/colossus
+ small_icon_state = "Basilisk_alert"
+
+/datum/action/small_sprite/megafauna/stalwart
+ small_icon_state = "Basilisk"
+
+/datum/action/small_sprite/megafauna/bubblegum
+ small_icon_state = "goliath2"
+
+/datum/action/small_sprite/megafauna/legion
+ small_icon_state = "dwarf_legion"
+
+/datum/action/small_sprite/space_dragon
+ small_icon = 'icons/mob/carp.dmi'
+ small_icon_state = "carp"
+ button_icon = 'icons/mob/carp.dmi'
+ button_icon_state = "carp"
+
+/datum/action/small_sprite/Trigger(trigger_flags)
+ ..()
+ if(!small)
+ var/image/I = image(icon = small_icon, icon_state = small_icon_state, loc = owner)
+ I.override = TRUE
+ I.pixel_x -= owner.pixel_x
+ I.pixel_y -= owner.pixel_y
+ owner.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic, "smallsprite", I, AA_TARGET_SEE_APPEARANCE | AA_MATCH_TARGET_OVERLAYS)
+ small = TRUE
+ else
+ owner.remove_alt_appearance("smallsprite")
+ small = FALSE
diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm
index a6314fcb039f..5597f8f573f5 100644
--- a/code/datums/ai_laws.dm
+++ b/code/datums/ai_laws.dm
@@ -449,9 +449,9 @@
inherent = templaws.inherent
if(3)
- pick_weighted_lawset()
+ pickweighted_lawset()
-/datum/ai_laws/proc/pick_weighted_lawset()
+/datum/ai_laws/proc/pickweighted_lawset()
var/datum/ai_laws/lawtype
var/list/law_weights = CONFIG_GET(keyed_list/law_weight)
while(!lawtype && law_weights.len)
diff --git a/code/datums/brain_damage/creepy_trauma.dm b/code/datums/brain_damage/creepy_trauma.dm
index e329436f52ac..d09720f1ac1f 100644
--- a/code/datums/brain_damage/creepy_trauma.dm
+++ b/code/datums/brain_damage/creepy_trauma.dm
@@ -78,9 +78,8 @@
if(prob(25)) // 25% chances to be nervous and stutter.
if(prob(50)) // 12.5% chance (previous check taken into account) of doing something suspicious.
addtimer(CALLBACK(src, PROC_REF(on_failed_social_interaction)), rand(1, 3) SECONDS)
- else if(!owner.stuttering)
+ if(owner.set_stutter_if_lower(3 SECONDS))
to_chat(owner, span_warning("Being near [obsession] makes you nervous and you begin to stutter..."))
- owner.stuttering = max(3, owner.stuttering)
/datum/brain_trauma/special/obsessed/on_hug(mob/living/hugger, mob/living/hugged)
if(hugged == obsession)
@@ -92,7 +91,7 @@
switch(rand(1, 100))
if(1 to 40)
INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), pick("blink", "blink_r"))
- owner.blur_eyes(10)
+ owner.blur_eyes(10 SECONDS)
to_chat(owner, span_userdanger("You sweat profusely and have a hard time focusing..."))
if(41 to 80)
INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "pale")
@@ -101,7 +100,7 @@
to_chat(owner, span_userdanger("You feel your heart lurching in your chest..."))
if(81 to 100)
INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "cough")
- owner.dizziness += 10
+ owner.adjust_dizzy(20 SECONDS)
owner.adjust_disgust(5)
to_chat(owner, span_userdanger("You gag and swallow a bit of bile..."))
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 1501c554b08c..9ca7d97f2f70 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -200,8 +200,9 @@
/datum/action/innate/imaginary_join
name = "Join"
desc = "Join your owner, following them from inside their mind."
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
background_icon_state = "bg_revenant"
+ overlay_icon_state = "bg_revenant_border"
button_icon_state = "join"
/datum/action/innate/imaginary_join/Activate()
@@ -211,8 +212,9 @@
/datum/action/innate/imaginary_hide
name = "Hide"
desc = "Hide yourself from your owner's sight."
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
background_icon_state = "bg_revenant"
+ overlay_icon_state = "bg_revenant_border"
button_icon_state = "hide"
/datum/action/innate/imaginary_hide/proc/update_status()
@@ -225,13 +227,32 @@
name = "Hide"
desc = "Hide yourself from your owner's sight."
button_icon_state = "hide"
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/imaginary_hide/Activate()
- var/mob/camera/imaginary_friend/I = owner
- I.hidden = !I.hidden
- I.Show()
- update_status()
+ var/mob/camera/imaginary_friend/fake_friend = owner
+ fake_friend.hidden = !fake_friend.hidden
+ fake_friend.Show()
+ build_all_button_icons(UPDATE_BUTTON_NAME|UPDATE_BUTTON_ICON)
+
+/datum/action/innate/imaginary_hide/update_button_name(atom/movable/screen/movable/action_button/button, force)
+ var/mob/camera/imaginary_friend/fake_friend = owner
+ if(fake_friend.hidden)
+ name = "Show"
+ desc = "Become visible to your owner."
+ else
+ name = "Hide"
+ desc = "Hide yourself from your owner's sight."
+ return ..()
+
+/datum/action/innate/imaginary_hide/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force = FALSE)
+ var/mob/camera/imaginary_friend/fake_friend = owner
+ if(fake_friend.hidden)
+ button_icon_state = "unhide"
+ else
+ button_icon_state = "hide"
+
+ return ..()
//down here is the trapped mind
//like imaginary friend but a lot less imagination and more like mind prison//
diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm
index 8a70d7f7da94..07f8f1a1304e 100644
--- a/code/datums/brain_damage/mild.dm
+++ b/code/datums/brain_damage/mild.dm
@@ -13,11 +13,16 @@
lose_text = ""
/datum/brain_trauma/mild/hallucinations/on_life()
- owner.hallucination = min(owner.hallucination + 10, 50)
+ if(owner.stat != CONSCIOUS || owner.IsSleeping() || owner.IsUnconscious())
+ return
+// if(HAS_TRAIT(owner, TRAIT_RDS_SUPPRESSED))
+// return
+
+ owner.adjust_hallucinations_up_to(10 SECONDS, 100 SECONDS)
..()
/datum/brain_trauma/mild/hallucinations/on_lose()
- owner.hallucination = 0
+ owner.remove_status_effect(/datum/status_effect/hallucination)
..()
/datum/brain_trauma/mild/reality_dissociation
@@ -31,13 +36,13 @@
/datum/brain_trauma/mild/reality_dissociation/on_life()
if(owner.reagents.has_reagent(/datum/reagent/toxin/mindbreaker, needs_metabolizing = TRUE))
- owner.hallucination = 0
+ owner.remove_status_effect(/datum/status_effect/hallucination)
else if(prob(2))
- owner.hallucination += rand(10, 25)
+ owner.adjust_hallucinations(rand(10, 25))
..()
/datum/brain_trauma/mild/reality_dissociation/on_lose()
- owner.hallucination = 0
+ owner.remove_status_effect(/datum/status_effect/hallucination)
..()
/datum/brain_trauma/mild/stuttering
@@ -48,11 +53,11 @@
lose_text = ""
/datum/brain_trauma/mild/stuttering/on_life()
- owner.stuttering = min(owner.stuttering + 5, 25)
+ owner.adjust_stutter_up_to(5 SECONDS, 50 SECONDS)
..()
/datum/brain_trauma/mild/stuttering/on_lose()
- owner.stuttering = 0
+ owner.remove_status_effect(/datum/status_effect/speech/stutter)
..()
/datum/brain_trauma/mild/dumbness
@@ -68,7 +73,7 @@
..()
/datum/brain_trauma/mild/dumbness/on_life()
- owner.derpspeech = min(owner.derpspeech + 5, 25)
+ owner.adjust_derpspeech_up_to(5 SECONDS, 50 SECONDS)
if(prob(3))
owner.emote("drool")
else if(owner.stat == CONSCIOUS && prob(3))
@@ -77,7 +82,7 @@
/datum/brain_trauma/mild/dumbness/on_lose()
REMOVE_TRAIT(owner, TRAIT_DUMB, TRAUMA_TRAIT)
- owner.derpspeech = 0
+ owner.remove_status_effect(/datum/status_effect/speech/stutter/derpspeech)
SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "dumb")
..()
@@ -109,12 +114,12 @@
if(1)
owner.vomit()
if(2,3)
- owner.dizziness += 10
+ owner.adjust_dizzy(20 SECONDS)
if(4,5)
- owner.confused += 10
+ owner.adjust_confusion(10 SECONDS)
owner.blur_eyes(10)
if(6 to 9)
- owner.slurring += 30
+ owner.adjust_slurring(1 MINUTES)
if(10)
to_chat(owner, span_notice("You forget for a moment what you were doing."))
owner.Stun(20)
@@ -238,13 +243,7 @@
return new_word + jointext(shuffled, "") + word[length(word)]
/datum/brain_trauma/mild/expressive_aphasia/handle_speech(datum/source, list/speech_args)
- if(ishuman(source))
- var/mob/living/carbon/human/H = source
- if(H.drunkenness > 10)
- return
-
var/message = speech_args[SPEECH_MESSAGE]
-
if(message)
var/list/message_split = splittext(message, " ")
var/list/new_message = list()
@@ -252,29 +251,27 @@
for(var/word in message_split)
var/suffix = ""
var/suffix_foundon = 0
- for(var/potential_suffix in list(".", ",", ";", "!", ":", "?"))
+ for(var/potential_suffix in list("." , "," , ";" , "!" , ":" , "?"))
suffix_foundon = findtext(word, potential_suffix, -length(potential_suffix))
if(suffix_foundon)
- suffix = copytext(word,suffix_foundon,length(word)+1)
+ suffix = potential_suffix
break
if(suffix_foundon)
word = copytext(word, 1, suffix_foundon)
-
word = html_decode(word)
- if((length(word) < 8) || (lowertext(word) in common_words) || is_simple(word))
+ if(lowertext(word) in common_words)
new_message += word + suffix
else
- var/new_word = ""
- if(prob(70))
- new_word += stutter(word)
- if(prob(40))
- new_word += pick(stutter(lowertext(word)), partial_shuffle(lowertext(word)) + suffix)
+ if(prob(30) && message_split.len > 2)
+ new_message += pick("uh","erm")
+ break
else
- new_word += partial_shuffle(word) + suffix
- new_message += new_word
- break
+ var/list/charlist = text2charlist(word)
+ charlist.len = round(charlist.len * 0.5, 1)
+ shuffle_inplace(charlist)
+ new_message += jointext(charlist, "") + suffix
message = jointext(new_message, " ")
diff --git a/code/datums/brain_damage/mrat.dm b/code/datums/brain_damage/mrat.dm
index 149529a81338..47aa4b7d87a3 100644
--- a/code/datums/brain_damage/mrat.dm
+++ b/code/datums/brain_damage/mrat.dm
@@ -119,7 +119,7 @@
/datum/action/innate/mrat_costume
name = "Change Appearance"
desc = "Shape your appearance to whatever you desire."
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
background_icon_state = "bg_revenant"
button_icon_state = "ninja_phase"
@@ -130,7 +130,7 @@
/datum/action/innate/mrat_leave
name = "Leave"
desc = "Leave and return to your ghost form."
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
background_icon_state = "bg_revenant"
button_icon_state = "beam_up"
diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm
index bd96771ed18f..54d705740cdb 100644
--- a/code/datums/brain_damage/phobia.dm
+++ b/code/datums/brain_damage/phobia.dm
@@ -117,22 +117,22 @@
if(1)
to_chat(owner, span_warning("You are paralyzed with fear!"))
owner.Stun(70)
- owner.Jitter(8)
+ owner.adjust_jitter(8 SECONDS)
if(2)
owner.emote("scream")
- owner.Jitter(5)
+ owner.adjust_jitter(5 SECONDS)
owner.say("AAAAH!!", forced = "phobia")
if(reason)
owner.pointed(reason)
if(3)
to_chat(owner, span_warning("You shut your eyes in terror!"))
- owner.Jitter(5)
+ owner.adjust_jitter(5 SECONDS)
owner.blind_eyes(10)
if(4)
- owner.dizziness += 10
- owner.confused += 10
- owner.Jitter(10)
- owner.stuttering += 10
+ owner.adjust_dizzy(10 SECONDS)
+ owner.adjust_confusion(10 SECONDS)
+ owner.adjust_jitter(10 SECONDS)
+ owner.adjust_stutter(10 SECONDS)
// Defined phobia types for badminry, not included in the RNG trauma pool to avoid diluting.
diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm
index fff5540bf02b..f57bdac83ed0 100644
--- a/code/datums/brain_damage/severe.dm
+++ b/code/datums/brain_damage/severe.dm
@@ -123,21 +123,24 @@
gain_text = ""
lose_text = ""
-/datum/brain_trauma/severe/narcolepsy/on_life()
- ..()
+/datum/brain_trauma/severe/narcolepsy/on_life(delta_time, times_fired)
if(owner.IsSleeping())
return
+
var/sleep_chance = 1
+ var/drowsy = !!owner.has_status_effect(/datum/status_effect/drowsiness)
if(owner.m_intent == MOVE_INTENT_RUN)
sleep_chance += 2
- if(owner.drowsyness)
+ if(drowsy)
sleep_chance += 3
- if(prob(sleep_chance))
+
+ if(DT_PROB(0.5 * sleep_chance, delta_time))
to_chat(owner, span_warning("You fall asleep."))
- owner.Sleeping(60)
- else if(!owner.drowsyness && prob(sleep_chance * 2))
+ owner.Sleeping(6 SECONDS)
+
+ else if(!drowsy && DT_PROB(sleep_chance, delta_time))
to_chat(owner, span_warning("You feel tired..."))
- owner.drowsyness += 10
+ owner.adjust_drowsiness(20 SECONDS)
/datum/brain_trauma/severe/monophobia
name = "Monophobia"
@@ -178,38 +181,33 @@
return
var/high_stress = (stress > 60) //things get psychosomatic from here on
- switch(rand(1,6))
+ switch(rand(1, 6))
if(1)
- if(!high_stress)
- to_chat(owner, span_warning("You feel sick..."))
- else
+ if(high_stress)
to_chat(owner, span_warning("You feel really sick at the thought of being alone!"))
+ else
+ to_chat(owner, span_warning("You feel sick..."))
addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living/carbon, vomit), high_stress), 50) //blood vomit if high stress
if(2)
- if(!high_stress)
- to_chat(owner, span_warning("You can't stop shaking..."))
- owner.dizziness += 20
- owner.confused += 20
- owner.Jitter(20)
- else
+ if(high_stress)
to_chat(owner, span_warning("You feel weak and scared! If only you weren't alone..."))
- owner.dizziness += 20
- owner.confused += 20
- owner.Jitter(20)
owner.adjustStaminaLoss(50)
+ else
+ to_chat(owner, span_warning("You can't stop shaking..."))
+
+ owner.adjust_dizzy(40 SECONDS)
+ owner.adjust_confusion(20 SECONDS)
+ owner.set_jitter_if_lower(40 SECONDS)
if(3, 4)
- if(!high_stress)
- to_chat(owner, span_warning("You feel really lonely..."))
- else
+ if(high_stress)
to_chat(owner, span_warning("You're going mad with loneliness!"))
- owner.hallucination += 30
+ owner.adjust_hallucinations(60 SECONDS)
+ else
+ to_chat(owner, span_warning("You feel really lonely..."))
if(5)
- if(!high_stress)
- to_chat(owner, span_warning("Your heart skips a beat."))
- owner.adjustOxyLoss(8)
- else
+ if(high_stress)
if(prob(15) && ishuman(owner))
var/mob/living/carbon/human/H = owner
H.set_heartattack(TRUE)
@@ -217,7 +215,12 @@
else
to_chat(owner, span_userdanger("You feel your heart lurching in your chest..."))
owner.adjustOxyLoss(8)
+ else
+ to_chat(owner, span_warning("Your heart skips a beat."))
+ owner.adjustOxyLoss(8)
+
else
+ //No effect
return
/datum/brain_trauma/severe/discoordination
diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm
index 1daf5e0821f8..335210ee86c3 100644
--- a/code/datums/brain_damage/split_personality.dm
+++ b/code/datums/brain_damage/split_personality.dm
@@ -19,12 +19,12 @@
/datum/brain_trauma/severe/split_personality/proc/make_backseats()
stranger_backseat = new(owner, src)
- var/obj/effect/proc_holder/spell/targeted/personality_commune/stranger_spell = new(src)
- stranger_backseat.AddSpell(stranger_spell)
+ var/datum/action/cooldown/spell/personality_commune/stranger_spell = new(src)
+ stranger_spell.Grant(stranger_backseat)
owner_backseat = new(owner, src)
- var/obj/effect/proc_holder/spell/targeted/personality_commune/owner_spell = new(src)
- owner_backseat.AddSpell(owner_spell)
+ var/datum/action/cooldown/spell/personality_commune/owner_spell = new(src)
+ owner_spell.Grant(owner_backseat)
/datum/brain_trauma/severe/split_personality/proc/get_ghost()
diff --git a/code/datums/components/action_item_overlay.dm b/code/datums/components/action_item_overlay.dm
new file mode 100644
index 000000000000..79230aff8ea1
--- /dev/null
+++ b/code/datums/components/action_item_overlay.dm
@@ -0,0 +1,79 @@
+/**
+ * Apply to an action to allow it to take an item
+ * and apply it as an overlay of the action button
+ */
+/datum/component/action_item_overlay
+ /// Weakref to what item the component uses to apply as an overlay.
+ var/datum/weakref/item_ref
+ /// Callback that dictates what item the component uses to apply as an overlay.
+ var/datum/callback/item_callback
+
+ /// The appearance of the item we've applied
+ var/mutable_appearance/item_appearance
+
+/datum/component/action_item_overlay/Initialize(atom/movable/item, datum/callback/item_callback)
+ if(!istype(parent, /datum/action))
+ return COMPONENT_INCOMPATIBLE
+
+ ASSERT(isnull(item) || istype(item))
+
+ if(!item && !item_callback)
+ stack_trace("[type] created without a reference item or an item callback - one or the other is required.")
+ return COMPONENT_INCOMPATIBLE
+
+ src.item_ref = WEAKREF(item)
+ src.item_callback = item_callback
+
+/datum/component/action_item_overlay/Destroy(force, silent)
+ item_ref = null
+ QDEL_NULL(item_callback)
+ item_appearance = null
+ return ..()
+
+/datum/component/action_item_overlay/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ACTION_OVERLAY_APPLY, PROC_REF(on_overlays_applied))
+
+ var/datum/action/parent_action = parent
+ parent_action.build_all_button_icons(UPDATE_BUTTON_OVERLAY)
+
+/datum/component/action_item_overlay/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_ACTION_OVERLAY_APPLY)
+
+ // If we're being unregistered / deleted but our parent is sticking around,
+ // force an overlay update to get rid of our item appearance
+ if(!QDELING(parent))
+ var/datum/action/parent_action = parent
+ parent_action.build_all_button_icons(UPDATE_BUTTON_OVERLAY)
+
+/// Signal proc for [COMSIG_ACTION_OVERLAY_APPLY], applies the item appearance if possible.
+/datum/component/action_item_overlay/proc/on_overlays_applied(datum/action/source, atom/movable/screen/movable/action_button/current_button, force)
+// SIGNAL_HANDLER , sinful but needed -> change when porting update_icon changes
+
+ // We're in the middle of being removed / deleted, remove our associated overlay
+ if(QDELING(src))
+ if(item_appearance)
+ current_button.cut_overlay(item_appearance)
+ item_appearance = null
+ return
+
+ var/atom/movable/muse = item_callback?.Invoke() || item_ref?.resolve()
+ if(!istype(muse))
+ if(item_appearance) // New item does not exist but we have an old appearance
+ current_button.cut_overlay(item_appearance)
+ item_appearance = null
+ return
+
+ if(item_appearance)
+ // For caching purposes, we will try not to update if we don't need to
+ if(!force && item_appearance.icon == muse.icon && item_appearance.icon_state == muse.icon_state)
+ return
+ current_button.cut_overlay(item_appearance)
+
+ var/mutable_appearance/muse_appearance = new(muse.appearance)
+ muse_appearance.plane = FLOAT_PLANE
+ muse_appearance.layer = FLOAT_LAYER
+ muse_appearance.pixel_x = 0
+ muse_appearance.pixel_y = 0
+
+ current_button.add_overlay(muse_appearance)
+ item_appearance = muse_appearance
diff --git a/code/datums/components/anti_magic.dm b/code/datums/components/anti_magic.dm
index 3cc720268038..b670ce60c978 100644
--- a/code/datums/components/anti_magic.dm
+++ b/code/datums/components/anti_magic.dm
@@ -45,5 +45,5 @@
if(charges <= 0)
expire?.Invoke(user)
qdel(src)
- return COMPONENT_BLOCK_MAGIC
+ return COMPONENT_MAGIC_BLOCKED
diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm
index 5741ea53442d..528b4e74c963 100644
--- a/code/datums/components/chasm.dm
+++ b/code/datums/components/chasm.dm
@@ -81,7 +81,7 @@
return FALSE
if(ishuman(AM))
var/mob/living/carbon/human/H = AM
- for(var/obj/item/wormhole_jaunter/J in H.GetAllContents())
+ for(var/obj/item/wormhole_jaunter/J in H.get_all_contents())
//To freak out any bystanders
H.visible_message(span_boldwarning("[H] falls into [parent]!"))
J.chasm_react(H)
@@ -220,7 +220,7 @@
var/list/fishing_contents = list()
for(var/turf/T in range(3, src.parent))
if(ischasm(T))
- fishing_contents += T.GetAllContents()
+ fishing_contents += T.get_all_contents()
if(!length(fishing_contents))
to_chat(user, span_warning("There's nothing here!"))
diff --git a/code/datums/components/curse_of_hunger.dm b/code/datums/components/curse_of_hunger.dm
new file mode 100644
index 000000000000..dfd36bddcd66
--- /dev/null
+++ b/code/datums/components/curse_of_hunger.dm
@@ -0,0 +1,159 @@
+///the point where you can notice the item is hungry on examine.
+#define HUNGER_THRESHOLD_WARNING 25
+///the point where the item has a chance to eat something on every tick. possibly you!
+#define HUNGER_THRESHOLD_TRY_EATING 50
+
+/**
+ * curse of hunger component; for very hungry items.
+ *
+ * Used as a rpgloot suffix and wizard spell!
+ */
+/datum/component/curse_of_hunger
+ ///whether to add dropdel to the item with curse of hunger, used for temporary curses like the wizard duffelbags
+ var/add_dropdel
+ ///items given the curse of hunger will not seek out someone else to latch onto until they are dropped for the first time.
+ var/awakened = FALSE
+ ///counts time passed since it ate food
+ var/hunger = 0
+ ///The bag's max "health". IE, how many times you need to poison it.
+ var/max_health = 2
+ ///The bag's current "health". IE, how many more times you need to poison it to stop it.
+ var/current_health = 2
+
+/datum/component/curse_of_hunger/Initialize(add_dropdel = FALSE, max_health = 2)
+ . = ..()
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+ src.add_dropdel = add_dropdel
+ src.max_health = max_health
+ src.current_health = max_health
+
+/datum/component/curse_of_hunger/RegisterWithParent()
+ . = ..()
+ var/obj/item/cursed_item = parent
+ RegisterSignal(cursed_item, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(cursed_item, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
+
+/datum/component/curse_of_hunger/UnregisterFromParent()
+ . = ..()
+ UnregisterSignal(parent, list(
+ COMSIG_PARENT_EXAMINE,
+ COMSIG_ITEM_EQUIPPED,
+ COMSIG_ITEM_DROPPED,
+ ))
+
+///signal called on parent being examined
+/datum/component/curse_of_hunger/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+ if(!awakened)
+ return //we should not reveal we are cursed until equipped
+ if(current_health < max_health)
+ examine_list += span_notice("[parent] looks sick from something it ate.")
+ if(hunger > HUNGER_THRESHOLD_WARNING)
+ examine_list += span_danger("[parent] hungers for something to eat...")
+
+///signal called from equipping parent
+/datum/component/curse_of_hunger/proc/on_equip(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+ var/obj/item/at_least_item = parent
+ // Items with no slot flags curse on pickup (because hand slot)
+ if(at_least_item.slot_flags && !(at_least_item.slot_flags & slot))
+ return
+ the_curse_begins(equipper)
+
+///signal called from dropping parent
+/datum/component/curse_of_hunger/proc/on_drop(datum/source, mob/dropper)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(the_curse_ends), dropper)
+
+/datum/component/curse_of_hunger/proc/the_curse_begins(mob/cursed)
+ var/obj/item/cursed_item = parent
+ awakened = TRUE
+ START_PROCESSING(SSobj, src)
+ ADD_TRAIT(cursed_item, TRAIT_NODROP, CURSED_ITEM_TRAIT(cursed_item.type))
+ ADD_TRAIT(cursed, TRAIT_CLUMSY, CURSED_ITEM_TRAIT(cursed_item.type))
+ ADD_TRAIT(cursed, TRAIT_PACIFISM, CURSED_ITEM_TRAIT(cursed_item.type))
+ if(add_dropdel)
+ cursed_item.item_flags |= DROPDEL
+
+ RegisterSignal(cursed_item, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
+
+/datum/component/curse_of_hunger/proc/the_curse_ends(mob/uncursed)
+ var/obj/item/cursed_item = parent
+ STOP_PROCESSING(SSobj, src)
+ REMOVE_TRAIT(cursed_item, TRAIT_NODROP, CURSED_ITEM_TRAIT(cursed_item.type))
+ REMOVE_TRAIT(uncursed, TRAIT_CLUMSY, CURSED_ITEM_TRAIT(cursed_item.type))
+ REMOVE_TRAIT(uncursed, TRAIT_PACIFISM, CURSED_ITEM_TRAIT(cursed_item.type))
+ //remove either one of the signals that could have called this proc
+ UnregisterSignal(cursed_item, COMSIG_ITEM_DROPPED)
+
+ var/turf/vomit_turf = get_turf(cursed_item)
+ playsound(vomit_turf, 'sound/effects/splat.ogg', 50, TRUE)
+ new /obj/effect/decal/cleanable/vomit(vomit_turf)
+
+ uncursed.dropItemToGround(cursed_item, force = TRUE)
+ if(!QDELING(cursed_item)) //gives a head start for the person to get away from the cursed item before it begins hunting again!
+ addtimer(CALLBACK(src, PROC_REF(seek_new_target)), 10 SECONDS)
+
+///proc called after a timer to awaken the AI in the cursed item if it doesn't have a target already.
+/datum/component/curse_of_hunger/proc/seek_new_target()
+ var/obj/item/cursed_item = parent
+ if(iscarbon(cursed_item.loc))
+ return
+ else if(!isturf(cursed_item.loc))
+ cursed_item.forceMove(get_turf(cursed_item))
+ //only taking the most reasonable slot is fine since it unequips what is there to equip itself.
+// cursed_item.AddElement(/datum/element/cursed, cursed_item.slot_equipment_priority[1])
+ cursed_item.visible_message(span_warning("[cursed_item] begins to move on [cursed_item.p_their()] own..."))
+
+/datum/component/curse_of_hunger/process(delta_time)
+ var/obj/item/cursed_item = parent
+ var/mob/living/carbon/cursed = cursed_item.loc
+ ///check hp
+ if(current_health <= 0)
+ the_curse_ends(cursed)
+ return
+
+ hunger += delta_time
+ if((hunger <= HUNGER_THRESHOLD_TRY_EATING) || prob(80))
+ return
+
+ playsound(cursed_item, 'sound/items/eatfood.ogg', 20, TRUE)
+ hunger = 0
+
+ //check hungry enough to eat something!
+ for(var/obj/item/food in cursed_item.contents + cursed.contents)
+ if(!istype(food, /obj/item/reagent_containers/food))
+ continue
+ food.forceMove(cursed.loc)
+ ///poisoned food damages it
+ if(locate(/datum/reagent/toxin) in food.reagents.reagent_list)
+ var/sick_word = pick("queasy", "sick", "iffy", "unwell")
+ cursed.visible_message(
+ span_notice("[cursed_item] eats something from [cursed], and looks [sick_word] afterwards!"),
+ span_notice("[cursed_item] eats your [food.name] to sate [cursed_item.p_their()] hunger, and looks [sick_word] afterwards!"),
+ )
+ current_health--
+ else
+ cursed.visible_message(
+ span_warning("[cursed_item] eats something from [cursed] to sate [cursed_item.p_their()] hunger."),
+ span_warning("[cursed_item] eats your [food.name] to sate [cursed_item.p_their()] hunger."),
+ )
+ cursed.temporarilyRemoveItemFromInventory(food, force = TRUE)
+ qdel(food)
+ return
+
+ ///no food found, but you're dead: it bites you slightly, and doesn't regain health.
+ if(cursed.stat == DEAD)
+ cursed.visible_message(span_danger("[cursed_item] nibbles on [cursed]."), span_userdanger("[cursed_item] nibbles on you!"))
+ cursed.apply_damage(10, BRUTE, BODY_ZONE_CHEST)
+ return
+
+ ///no food found: it bites you and regains some health.
+ cursed.visible_message(span_danger("[cursed_item] bites [cursed]!"), span_userdanger("[cursed_item] bites you to sate [cursed_item.p_their()] hunger!"))
+ cursed.apply_damage(60, BRUTE, BODY_ZONE_CHEST, wound_bonus = -20, bare_wound_bonus = 20)
+ current_health = min(current_health + 1, max_health)
+
+#undef HUNGER_THRESHOLD_WARNING
+#undef HUNGER_THRESHOLD_TRY_EATING
diff --git a/code/datums/components/igniter.dm b/code/datums/components/igniter.dm
index f8e7b4b38322..882300a1cd0f 100644
--- a/code/datums/components/igniter.dm
+++ b/code/datums/components/igniter.dm
@@ -33,4 +33,4 @@
if(isliving(target))
var/mob/living/L = target
L.adjust_fire_stacks(fire_stacks)
- L.IgniteMob()
\ No newline at end of file
+ L.ignite_mob()
diff --git a/code/datums/components/mind_linker.dm b/code/datums/components/mind_linker.dm
new file mode 100644
index 000000000000..e1c871c1e12e
--- /dev/null
+++ b/code/datums/components/mind_linker.dm
@@ -0,0 +1,214 @@
+/**
+ * # Mind Linker
+ *
+ * A component that handles linking multiple player's minds
+ * into one network which allows them to talk directly to one another.
+ * Like telepathy but for multiple people at once!
+ */
+/datum/component/mind_linker
+ /// The name of our network, displayed to all users.
+ var/network_name = "Mind Link"
+ /// The color of the network when talking in chat
+ var/chat_color
+ /// The message sent to someone when linked up.
+ var/link_message
+ /// The message sent to someone when unlinked.
+ var/unlink_message
+ /// A list of all signals that will call qdel() on our component if triggered. Optional.
+ var/list/signals_which_destroy_us
+ /// A callback invoked after an unlink is done. Optional.
+ var/datum/callback/post_unlink_callback
+ /// The icon file given to the speech action handed out.
+ var/speech_action_icon = 'icons/mob/actions/actions_slime.dmi'
+ /// The icon state applied to the speech action handed out.
+ var/speech_action_icon_state = "link_speech"
+ /// The icon background for the speech action handed out.
+ var/speech_action_background_icon_state = "bg_alien"
+ /// The master's linking action, which allows them to link people to the network.
+ var/datum/action/linker_action
+ /// The master's speech action. The owner of the link shouldn't lose this as long as the link remains.
+ var/datum/action/innate/linked_speech/master_speech
+ /// An assoc list of [mob/living]s to [datum/action/innate/linked_speech]s. All the mobs that are linked to our network.
+ var/list/mob/living/linked_mobs = list()
+
+/datum/component/mind_linker/Initialize(
+ network_name = "Mind Link",
+ chat_color = "#008CA2",
+ linker_action_path,
+ link_message,
+ unlink_message,
+ signals_which_destroy_us,
+ datum/callback/post_unlink_callback,
+ speech_action_icon = 'icons/mob/actions/actions_slime.dmi',
+ speech_action_icon_state = "link_speech",
+ speech_action_background_icon_state = "bg_alien",
+ )
+
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ var/mob/living/owner = parent
+
+ src.network_name = network_name
+ src.chat_color = chat_color
+ src.link_message = link_message || "You are now connected to [owner.real_name]'s [network_name]."
+ src.unlink_message = unlink_message || "You are no longer connected to [owner.real_name]'s [network_name]."
+
+ if(islist(signals_which_destroy_us))
+ src.signals_which_destroy_us = signals_which_destroy_us
+ if(post_unlink_callback)
+ src.post_unlink_callback = post_unlink_callback
+
+ src.speech_action_icon = speech_action_icon
+ src.speech_action_icon_state = speech_action_icon_state
+ src.speech_action_background_icon_state = speech_action_background_icon_state
+
+ if(ispath(linker_action_path))
+ linker_action = new linker_action_path(src)
+ linker_action.Grant(owner)
+ else
+ stack_trace("[type] was created without a valid linker_action_path. No one will be able to link to it.")
+
+ master_speech = new(src)
+ master_speech.Grant(owner)
+
+ to_chat(owner, span_boldnotice("You establish a [network_name], allowing you to link minds to communicate telepathically."))
+
+/datum/component/mind_linker/Destroy(force, silent)
+ for(var/mob/living/remaining_mob as anything in linked_mobs)
+ unlink_mob(remaining_mob)
+ linked_mobs.Cut()
+ QDEL_NULL(linker_action)
+ QDEL_NULL(master_speech)
+ QDEL_NULL(post_unlink_callback)
+ return ..()
+
+/datum/component/mind_linker/RegisterWithParent()
+ if(signals_which_destroy_us)
+ RegisterSignals(parent, signals_which_destroy_us, PROC_REF(destroy_link))
+
+/datum/component/mind_linker/UnregisterFromParent()
+ if(signals_which_destroy_us)
+ UnregisterSignal(parent, signals_which_destroy_us)
+
+/**
+ * Attempts to link [to_link] to our network, giving them a speech action.
+ *
+ * Returns TRUE if successful, FALSE otherwise
+ */
+/datum/component/mind_linker/proc/link_mob(mob/living/to_link)
+ if(QDELETED(to_link) || to_link.stat == DEAD)
+ return FALSE
+ if(HAS_TRAIT(to_link, TRAIT_MINDSHIELD)) // Mindshield implant - no dice
+ return FALSE
+ if(to_link.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0))
+ return FALSE
+ if(linked_mobs[to_link])
+ return FALSE
+
+ var/mob/living/owner = parent
+ if(to_link == owner)
+ return FALSE
+
+ to_chat(to_link, span_notice(link_message))
+ to_chat(owner, span_notice("You connect [to_link]'s mind to your [network_name]."))
+
+ for(var/mob/living/other_link as anything in linked_mobs)
+ to_chat(other_link, span_notice("You feel a new presence within [owner.real_name]'s [network_name]."))
+
+ var/datum/action/innate/linked_speech/new_link = new(src)
+ new_link.Grant(to_link)
+
+ linked_mobs[to_link] = new_link
+ RegisterSignals(to_link, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MINDSHIELD_IMPLANTED), PROC_REF(unlink_mob))
+
+ return TRUE
+
+/**
+ * Unlinks [to_unlink] from our network, deleting their speech action
+ * and cleaning up anything involved.
+ *
+ * Also invokes post_unlink_callback, if supplied.
+ */
+/datum/component/mind_linker/proc/unlink_mob(mob/living/to_unlink)
+ SIGNAL_HANDLER
+
+ if(!linked_mobs[to_unlink])
+ return
+
+ to_chat(to_unlink, span_warning(unlink_message))
+ INVOKE_ASYNC(post_unlink_callback, TYPE_PROC_REF(/datum/callback/, Invoke), to_unlink)
+
+ UnregisterSignal(to_unlink, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MINDSHIELD_IMPLANTED))
+
+ var/datum/action/innate/linked_speech/old_link = linked_mobs[to_unlink]
+ linked_mobs -= to_unlink
+ qdel(old_link)
+
+ var/mob/living/owner = parent
+
+ to_chat(owner, span_warning("You feel someone disconnect from your [network_name]."))
+ for(var/mob/living/other_link as anything in linked_mobs)
+ to_chat(other_link, span_warning("You feel a pressence disappear from [owner.real_name]'s [network_name]."))
+
+/**
+ * Signal proc sent from any signals given to us initialize.
+ * Destroys our component and unlinks everyone.
+ */
+/datum/component/mind_linker/proc/destroy_link(datum/source)
+ SIGNAL_HANDLER
+
+ if(isliving(source))
+ var/mob/living/owner = source
+ to_chat(owner, span_boldwarning("Your [network_name] breaks!"))
+
+ qdel(src)
+
+/datum/action/innate/linked_speech
+ name = "Mind Link Speech"
+ desc = "Send a psychic message to everyone connected to your Link."
+ button_icon_state = "link_speech"
+ button_icon = 'icons/mob/actions/actions_slime.dmi'
+ background_icon_state = "bg_alien"
+ overlay_icon_state = "bg_alien_border"
+
+/datum/action/innate/linked_speech/New(Target)
+ . = ..()
+ if(!istype(Target, /datum/component/mind_linker))
+ stack_trace("[name] ([type]) was instantiated on a non-mind_linker target, this doesn't work.")
+ qdel(src)
+ return
+
+ var/datum/component/mind_linker/linker = Target
+ name = "[linker.network_name] Speech"
+ desc = "Send a psychic message to everyone connected to your [linker.network_name]."
+ background_icon = linker.speech_action_icon
+ button_icon_state = linker.speech_action_icon_state
+ background_icon_state = linker.speech_action_background_icon_state
+
+/datum/action/innate/linked_speech/IsAvailable(feedback = FALSE)
+ return ..() && (owner.stat != DEAD)
+
+/datum/action/innate/linked_speech/Activate()
+
+ var/datum/component/mind_linker/linker = target
+ var/mob/living/linker_parent = linker.parent
+
+ var/message = sanitize(tgui_input_text(owner, "Enter a message to transmit.", "[linker.network_name] Telepathy"))
+ if(!message || QDELETED(src) || QDELETED(owner) || owner.stat == DEAD)
+ return
+
+ if(QDELETED(linker))
+ to_chat(owner, span_warning("The link seems to have been severed."))
+ return
+
+ var/formatted_message = "\[[linker_parent.real_name]'s [linker.network_name]\] [owner]: [message]"
+ log_directed_talk(owner, linker_parent, message, LOG_SAY, "mind link ([linker.network_name])")
+
+ var/list/all_who_can_hear = assoc_to_keys(linker.linked_mobs) + linker_parent
+
+ for(var/mob/living/recipient as anything in all_who_can_hear)
+ to_chat(recipient, formatted_message)
+
+ for(var/mob/recipient as anything in GLOB.dead_mob_list)
+ to_chat(recipient, "[FOLLOW_LINK(recipient, owner)] [formatted_message]")
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index 2f4eb1f370a9..ec5cc7d9b7c5 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -39,11 +39,11 @@
msg += span_notice("My mental status: ") //Long term
switch(sanity)
if(SANITY_GREAT to INFINITY)
- msg += "My mind feels like a temple!\n"
+ msg += "[span_nicegreen("My mind feels like a temple!")]\n"
if(SANITY_NEUTRAL to SANITY_GREAT)
- msg += "I have been feeling great lately!\n"
+ msg += "[span_nicegreen("I have been feeling great lately!")]\n"
if(SANITY_DISTURBED to SANITY_NEUTRAL)
- msg += "I have felt quite decent lately.\n"
+ msg += "[span_nicegreen("I have felt quite decent lately.")]\n"
if(SANITY_UNSTABLE to SANITY_DISTURBED)
msg += "[span_warning("I'm feeling a little bit unhinged...")]\n"
if(SANITY_CRAZY to SANITY_UNSTABLE)
@@ -54,23 +54,23 @@
msg += span_notice("My current mood: ") //Short term
switch(mood_level)
if(1)
- msg += "I wish I was dead!\n"
+ msg += "[span_boldwarning("I wish I was dead!")]\n"
if(2)
- msg += "I feel terrible...\n"
+ msg += "[span_boldwarning("I feel terrible...")]\n"
if(3)
- msg += "I feel very upset.\n"
+ msg += "[span_boldwarning("I feel very upset.")]\n"
if(4)
- msg += "I'm a bit sad.\n"
+ msg += "[span_boldwarning("I'm a bit sad.")]\n"
if(5)
- msg += "I'm alright.\n"
+ msg += "[span_nicegreen("I'm alright.")]\n"
if(6)
- msg += "I feel pretty okay.\n"
+ msg += "[span_nicegreen("I feel pretty okay.")]\n"
if(7)
- msg += "I feel pretty good.\n"
+ msg += "[span_nicegreen("I feel pretty good.")]\n"
if(8)
- msg += "I feel amazing!\n"
+ msg += "[span_nicegreen("II feel amazing!")]\n"
if(9)
- msg += "I love life!\n"
+ msg += "[span_nicegreen("I love life!")]\n"
msg += span_notice("Moodlets:\n")//All moodlets
if(mood_events.len)
@@ -78,7 +78,7 @@
var/datum/mood_event/event = mood_events[i]
msg += event.description
else
- msg += "I don't have much of a reaction to anything right now.\n"
+ msg += "[span_nicegreen("I don't have much of a reaction to anything right now.")]\n"
to_chat(user || parent, examine_block(msg))
/datum/component/mood/proc/update_mood() //Called whenever a mood event is added or removed
@@ -139,9 +139,14 @@
highest_absolute_mood = absmood
if(!conflicting_moodies.len) //no special icons- go to the normal icon states
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(/datum/antagonist/bloodsucker) //bloodsucker edit
if(sanity < 25)
screen_obj.icon_state = "mood_insane"
+ if(IS_BLOODSUCKER(owner) && bloodsuckerdatum.my_clan?.get_clan() == CLAN_TOREADOR)
+ screen_obj.add_overlay("teeth_insane")
else
+ if(IS_BLOODSUCKER(owner) && bloodsuckerdatum.my_clan?.get_clan() == CLAN_TOREADOR)
+ screen_obj.add_overlay("teeth[mood_level]")
screen_obj.icon_state = "mood[mood_level]"
screen_obj_sanity.icon_state = "sanity[sanity_level]"
return
diff --git a/code/datums/components/phylactery.dm b/code/datums/components/phylactery.dm
new file mode 100644
index 000000000000..1ca32c2eed67
--- /dev/null
+++ b/code/datums/components/phylactery.dm
@@ -0,0 +1,219 @@
+/**
+ * ## Phylactery component
+ *
+ * Used for lichtom to turn (almost) any object into a phylactery
+ * A mob linked to a phylactery will repeatedly revive on death.
+ */
+/datum/component/phylactery
+ // Set in initialize.
+ /// The mind of the lich who is linked to this phylactery.
+ var/datum/mind/lich_mind
+ /// The respawn timer of the phylactery.
+ var/base_respawn_time = 3 MINUTES
+ /// How much time is added on to the respawn time per revival.
+ var/time_per_resurrection = 0
+ /// How much stun (paralyze) is caused on respawn per revival.
+ var/stun_per_resurrection = 20 SECONDS
+ /// The color of the phylactery itself. Applied on creation.
+ var/phylactery_color = COLOR_VERY_DARK_LIME_GREEN
+
+ // Internal vars.
+ /// The number of ressurections that have occured from this phylactery.
+ var/num_resurrections = 0
+ /// A timerid to the current revival timer.
+ var/revive_timer
+
+/datum/component/phylactery/Initialize(
+ datum/mind/lich_mind,
+ base_respawn_time = 3 MINUTES,
+ time_per_resurrection = 0 SECONDS,
+ stun_per_resurrection = 20 SECONDS,
+ phylactery_color = COLOR_VERY_DARK_LIME_GREEN,
+)
+ if(!isobj(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ if(isnull(lich_mind))
+ stack_trace("A [type] was created with no target lich mind!")
+ return COMPONENT_INCOMPATIBLE
+
+ src.lich_mind = lich_mind
+ src.base_respawn_time = base_respawn_time
+ src.time_per_resurrection = time_per_resurrection
+ src.stun_per_resurrection = stun_per_resurrection
+ src.phylactery_color = phylactery_color
+
+ RegisterSignal(lich_mind, COMSIG_PARENT_QDELETING, PROC_REF(on_lich_mind_lost))
+ RegisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH, PROC_REF(check_if_lich_died))
+
+ var/obj/obj_parent = parent
+ GLOB.poi_list |= parent //CHANGE TO SSpoints_of_interest WHEN POSSIBLE
+ obj_parent.name = "ensouled [obj_parent.name]"
+ obj_parent.add_atom_colour(phylactery_color, ADMIN_COLOUR_PRIORITY)
+ obj_parent.AddComponent(/datum/component/stationloving, FALSE, TRUE)
+
+ RegisterSignal(obj_parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+
+/datum/component/phylactery/Destroy()
+ var/obj/obj_parent = parent
+ GLOB.poi_list -= parent
+ obj_parent.name = initial(obj_parent.name)
+ obj_parent.remove_atom_colour(ADMIN_COLOUR_PRIORITY, phylactery_color)
+ // Stationloving items should really never be made a phylactery so I feel safe in doing this
+ qdel(obj_parent.GetComponent(/datum/component/stationloving))
+
+ UnregisterSignal(obj_parent, COMSIG_PARENT_EXAMINE)
+ UnregisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH)
+ // Sweep up any revive signals left on the mind's current
+ UnregisterSignal(lich_mind.current, COMSIG_LIVING_REVIVE)
+
+ lich_mind = null
+ return ..()
+
+/**
+ * Signal proc for [COMSIG_PARENT_EXAMINE].
+ *
+ * Gives some flavor for the phylactery on examine.
+ */
+/datum/component/phylactery/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ if(iswizard(user) || isobserver(user))
+ if(user.mind == lich_mind)
+ var/time_to_revive = base_respawn_time + (num_resurrections * time_per_resurrection)
+ examine_list += span_green("Your phylactery. The next time you meet an untimely demise, \
+ you will revive at this object in [time_to_revive / 10 / 60] minute\s.")
+ else
+ examine_list += span_green("A lich's phylactery. This one belongs to [lich_mind].")
+
+ if(num_resurrections > 0)
+ examine_list += span_green("There's [num_resurrections] notches in the side of it.")
+
+ else
+ examine_list += span_green("A terrible aura surrounds this item. Its very existence is offensive to life itself...")
+
+/**
+ * Signal proc for [COMSIG_PARENT_QDELETING] registered on the lich's mind.
+ *
+ * Minds shouldn't be getting deleted but if for some ungodly reason
+ * the lich'd mind is deleted our component should go with it, as
+ * we don't have a reason to exist anymore.
+ */
+/datum/component/phylactery/proc/on_lich_mind_lost(datum/source)
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+/**
+ * Signal proc for [COMSIG_GLOB_MOB_DEATH].
+ *
+ * If the mob containing our lich's mind is killed,
+ * we can initiate the revival process.
+ *
+ * We use the global mob death signal here,
+ * instead of registering the normal death signal,
+ * as it's entirely possible the wizard mindswaps
+ * or is gibbed or something wacky happens, and
+ * we need to make sure WHOEVER has our mind is dead
+ */
+/datum/component/phylactery/proc/check_if_lich_died(datum/source, mob/living/died, gibbed)
+ SIGNAL_HANDLER
+
+ if(!died.mind)
+ return
+
+ if(died.mind != lich_mind)
+ return
+
+ // If we aren't gibbed, we need to check if the lich is
+ // revived at some point between returning
+ if(!gibbed)
+ RegisterSignal(died, COMSIG_LIVING_REVIVE, PROC_REF(stop_timer))
+
+ // Start revival
+ var/time_to_revive = base_respawn_time + (num_resurrections * time_per_resurrection)
+ revive_timer = addtimer(CALLBACK(src, PROC_REF(revive_lich), died), time_to_revive, TIMER_UNIQUE|TIMER_STOPPABLE)
+ to_chat(died, span_green("You feel your soul being dragged back to this world... \
+ you will revive at your phylactery in [time_to_revive / 10 / 60] minute\s."))
+
+/**
+ * Signal proc for [COMSIG_LIVING_REVIVE].
+ *
+ * If our lich's mob is revived at some point before returning, stop the timer
+ */
+/datum/component/phylactery/proc/stop_timer(mob/living/source, full_heal_flags)
+ SIGNAL_HANDLER
+
+ deltimer(revive_timer)
+ revive_timer = null
+
+ UnregisterSignal(source, COMSIG_LIVING_REVIVE)
+
+/**
+ * Actually undergo the process of reviving the lich at the site of the phylacery.
+ *
+ * Arguments
+ * * corpse - optional, the old body of the lich. Can be QDELETED or null.
+ */
+/datum/component/phylactery/proc/revive_lich(mob/living/corpse)
+ // If we have a current, and it's not dead, don't yoink their mind
+ // But if we don't have a current (body destroyed) move on like normal
+ if(lich_mind.current && lich_mind.current.stat != DEAD)
+ CRASH("[type] - revive_lich was called when the lich's mind had a current mob that wasn't dead.")
+
+ var/turf/parent_turf = get_turf(parent)
+ if(!istype(parent_turf))
+ CRASH("[type] - revive_lich was called when the phylactery was in an invalid location (nullspace?) (was in: [parent_turf]).")
+
+ revive_timer = null
+ var/mob/living/carbon/human/lich = new(parent_turf)
+ ADD_TRAIT(lich, TRAIT_NO_SOUL, LICH_TRAIT)
+
+ var/obj/item/organ/brain/new_lich_brain = lich.getorganslot(ORGAN_SLOT_BRAIN)
+ if(new_lich_brain) // Prevent MMI cheese
+ new_lich_brain.organ_flags &= ~ORGAN_VITAL
+ new_lich_brain.decoy_override = TRUE
+
+ // Give them some duds
+ lich.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(lich), ITEM_SLOT_FEET)
+ lich.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(lich), ITEM_SLOT_ICLOTHING)
+ lich.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(lich), ITEM_SLOT_OCLOTHING)
+ lich.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(lich), ITEM_SLOT_HEAD)
+
+ // Fix their name
+ lich.dna.real_name = lich_mind.name
+ lich.real_name = lich_mind.name
+ // Slap the lich mind in and get their ghost
+ lich_mind.transfer_to(lich)
+ lich_mind.grab_ghost(force = TRUE)
+ // Make sure they're a spooky skeleton, and their DNA is right
+ lich.set_species(/datum/species/skeleton)
+ lich.dna.generate_unique_enzymes()
+
+ to_chat(lich, span_green("Your bones clatter and shudder as you are pulled back into this world!"))
+ num_resurrections++
+ lich.Paralyze(stun_per_resurrection * num_resurrections)
+
+ if(!QDELETED(corpse))
+ UnregisterSignal(corpse, COMSIG_LIVING_REVIVE)
+
+ if(iscarbon(corpse))
+ var/mob/living/carbon/carbon_body = corpse
+ for(var/obj/item/organ/to_drop as anything in carbon_body.internal_organs)
+ // Skip the brain - it can disappear, we don't need it anymore
+ if(isbrain(to_drop))
+ continue
+
+ // For the rest, drop all the organs onto the floor (for style)
+ to_drop.Remove(carbon_body)
+ to_drop.forceMove(corpse.drop_location())
+
+ var/turf/body_turf = get_turf(corpse)
+ var/wheres_wizdo = dir2text(get_dir(body_turf, parent_turf))
+ if(wheres_wizdo)
+ corpse.visible_message(span_warning("Suddenly, [corpse.name]'s corpse falls to pieces! You see a strange energy rise from the remains, and speed off towards the [wheres_wizdo]!"))
+ body_turf.Beam(parent_turf, icon_state = "lichbeam", time = 1 SECONDS * (num_resurrections + 1))
+
+ corpse.dust(drop_items = TRUE)
+
+ return TRUE
diff --git a/code/datums/components/spooky.dm b/code/datums/components/spooky.dm
index 2a472cc09a7b..f7c82b74e41c 100644
--- a/code/datums/components/spooky.dm
+++ b/code/datums/components/spooky.dm
@@ -9,8 +9,8 @@
var/mob/living/carbon/human/U = user
if(!istype(U.dna.species, /datum/species/skeleton))
U.adjustStaminaLoss(35) //Extra Damage
- U.Jitter(35)
- U.stuttering = 20
+ U.adjust_jitter(35 SECONDS)
+ U.adjust_stutter(20 SECONDS)
if(U.getStaminaLoss() > 95)
to_chat(U, "Your ears weren't meant for this spectral sound.")
spectral_change(U)
@@ -23,20 +23,20 @@
if(istype(H.dna.species, /datum/species/zombie))
H.adjustStaminaLoss(25)
H.Paralyze(15) //zombies can't resist the doot
- C.Jitter(35)
- C.stuttering = 20
+ C.adjust_jitter(35 SECONDS)
+ C.adjust_stutter(20 SECONDS)
if((!istype(H.dna.species, /datum/species/skeleton)) && (!istype(H.dna.species, /datum/species/golem)) && (!istype(H.dna.species, /datum/species/android)) && (!istype(H.dna.species, /datum/species/jelly)))
C.adjustStaminaLoss(25) //boneless humanoids don't lose the will to live
to_chat(C, "DOOT")
spectral_change(H)
else //the sound will spook monkeys.
- C.Jitter(15)
- C.stuttering = 20
+ C.adjust_jitter(15 SECONDS)
+ C.adjust_stutter(20 SECONDS)
/datum/component/spooky/proc/spectral_change(mob/living/carbon/human/H, mob/user)
if((H.getStaminaLoss() > 95) && (!istype(H.dna.species, /datum/species/skeleton)) && (!istype(H.dna.species, /datum/species/golem)) && (!istype(H.dna.species, /datum/species/android)) && (!istype(H.dna.species, /datum/species/jelly)))
- H.Paralyze(20)
+ H.Paralyze(2 SECONDS)
H.set_species(/datum/species/skeleton)
H.visible_message(span_warning("[H] has given up on life as a mortal."))
var/T = get_turf(H)
diff --git a/code/datums/components/stationloving.dm b/code/datums/components/stationloving.dm
index 79d94a067463..e3a4205d19c7 100644
--- a/code/datums/components/stationloving.dm
+++ b/code/datums/components/stationloving.dm
@@ -47,7 +47,7 @@
if(inform_admins)
message_admins("[parent] has been moved out of bounds in [ADMIN_VERBOSEJMP(currentturf)]. Moving it to [ADMIN_VERBOSEJMP(targetturf)].")
-/datum/component/stationloving/proc/check_soul_imbue()
+/datum/component/stationloving/proc/check_soul_imbue(datum/source)
return disallow_soul_imbue
/datum/component/stationloving/proc/in_bounds()
diff --git a/code/datums/components/storage/concrete/bag_of_holding.dm b/code/datums/components/storage/concrete/bag_of_holding.dm
index aab64f98e376..d549c0bbcaea 100644
--- a/code/datums/components/storage/concrete/bag_of_holding.dm
+++ b/code/datums/components/storage/concrete/bag_of_holding.dm
@@ -2,7 +2,7 @@
var/atom/A = parent
if(A == W) //don't put yourself into yourself.
return
- var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.GetAllContents(), typecacheof(/obj/item/storage/backpack/holding))
+ var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.get_all_contents(), typecacheof(/obj/item/storage/backpack/holding))
matching -= A
if(istype(W, /obj/item/storage/backpack/holding) || matching.len)
INVOKE_ASYNC(src, PROC_REF(recursive_insertion), W, user)
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index d7a3779ea3d8..14dbd5abd8d9 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -50,7 +50,7 @@
var/attack_hand_interact = TRUE //interact on attack hand.
var/quickdraw = FALSE //altclick interact
- var/datum/action/item_action/storage_gather_mode/modeswitch_action
+ var/datum/weakref/modeswitch_action_ref
//Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future.
var/screen_max_columns = 7 //These two determine maximum screen sizes.
@@ -157,17 +157,17 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
return "\n\t[span_notice("[desc.Join("\n\t")]")]"
/datum/component/storage/proc/update_actions()
- QDEL_NULL(modeswitch_action)
if(!isitem(parent) || !allow_quick_gather)
+ QDEL_NULL(modeswitch_action_ref)
return
- var/obj/item/I = parent
- modeswitch_action = new(I)
+ var/datum/action/existing = modeswitch_action_ref?.resolve()
+ if(!QDELETED(existing))
+ return
+
+ var/obj/item/item_parent = parent
+ var/datum/action/modeswitch_action = item_parent.add_item_action(/datum/action/item_action/storage_gather_mode)
RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, PROC_REF(action_trigger))
- if(I.obj_flags & IN_INVENTORY)
- var/mob/M = I.loc
- if(!istype(M))
- return
- modeswitch_action.Grant(M)
+ modeswitch_action_ref = WEAKREF(modeswitch_action)
/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master)
if(new_master == src || (!isnull(new_master) && !istype(new_master)))
diff --git a/code/datums/components/wet_floor.dm b/code/datums/components/wet_floor.dm
index 4bb3352ee971..c0b962c71307 100644
--- a/code/datums/components/wet_floor.dm
+++ b/code/datums/components/wet_floor.dm
@@ -67,9 +67,9 @@
T.add_overlay(intended)
current_overlay = intended
-/datum/component/wet_floor/proc/AfterSlip(mob/living/L)
+/datum/component/wet_floor/proc/AfterSlip(mob/living/slipped)
if(highest_strength == TURF_WET_LUBE)
- L.confused = max(L.confused, 8)
+ slipped.set_confusion_if_lower(8 SECONDS)
/datum/component/wet_floor/proc/update_flags()
var/intensity
diff --git a/code/datums/dash_weapon.dm b/code/datums/dash_weapon.dm
index f3a7e1fc254c..24bc665d8755 100644
--- a/code/datums/dash_weapon.dm
+++ b/code/datums/dash_weapon.dm
@@ -1,7 +1,7 @@
/datum/action/innate/dash
name = "Dash"
desc = "Teleport to the targeted location."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "jetboot"
var/current_charges = 1
var/max_charges = 1
@@ -19,7 +19,7 @@
dashing_item = dasher
holder = user
-/datum/action/innate/dash/IsAvailable()
+/datum/action/innate/dash/IsAvailable(feedback = FALSE)
if(current_charges > 0)
return TRUE
else
@@ -29,7 +29,7 @@
dashing_item.attack_self(holder) //Used to toggle dash behavior in the dashing item
/datum/action/innate/dash/proc/Teleport(mob/user, atom/target)
- if(!IsAvailable())
+ if(!IsAvailable(feedback = FALSE))
return FALSE
var/turf/T = get_turf(target)
var/area/AU = get_area(user)
@@ -43,12 +43,12 @@
var/obj/spot2 = new phasein(get_turf(user), user.dir)
spot1.Beam(spot2,beam_effect,time=20)
current_charges--
- holder.update_action_buttons_icon()
+ holder.update_mob_action_buttons()
addtimer(CALLBACK(src, PROC_REF(charge)), charge_rate)
/datum/action/innate/dash/proc/charge()
current_charges = clamp(current_charges + 1, 0, max_charges)
- holder.update_action_buttons_icon()
+ holder.update_mob_action_buttons()
if(recharge_sound)
playsound(dashing_item, recharge_sound, 50, 1)
to_chat(holder, span_notice("[src] now has [current_charges]/[max_charges] charges."))
diff --git a/code/datums/diseases/advance/symptoms/confusion.dm b/code/datums/diseases/advance/symptoms/confusion.dm
index dafa7e688fc1..c59ccb6e789b 100644
--- a/code/datums/diseases/advance/symptoms/confusion.dm
+++ b/code/datums/diseases/advance/symptoms/confusion.dm
@@ -57,7 +57,7 @@ Bonus
to_chat(M, span_warning("[pick("Your head hurts.", "Your mind blanks for a moment.")]"))
else
to_chat(M, span_userdanger("You can't think straight!"))
- M.confused = min(100 * power, M.confused + 8)
+ M.adjust_confusion_up_to(100 * power, 800 * power)
if(brain_damage)
M.adjustOrganLoss(ORGAN_SLOT_BRAIN,3 * power, 80)
M.updatehealth()
diff --git a/code/datums/diseases/advance/symptoms/dizzy.dm b/code/datums/diseases/advance/symptoms/dizzy.dm
index 8fa08acc78bc..386be9445e9d 100644
--- a/code/datums/diseases/advance/symptoms/dizzy.dm
+++ b/code/datums/diseases/advance/symptoms/dizzy.dm
@@ -52,6 +52,6 @@ Bonus
to_chat(M, span_warning("[pick("You feel dizzy.", "Your head spins.")]"))
else
to_chat(M, span_userdanger("A wave of dizziness washes over you!"))
- M.Dizzy(5)
+ M.adjust_dizzy(5 SECONDS)
if(power >= 2)
M.set_drugginess(5)
diff --git a/code/datums/diseases/advance/symptoms/fire.dm b/code/datums/diseases/advance/symptoms/fire.dm
index 7ba4420e25ea..61e9d39fbb4a 100644
--- a/code/datums/diseases/advance/symptoms/fire.dm
+++ b/code/datums/diseases/advance/symptoms/fire.dm
@@ -60,12 +60,12 @@ Bonus
to_chat(M, span_warning("[pick("You feel hot.", "You hear a crackling noise.", "You smell smoke.")]"))
if(4)
Firestacks_stage_4(M, A)
- M.IgniteMob()
+ M.ignite_mob()
to_chat(M, span_userdanger("Your skin bursts into flames!"))
M.emote("scream")
if(5)
Firestacks_stage_5(M, A)
- M.IgniteMob()
+ M.ignite_mob()
to_chat(M, span_userdanger("Your skin erupts into an inferno!"))
M.emote("scream")
@@ -147,7 +147,7 @@ Bonus
to_chat(M, span_warning("[pick("Your veins boil.", "You feel hot.", "You smell meat cooking.")]"))
if(4)
Alkali_fire_stage_4(M, A)
- M.IgniteMob()
+ M.ignite_mob()
to_chat(M, span_userdanger("Your sweat bursts into flames!"))
M.emote("scream")
if(5)
@@ -156,7 +156,7 @@ Bonus
explosion(get_turf(M),0,0,2 * explosion_power)
Alkali_fire_stage_5(M, A)
Alkali_fire_stage_5(M, A)
- M.IgniteMob()
+ M.ignite_mob()
to_chat(M, span_userdanger("Your skin erupts into an inferno!"))
M.emote("scream")
diff --git a/code/datums/diseases/advance/symptoms/hallucigen.dm b/code/datums/diseases/advance/symptoms/hallucigen.dm
index c92c41ca0d9e..dcd44a1a7a88 100644
--- a/code/datums/diseases/advance/symptoms/hallucigen.dm
+++ b/code/datums/diseases/advance/symptoms/hallucigen.dm
@@ -67,4 +67,4 @@ Bonus
else
if(prob(base_message_chance))
to_chat(M, span_userdanger("[pick("Oh, your head...", "Your head pounds.", "They're everywhere! Run!", "Something in the shadows...")]"))
- M.hallucination += (45 * power)
+ M.adjust_hallucinations(45 * power)
diff --git a/code/datums/diseases/advance/symptoms/narcolepsy.dm b/code/datums/diseases/advance/symptoms/narcolepsy.dm
index 322bed7af642..09b892e0906f 100644
--- a/code/datums/diseases/advance/symptoms/narcolepsy.dm
+++ b/code/datums/diseases/advance/symptoms/narcolepsy.dm
@@ -46,7 +46,7 @@ Bonus
//this ticks even when on cooldown
switch(sleep_level) //Works sorta like morphine
if(10 to 19)
- M.drowsyness += 1
+ M.adjust_drowsiness(2 SECONDS)
if(20 to INFINITY)
M.Sleeping(30, 0)
sleep_level = 0
@@ -87,7 +87,7 @@ Bonus
if(5)
if(prob(25))
to_chat(M, span_warning("[pick("So tired...","You feel very sleepy.","You have a hard time keeping your eyes open.","You try to stay awake.")]"))
- M.drowsyness = max(M.drowsyness, 2)
+ M.adjust_drowsiness(rand(14, 7) SECONDS)
sleepy_ticks += rand(10,14)
if(stamina)
M.adjustStaminaLoss(30)
diff --git a/code/datums/diseases/advance/symptoms/sensory.dm b/code/datums/diseases/advance/symptoms/sensory.dm
index 1fe061eb4a25..be603032d9a6 100644
--- a/code/datums/diseases/advance/symptoms/sensory.dm
+++ b/code/datums/diseases/advance/symptoms/sensory.dm
@@ -36,23 +36,22 @@
if(A.stage >= 3)
- M.dizziness = max(0, M.dizziness - 2)
- M.drowsyness = max(0, M.drowsyness - 2)
- M.slurring = max(0, M.slurring - 2)
- M.confused = max(0, M.confused - 2)
+ M.adjust_dizzy(-4 SECONDS)
+ M.adjust_drowsiness(-4 SECONDS)
+ M.adjust_slurring(-1 SECONDS)
+ M.adjust_confusion(-2 SECONDS)
if(purge_alcohol)
M.reagents.remove_all_type(/datum/reagent/consumable/ethanol, 3)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- H.drunkenness = max(H.drunkenness - 5, 0)
+ M.adjust_drunk_effect(-5)
if(A.stage >= 4)
- M.drowsyness = max(0, M.drowsyness - 2)
+ M.adjust_drowsiness(-4 SECONDS)
if(M.reagents.has_reagent(/datum/reagent/toxin/mindbreaker))
M.reagents.remove_reagent(/datum/reagent/toxin/mindbreaker, 5)
if(M.reagents.has_reagent(/datum/reagent/toxin/histamine))
M.reagents.remove_reagent(/datum/reagent/toxin/histamine, 5)
- M.hallucination = max(0, M.hallucination - 10)
+
+ M.adjust_hallucinations(-20 SECONDS)
if(A.stage >= 5)
M.adjustOrganLoss(ORGAN_SLOT_BRAIN, -3)
@@ -60,12 +59,14 @@
var/mob/living/carbon/C = M
if(prob(10))
if(trauma_heal_severe)
- C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_LOBOTOMY)
+ C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_SURGERY)
else
C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC)
+
+
/datum/symptom/sensory_restoration
name = "Sensory Restoration"
desc = "The virus stimulates the production and replacement of sensory tissues, causing the host to regenerate eyes and ears when damaged."
diff --git a/code/datums/diseases/anxiety.dm b/code/datums/diseases/anxiety.dm
index 22a453894f1a..eb3128ec9fb2 100644
--- a/code/datums/diseases/anxiety.dm
+++ b/code/datums/diseases/anxiety.dm
@@ -11,31 +11,33 @@
desc = "If left untreated subject will regurgitate butterflies."
severity = DISEASE_SEVERITY_MINOR
-/datum/disease/anxiety/stage_act()
- ..()
+/datum/disease/anxiety/stage_act(delta_time, times_fired)
+ . = ..()
+ if(!.)
+ return
+
switch(stage)
if(2) //also changes say, see say.dm
- if(prob(5))
+ if(DT_PROB(2.5, delta_time))
to_chat(affected_mob, span_notice("You feel anxious."))
if(3)
- if(prob(10))
+ if(DT_PROB(5, delta_time))
to_chat(affected_mob, span_notice("Your stomach flutters."))
- if(prob(5))
+ if(DT_PROB(2.5, delta_time))
to_chat(affected_mob, span_notice("You feel panicky."))
- if(prob(2))
+ if(DT_PROB(1, delta_time))
to_chat(affected_mob, span_danger("You're overtaken with panic!"))
- affected_mob.confused += (rand(2,3))
+ affected_mob.adjust_confusion(rand(2 SECONDS, 3 SECONDS))
if(4)
- if(prob(10))
+ if(DT_PROB(5, delta_time))
to_chat(affected_mob, span_danger("You feel butterflies in your stomach."))
- if(prob(5))
+ if(DT_PROB(2.5, delta_time))
affected_mob.visible_message(span_danger("[affected_mob] stumbles around in a panic."), \
span_userdanger("You have a panic attack!"))
- affected_mob.confused += (rand(6,8))
- affected_mob.jitteriness += (rand(6,8))
- if(prob(2))
+ affected_mob.adjust_confusion(rand(6 SECONDS, 8 SECONDS))
+ affected_mob.adjust_jitter(rand(12 SECONDS, 16 SECONDS))
+ if(DT_PROB(1, delta_time))
affected_mob.visible_message(span_danger("[affected_mob] coughs up butterflies!"), \
span_userdanger("You cough up butterflies!"))
new /mob/living/simple_animal/butterfly(affected_mob.loc)
new /mob/living/simple_animal/butterfly(affected_mob.loc)
- return
\ No newline at end of file
diff --git a/code/datums/diseases/brainrot.dm b/code/datums/diseases/brainrot.dm
index 00ce32ff569e..9e231e9aad0a 100644
--- a/code/datums/diseases/brainrot.dm
+++ b/code/datums/diseases/brainrot.dm
@@ -54,6 +54,6 @@
if(prob(1))
affected_mob.emote("snore")
if(prob(15))
- affected_mob.stuttering += 3
+ affected_mob.adjust_stutter(3 SECONDS)
return
diff --git a/code/datums/diseases/decloning.dm b/code/datums/diseases/decloning.dm
index 1ee7e82204c4..6347bf614149 100644
--- a/code/datums/diseases/decloning.dm
+++ b/code/datums/diseases/decloning.dm
@@ -43,7 +43,7 @@
affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 170)
affected_mob.adjustCloneLoss(2)
if(prob(15))
- affected_mob.stuttering += 3
+ affected_mob.adjust_stutter(3 SECONDS)
if(5)
if(prob(2))
affected_mob.emote("itch")
@@ -56,4 +56,4 @@
affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 170)
if(affected_mob.cloneloss >= 100)
affected_mob.visible_message(span_danger("[affected_mob] skin turns to dust!"), "Your skin turns to dust!")
- affected_mob.dust()
\ No newline at end of file
+ affected_mob.dust()
diff --git a/code/datums/diseases/heart_failure.dm b/code/datums/diseases/heart_failure.dm
index 8c048078bf4a..f6dadf5ddaca 100644
--- a/code/datums/diseases/heart_failure.dm
+++ b/code/datums/diseases/heart_failure.dm
@@ -36,7 +36,7 @@
to_chat(H, span_warning("You feel [pick("discomfort", "pressure", "a burning sensation", "pain")] in your chest."))
if(prob(2))
to_chat(H, span_warning("You feel dizzy."))
- H.confused += 6
+ H.adjust_confusion(6 SECONDS)
if(prob(3))
to_chat(H, span_warning("You feel [pick("full", "nauseated", "sweaty", "weak", "tired", "short on breath", "uneasy")]."))
if(3 to 4)
@@ -52,7 +52,7 @@
H.losebreath += 4
if(prob(3))
to_chat(H, span_danger("You feel very weak and dizzy..."))
- H.confused += 8
+ H.adjust_confusion(8 SECONDS)
H.adjustStaminaLoss(40)
H.emote("cough")
if(5)
diff --git a/code/datums/diseases/jitters.dm b/code/datums/diseases/jitters.dm
index 3dc80b31ec09..7101d8288bb0 100644
--- a/code/datums/diseases/jitters.dm
+++ b/code/datums/diseases/jitters.dm
@@ -17,15 +17,15 @@
switch(stage)
if(1)
if(prob(10))
- affected_mob.Jitter(2)
+ affected_mob.adjust_jitter(2 SECONDS)
if(2)
if(prob(20))
- affected_mob.Jitter(2)
+ affected_mob.adjust_jitter(2 SECONDS)
if(prob(10))
to_chat(affected_mob, span_notice("You feel ants in your legs."))
if(3)
if(prob(40))
- affected_mob.Jitter(2)
+ affected_mob.adjust_jitter(2 SECONDS)
if(prob(20))
to_chat(affected_mob, span_danger("You feel a million pricks on your legs!"))
if((affected_mob.mobility_flags & MOBILITY_MOVE) && prob(15))
diff --git a/code/datums/diseases/plague.dm b/code/datums/diseases/plague.dm
index 543b3b8146de..465194a5251e 100644
--- a/code/datums/diseases/plague.dm
+++ b/code/datums/diseases/plague.dm
@@ -25,7 +25,7 @@
if(4)
if(prob(2))
to_chat(affected_mob, span_userdanger("You can't keep steady!"))
- affected_mob.Dizzy(5)
+ affected_mob.adjust_dizzy(5)
if(prob(2))
to_chat(affected_mob, span_danger("You can barely breathe!"))
affected_mob.adjustOxyLoss(5)
diff --git a/code/datums/diseases/rhumba_beat.dm b/code/datums/diseases/rhumba_beat.dm
index db8266bf04f8..488a15820e46 100644
--- a/code/datums/diseases/rhumba_beat.dm
+++ b/code/datums/diseases/rhumba_beat.dm
@@ -33,7 +33,7 @@
if(prob(20))
if (prob(50))
affected_mob.adjust_fire_stacks(2)
- affected_mob.IgniteMob()
+ affected_mob.ignite_mob()
else
affected_mob.emote("gasp")
to_chat(affected_mob, span_danger("You feel a burning beat inside..."))
diff --git a/code/datums/diseases/sleepy.dm b/code/datums/diseases/sleepy.dm
index 8e61dd03c574..0ca951fa8a04 100644
--- a/code/datums/diseases/sleepy.dm
+++ b/code/datums/diseases/sleepy.dm
@@ -46,7 +46,7 @@
affected_mob.blind_eyes(6)
if(prob(3))
to_chat(affected_mob, span_danger("Your legs feel weak."))
- affected_mob.confused = max(affected_mob.confused, 4)
+ affected_mob.set_confusion_if_lower(4 SECONDS)
affected_mob.adjustStaminaLoss(10)
if(prob(3))
to_chat(affected_mob, span_danger("Your eyes feel strained."))
@@ -54,7 +54,7 @@
if(prob(3))
to_chat(affected_mob, span_warning("[pick("So tired...","You feel very sleepy.","You have a hard time keeping your eyes open.","You try to stay awake.")]"))
affected_mob.blur_eyes(6)
- affected_mob.confused = max(affected_mob.confused, 4)
+ affected_mob.set_confusion_if_lower(4 SECONDS)
affected_mob.adjustStaminaLoss(15)
if(prob(5))
to_chat(affected_mob, span_danger("Just a little nap..."))
diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm
index b18cc84258e1..0d39ad0650ef 100644
--- a/code/datums/diseases/transformation.dm
+++ b/code/datums/diseases/transformation.dm
@@ -133,7 +133,7 @@
if(3)
if(prob(4))
to_chat(affected_mob, span_danger("You feel a stabbing pain in your head."))
- affected_mob.confused += 10
+ affected_mob.adjust_confusion(1 SECONDS)
if(4)
if(prob(3))
affected_mob.say(pick("Eeek, ook ook!", "Eee-eeek!", "Eeee!", "Ungh, ungh."), forced = "jungle fever")
diff --git a/code/datums/diseases/tuberculosis.dm b/code/datums/diseases/tuberculosis.dm
index 25816c909b0b..b168f18a443f 100644
--- a/code/datums/diseases/tuberculosis.dm
+++ b/code/datums/diseases/tuberculosis.dm
@@ -27,7 +27,7 @@
if(4)
if(prob(2))
to_chat(affected_mob, span_userdanger("You see four of everything"))
- affected_mob.Dizzy(5)
+ affected_mob.adjust_dizzy(5)
if(prob(2))
to_chat(affected_mob, span_danger("You feel a sharp pain from your lower chest!"))
affected_mob.adjustOxyLoss(5)
@@ -46,7 +46,7 @@
affected_mob.AdjustSleeping(100)
if(prob(2))
to_chat(affected_mob, span_userdanger("You feel your mind relax and your thoughts drift!"))
- affected_mob.confused = min(100, affected_mob.confused + 8)
+ affected_mob.adjust_confusion_up_to(8 SECONDS, 100 SECONDS)
if(prob(10))
affected_mob.vomit(20)
if(prob(3))
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 73385dfd2e7f..06b422fe8718 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -332,7 +332,7 @@
/mob/proc/has_dna()
- return
+ return FALSE
/mob/living/carbon/has_dna()
return dna
diff --git a/code/datums/elements/connect_loc.dm b/code/datums/elements/connect_loc.dm
new file mode 100644
index 000000000000..12fa35ea3fa7
--- /dev/null
+++ b/code/datums/elements/connect_loc.dm
@@ -0,0 +1,43 @@
+/// This element hooks a signal onto the loc the current object is on.
+/// When the object moves, it will unhook the signal and rehook it to the new object.
+/datum/element/connect_loc
+ element_flags = ELEMENT_BESPOKE
+ id_arg_index = 2
+
+ /// An assoc list of signal -> procpath to register to the loc this object is on.
+ var/list/connections
+
+/datum/element/connect_loc/Attach(atom/movable/listener, list/connections)
+ . = ..()
+ if (!istype(listener))
+ return ELEMENT_INCOMPATIBLE
+
+ src.connections = connections
+
+ RegisterSignal(listener, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved), override = TRUE)
+ update_signals(listener)
+
+/datum/element/connect_loc/Detach(atom/movable/listener)
+ . = ..()
+ unregister_signals(listener, listener.loc)
+ UnregisterSignal(listener, COMSIG_MOVABLE_MOVED)
+
+/datum/element/connect_loc/proc/update_signals(atom/movable/listener)
+ var/atom/listener_loc = listener.loc
+ if(isnull(listener_loc))
+ return
+
+ for (var/signal in connections)
+ //override=TRUE because more than one connect_loc element instance tracked object can be on the same loc
+ listener.RegisterSignal(listener_loc, signal, connections[signal], override=TRUE)
+
+/datum/element/connect_loc/proc/unregister_signals(datum/listener, atom/old_loc)
+ if(isnull(old_loc))
+ return
+
+ listener.UnregisterSignal(old_loc, connections)
+
+/datum/element/connect_loc/proc/on_moved(atom/movable/listener, atom/old_loc)
+ SIGNAL_HANDLER
+ unregister_signals(listener, old_loc)
+ update_signals(listener)
diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm
index ec929a5ff729..280b8c16183b 100644
--- a/code/datums/emotes.dm
+++ b/code/datums/emotes.dm
@@ -2,29 +2,52 @@
#define EMOTE_AUDIBLE 2
/datum/emote
- var/key = "" //What calls the emote
- var/key_third_person = "" //This will also call the emote
- var/message = "" //Message displayed when emote is used
- var/message_mime = "" //Message displayed if the user is a mime
- var/message_alien = "" //Message displayed if the user is a grown alien
- var/message_larva = "" //Message displayed if the user is an alien larva
- var/message_robot = "" //Message displayed if the user is a robot
- var/message_AI = "" //Message displayed if the user is an AI
- var/message_monkey = "" //Message displayed if the user is a monkey
- var/message_ipc = "" // Message to display if the user is an IPC
- var/message_simple = "" //Message to display if the user is a simple_animal
- var/message_param = "" //Message to display if a param was given
- var/emote_type = EMOTE_VISIBLE //Whether the emote is visible or audible
- var/restraint_check = FALSE //Checks if the mob is restrained before performing the emote
- var/muzzle_ignore = FALSE //Will only work if the emote is EMOTE_AUDIBLE
- var/list/mob_type_allowed_typecache = /mob //Types that are allowed to use that emote
- var/list/mob_type_blacklist_typecache //Types that are NOT allowed to use that emote
+ /// What calls the emote.
+ var/key = ""
+ /// This will also call the emote.
+ var/key_third_person = ""
+ /// Message displayed when emote is used.
+ var/message = ""
+ /// Message displayed if the user is a mime.
+ var/message_mime = ""
+ /// Message displayed if the user is a grown alien.
+ var/message_alien = ""
+ /// Message displayed if the user is an alien larva.
+ var/message_larva = ""
+ /// Message displayed if the user is a robot.
+ var/message_robot = ""
+ /// Message displayed if the user is an AI.
+ var/message_AI = ""
+ /// Message displayed if the user is a monkey.
+ var/message_monkey = ""
+ /// Message to display if the user is an IPC
+ var/message_ipc = ""
+ /// Message to display if the user is a simple_animal or basic mob.
+ var/message_simple = ""
+ /// Message with %t at the end to allow adding params to the message, like for mobs doing an emote relatively to something else.
+ var/message_param = ""
+ /// Whether the emote is visible and/or audible bitflag
+ var/emote_type = EMOTE_VISIBLE
+ /// Checks if the mob can use its hands before performing the emote.
+ var/hands_use_check = FALSE
+ /// Will only work if the emote is EMOTE_AUDIBLE.
+ var/muzzle_ignore = FALSE
+ /// Types that are allowed to use that emote.
+ var/list/mob_type_allowed_typecache = /mob
+ /// Types that are NOT allowed to use that emote.
+ var/list/mob_type_blacklist_typecache
+ /// Types that can use this emote regardless of their state.
var/list/mob_type_ignore_stat_typecache
+ /// In which state can you use this emote? (Check stat.dm for a full list of them)
var/stat_allowed = CONSCIOUS
- var/sound //Sound to play when emote is called
- var/vary = FALSE //used for the honk borg emote
- var/only_forced_audio = FALSE //can only code call this event instead of the player.
- var/cooldown = 0.4 SECONDS
+ /// Sound to play when emote is called.
+ var/sound
+ /// Used for the honk borg emote.
+ var/vary = FALSE
+ /// Can only code call this event instead of the player.
+ var/only_forced_audio = FALSE
+ /// The cooldown between the uses of the emote.
+ var/cooldown = 0.8 SECONDS
/datum/emote/New()
if (ispath(mob_type_allowed_typecache))
@@ -129,8 +152,17 @@
/datum/emote/proc/select_param(mob/user, params)
return replacetext(message_param, "%t", params)
+/**
+ * Check to see if the user is allowed to run the emote.
+ *
+ * Arguments:
+ * * user - Person that is trying to send the emote.
+ * * status_check - Bool that says whether we should check their stat or not.
+ * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE).
+ *
+ * Returns a bool about whether or not the user can run the emote.
+ */
/datum/emote/proc/can_run_emote(mob/user, status_check = TRUE, intentional = FALSE)
- . = TRUE
if(!is_type_in_typecache(user, mob_type_allowed_typecache))
return FALSE
if(is_type_in_typecache(user, mob_type_blacklist_typecache))
@@ -147,24 +179,16 @@
if(DEAD)
to_chat(user, span_notice("You cannot [key] while dead."))
return FALSE
- if(restraint_check)
- if(isliving(user))
- var/mob/living/L = user
- if(L.IsParalyzed() || L.IsStun())
- if(!intentional)
- return FALSE
- to_chat(user, span_notice("You cannot [key] while stunned."))
- return FALSE
- if(restraint_check && user.restrained())
+ if(hands_use_check && HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
if(!intentional)
return FALSE
- to_chat(user, span_notice("You cannot [key] while restrained."))
+ to_chat(user, span_warning("You cannot use your hands to [key] right now!"))
return FALSE
- if(isliving(user))
- var/mob/living/L = user
- if(HAS_TRAIT(L, TRAIT_EMOTEMUTE))
- return FALSE
+ if(HAS_TRAIT(user, TRAIT_EMOTEMUTE))
+ return FALSE
+
+ return TRUE
/**
* Allows the intrepid coder to send a basic emote
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index d47e3876f6fe..df014358cb44 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -39,14 +39,14 @@
precision = rand(1,100)
var/static/list/bag_cache = typecacheof(/obj/item/storage/backpack/holding)
- var/list/bagholding = typecache_filter_list(teleatom.GetAllContents(), bag_cache)
+ var/list/bagholding = typecache_filter_list(teleatom.get_all_contents(), bag_cache)
if(bagholding.len)
precision = max(rand(1,100)*bagholding.len,100)
if(isliving(teleatom))
var/mob/living/MM = teleatom
to_chat(MM, span_warning("The bluespace interface on your bag of holding interferes with the teleport!"))
MM.adjust_disgust(20+(precision/10)) //20-30 disgust, pretty nasty
- MM.confused += (10 + precision/20) //10-15 confusion, little wobbly
+ MM.adjust_confusion(10 + precision/20) //10-15 confusion, little wobbly
// if effects are not specified and not explicitly disabled, sparks
if ((!effectin || !effectout) && !no_effects)
diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm
index 276e24d6d662..d3e19c555b11 100644
--- a/code/datums/holocall.dm
+++ b/code/datums/holocall.dm
@@ -198,7 +198,7 @@
/datum/action/innate/end_holocall
name = "End Holocall"
- icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon = 'icons/mob/actions/actions_silicon.dmi'
button_icon_state = "camera_off"
var/datum/holocall/hcall
diff --git a/code/datums/hud.dm b/code/datums/hud.dm
index 58a8ccd7f154..7b890f336749 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -2,184 +2,432 @@
GLOBAL_LIST_EMPTY(all_huds)
+///gets filled by each /datum/atom_hud/New().
+///associative list of the form: list(hud category = list(all global atom huds that use that category))
+GLOBAL_LIST_EMPTY(huds_by_category)
+
//GLOBAL HUD LIST
GLOBAL_LIST_INIT(huds, list(
- DATA_HUD_SECURITY_BASIC = new/datum/atom_hud/data/human/security/basic(),
- DATA_HUD_SECURITY_ADVANCED = new/datum/atom_hud/data/human/security/advanced(),
- DATA_HUD_MEDICAL_BASIC = new/datum/atom_hud/data/human/medical/basic(),
- DATA_HUD_MEDICAL_ADVANCED = new/datum/atom_hud/data/human/medical/advanced(),
- DATA_HUD_DIAGNOSTIC_BASIC = new/datum/atom_hud/data/diagnostic/basic(),
- DATA_HUD_DIAGNOSTIC_ADVANCED = new/datum/atom_hud/data/diagnostic/advanced(),
- DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(),
- DATA_HUD_SENTIENT_DISEASE = new/datum/atom_hud/sentient_disease(),
- DATA_HUD_AI_DETECT = new/datum/atom_hud/ai_detector(),
- DATA_HUD_SECURITY_MEDICAL = new/datum/atom_hud/data/human/security/advanced/hos(),
- ANTAG_HUD_CULT = new/datum/atom_hud/antag(),
- ANTAG_HUD_REV = new/datum/atom_hud/antag(),
- ANTAG_HUD_OPS = new/datum/atom_hud/antag(),
- ANTAG_HUD_WIZ = new/datum/atom_hud/antag(),
- ANTAG_HUD_SHADOW = new/datum/atom_hud/antag(),
- ANTAG_HUD_TRAITOR = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_NINJA = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_CHANGELING = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_ABDUCTOR = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_DEVIL = new/datum/atom_hud/antag(),
- ANTAG_HUD_SINTOUCHED = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_SOULLESS = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_CLOCKWORK = new/datum/atom_hud/antag(),
- ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_HIVE = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_OBSESSED = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(),
- ANTAG_HUD_VAMPIRE = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_DARKSPAWN = new/datum/atom_hud/antag(),
- ANTAG_HUD_CAPITALIST = new/datum/atom_hud/antag(),
- ANTAG_HUD_COMMUNIST = new/datum/atom_hud/antag(),
- ANTAG_HUD_HERETIC = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_MINDSLAVE = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_ZOMBIE = new/datum/atom_hud/antag(),
- ANTAG_HUD_INFILTRATOR = new/datum/atom_hud/antag(),
- ANTAG_HUD_BLOODSUCKER = new/datum/atom_hud/antag(),
- ANTAG_HUD_MHUNTER = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_BRAINWASHED = new/datum/atom_hud/antag/hidden()
+ DATA_HUD_SECURITY_BASIC = new /datum/atom_hud/data/human/security/basic(),
+ DATA_HUD_SECURITY_ADVANCED = new /datum/atom_hud/data/human/security/advanced(),
+ DATA_HUD_MEDICAL_BASIC = new /datum/atom_hud/data/human/medical/basic(),
+ DATA_HUD_MEDICAL_ADVANCED = new /datum/atom_hud/data/human/medical/advanced(),
+ DATA_HUD_DIAGNOSTIC_BASIC = new /datum/atom_hud/data/diagnostic/basic(),
+ DATA_HUD_DIAGNOSTIC_ADVANCED = new /datum/atom_hud/data/diagnostic/advanced(),
+ DATA_HUD_ABDUCTOR = new /datum/atom_hud/abductor(),
+ DATA_HUD_SENTIENT_DISEASE = new /datum/atom_hud/sentient_disease(),
+ DATA_HUD_AI_DETECT = new /datum/atom_hud/ai_detector(),
+ DATA_HUD_SECURITY_MEDICAL = new /datum/atom_hud/data/human/security/advanced/hos(),
))
/datum/atom_hud
- var/list/atom/hudatoms = list() //list of all atoms which display this hud
- var/list/hudusers = list() //list with all mobs who can see the hud
- var/list/hud_icons = list() //these will be the indexes for the atom's hud_list
+ ///associative list of the form: list(z level = list(hud atom)).
+ ///tracks what hud atoms for this hud exists in what z level so we can only give users
+ ///the hud images that they can actually see.
+ var/list/atom/hud_atoms = list()
+
+ ///associative list of the form: list(z level = list(hud user client mobs)).
+ ///tracks mobs that can "see" us
+ // by z level so when they change z's we can adjust what images they see from this hud.
+ var/list/hud_users = list()
+
+ ///used for signal tracking purposes, associative list of the form: list(hud atom = TRUE) that isnt separated by z level
+ var/list/atom/hud_atoms_all_z_levels = list()
+
+ ///used for signal tracking purposes, associative list of the form: list(hud user = number of times this hud was added to this user).
+ ///that isnt separated by z level
+ var/list/mob/hud_users_all_z_levels = list()
- var/list/next_time_allowed = list() //mobs associated with the next time this hud can be added to them
- var/list/queued_to_see = list() //mobs that have triggered the cooldown and are queued to see the hud, but do not yet
+ ///these will be the indexes for the atom's hud_list
+ var/list/hud_icons = list()
- var/do_silicon_check = FALSE // true for medical and sec advanced and diagnostic basic HUDs
+ ///mobs associated with the next time this hud can be added to them
+ var/list/next_time_allowed = list()
+ ///mobs that have triggered the cooldown and are queued to see the hud, but do not yet
+ var/list/queued_to_see = list()
+ /// huduser = list(atoms with their hud hidden) - aka everyone hates targeted invisiblity
+ var/list/hud_exceptions = list()
+ ///whether or not this atom_hud type updates the global huds_by_category list.
+ ///some subtypes cant work like this since theyre supposed to "belong" to
+ ///one target atom each. it will still go in the other global hud lists.
+ var/uses_global_hud_category = TRUE
/datum/atom_hud/New()
GLOB.all_huds += src
+ for(var/z_level in 1 to world.maxz)
+ hud_atoms += list(list())
+ hud_users += list(list())
+
+ RegisterSignal(SSdcs, COMSIG_GLOB_NEW_Z, PROC_REF(add_z_level_huds))
+
+ if(uses_global_hud_category)
+ for(var/hud_icon in hud_icons)
+ GLOB.huds_by_category[hud_icon] += list(src)
/datum/atom_hud/Destroy()
- for(var/v in hudusers)
- remove_hud_from(v)
- for(var/v in hudatoms)
- remove_from_hud(v)
+ for(var/mob/mob as anything in hud_users_all_z_levels)
+ hide_from(mob)
+
+ for(var/atom/atom as anything in hud_atoms_all_z_levels)
+ remove_atom_from_hud(atom)
+
+ if(uses_global_hud_category)
+ for(var/hud_icon in hud_icons)
+ LAZYREMOVEASSOC(GLOB.huds_by_category, hud_icon, src)
+
GLOB.all_huds -= src
return ..()
-/datum/atom_hud/proc/remove_hud_from(mob/M, absolute = FALSE)
- if(!M || !hudusers[M])
- return
- if (absolute || !--hudusers[M])
- UnregisterSignal(M, COMSIG_PARENT_QDELETING)
- var/M_is_silicon = FALSE
- if(do_silicon_check && istype(M, /mob/living/silicon))
- M_is_silicon = TRUE
- hudusers -= M
- if(next_time_allowed[M])
- next_time_allowed -= M
- if(queued_to_see[M])
- queued_to_see -= M
+/datum/atom_hud/proc/add_z_level_huds()
+ SIGNAL_HANDLER
+ hud_atoms += list(list())
+ hud_users += list(list())
+
+///returns a list of all hud atoms in the given z level and linked lower z levels (because hud users in higher z levels can see below)
+/datum/atom_hud/proc/get_hud_atoms_for_z_level(z_level)
+ if(z_level <= 0)
+ return FALSE
+ if(z_level > length(hud_atoms))
+ stack_trace("get_hud_atoms_for_z_level() was given a z level index out of bounds of hud_atoms!")
+ return FALSE
+
+ . = list()
+ . += hud_atoms[z_level]
+
+ var/max_number_of_linked_z_levels_i_care_to_support_here = 10
+
+ while(max_number_of_linked_z_levels_i_care_to_support_here)
+ var/lower_z_level_exists = SSmapping.level_trait(z_level, ZTRAIT_DOWN)
+
+ if(lower_z_level_exists)
+ z_level--
+ . += hud_atoms[z_level]
+ max_number_of_linked_z_levels_i_care_to_support_here--
+ continue
+
else
- for(var/atom/A in hudatoms)
- if(M_is_silicon)
- if (istype(A, /mob))
- var/mob/B = A
- if(B.digitalcamo)
- continue
- remove_from_single_hud(M, A)
-
-/datum/atom_hud/proc/remove_from_hud(atom/A) //this is called when some stops existing or needs HUD removed
- if(!A)
+ break
+
+///returns a list of all hud users in the given z level and linked upper z levels (because hud users in higher z levels can see below)
+/datum/atom_hud/proc/get_hud_users_for_z_level(z_level)
+ if(z_level > length(hud_users) || z_level <= 0)
+ stack_trace("get_hud_atoms_for_z_level() was given a z level index [z_level] out of bounds 1->[length(hud_users)] of hud_atoms!")
return FALSE
- var/has_camo = FALSE
- if(do_silicon_check && istype(A, /mob))
- var/mob/B = A
- if(B.digitalcamo)
- has_camo = TRUE
- for(var/mob/M in hudusers)
- if(has_camo)
- if(istype(M, /mob/living/silicon))
- continue
- remove_from_single_hud(M, A)
- hudatoms -= A
- return TRUE
-/datum/atom_hud/proc/remove_from_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client this is used to Hide HUD A from mob M
- if(!M || !M.client || !A)
- return
- for(var/i in hud_icons)
- M.client.images -= A.hud_list[i]
+ . = list()
+ . += hud_users[z_level]
-/datum/atom_hud/proc/add_hud_to(mob/M) // this is called when something activates HUD
- if(!M)
+ var/max_number_of_linked_z_levels_i_care_to_support_here = 10
+
+ while(max_number_of_linked_z_levels_i_care_to_support_here)
+ var/upper_level_exists = SSmapping.level_trait(z_level, ZTRAIT_UP)
+
+ if(upper_level_exists)
+ z_level++
+ . += hud_users[z_level]
+ max_number_of_linked_z_levels_i_care_to_support_here--
+ continue
+
+ else
+ break
+
+///show this hud to the passed in user
+/datum/atom_hud/proc/show_to(mob/new_viewer)
+ if(!new_viewer)
return
- if(!hudusers[M])
- hudusers[M] = 1
- RegisterSignal(M, COMSIG_PARENT_QDELETING, PROC_REF(unregister_mob))
- var/M_is_silicon = FALSE
- if(do_silicon_check && istype(M, /mob/living/silicon))
- M_is_silicon = TRUE
- if(next_time_allowed[M] > world.time)
- if(!queued_to_see[M])
- addtimer(CALLBACK(src, PROC_REF(show_hud_images_after_cooldown), M, M_is_silicon), next_time_allowed[M] - world.time)
- queued_to_see[M] = TRUE
+
+ if(!hud_users_all_z_levels[new_viewer])
+ hud_users_all_z_levels[new_viewer] = 1
+
+ RegisterSignal(new_viewer, COMSIG_PARENT_QDELETING, PROC_REF(unregister_atom), override = TRUE) //both hud users and hud atoms use these signals
+ RegisterSignal(new_viewer, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_atom_or_user_z_level_changed), override = TRUE)
+
+ var/turf/their_turf = get_turf(new_viewer)
+ if(!their_turf)
+ return
+ hud_users[their_turf.z][new_viewer] = TRUE
+
+ if(next_time_allowed[new_viewer] > world.time)
+ if(!queued_to_see[new_viewer])
+ addtimer(CALLBACK(src, PROC_REF(show_hud_images_after_cooldown), new_viewer), next_time_allowed[new_viewer] - world.time)
+ queued_to_see[new_viewer] = TRUE
+
else
- next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN
- for(var/atom/A in hudatoms)
- if(M_is_silicon)
- if(istype(A, /mob))
- var/mob/B = A
- if(B.digitalcamo)
- continue
- add_to_single_hud(M, A)
+ next_time_allowed[new_viewer] = world.time + ADD_HUD_TO_COOLDOWN
+ for(var/atom/hud_atom_to_add as anything in get_hud_atoms_for_z_level(their_turf.z))
+ add_atom_to_single_mob_hud(new_viewer, hud_atom_to_add)
else
- hudusers[M]++ // the hell does this do?
+ hud_users_all_z_levels[new_viewer] += 1 //increment the number of times this hud has been added to this hud user
-/datum/atom_hud/proc/unregister_mob(datum/source, force)
- SIGNAL_HANDLER
- remove_hud_from(source, TRUE)
-
-/datum/atom_hud/proc/show_hud_images_after_cooldown(M, M_is_silicon)
- if(queued_to_see[M])
- queued_to_see -= M
- next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN
- for(var/atom/A in hudatoms)
- if(M_is_silicon)
- if(istype(A, /mob))
- var/mob/B = A
- if(B.digitalcamo)
- continue
- add_to_single_hud(M, A)
-
-/datum/atom_hud/proc/add_to_hud(atom/A) // something new starts existing or is in a need of a HUD
- if(!A)
+///Hides the images in this hud from former_viewer
+///If absolute is set to true, this will forcefully remove the hud, even if sources in theory remain
+/datum/atom_hud/proc/hide_from(mob/former_viewer, absolute = FALSE)
+ if(!former_viewer || !hud_users_all_z_levels[former_viewer])
+ return
+
+ hud_users_all_z_levels[former_viewer] -= 1//decrement number of sources for this hud on this user (bad way to track i know)
+
+ if (absolute || hud_users_all_z_levels[former_viewer] <= 0)//if forced or there arent any sources left, remove the user
+
+ if(!hud_atoms_all_z_levels[former_viewer])//make sure we arent unregistering changes on a mob thats also a hud atom for this hud
+ UnregisterSignal(former_viewer, COMSIG_MOVABLE_Z_CHANGED)
+ UnregisterSignal(former_viewer, COMSIG_PARENT_QDELETING)
+
+ hud_users_all_z_levels -= former_viewer
+
+ if(next_time_allowed[former_viewer])
+ next_time_allowed -= former_viewer
+
+ var/turf/their_turf = get_turf(former_viewer)
+ if(their_turf)
+ hud_users[their_turf.z] -= former_viewer
+
+ if(queued_to_see[former_viewer])
+ queued_to_see -= former_viewer
+ else if (their_turf)
+ for(var/atom/hud_atom as anything in get_hud_atoms_for_z_level(their_turf.z))
+ remove_atom_from_single_hud(former_viewer, hud_atom)
+
+/// add new_hud_atom to this hud
+/datum/atom_hud/proc/add_atom_to_hud(atom/new_hud_atom)
+ if(!new_hud_atom)
return FALSE
- hudatoms |= A
- var/has_camo = FALSE
- if(do_silicon_check && istype(A, /mob))
- var/mob/B = A
- if(B.digitalcamo)
- has_camo = TRUE
- for(var/mob/M in hudusers)
- if(has_camo)
- if(istype(M, /mob/living/silicon))
- continue
- if(!queued_to_see[M])
- add_to_single_hud(M, A)
+
+ // No matter where or who you are, you matter to me :)
+ RegisterSignal(new_hud_atom, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_atom_or_user_z_level_changed), override = TRUE)
+ RegisterSignal(new_hud_atom, COMSIG_PARENT_QDELETING, PROC_REF(unregister_atom), override = TRUE) //both hud atoms and hud users use these signals
+ hud_atoms_all_z_levels[new_hud_atom] = TRUE
+
+ var/turf/atom_turf = get_turf(new_hud_atom)
+ if(!atom_turf)
+ return TRUE
+
+ hud_atoms[atom_turf.z] |= new_hud_atom
+
+ for(var/mob/mob_to_show as anything in get_hud_users_for_z_level(atom_turf.z))
+ if(!queued_to_see[mob_to_show])
+ add_atom_to_single_mob_hud(mob_to_show, new_hud_atom)
+ return TRUE
+
+/// remove this atom from this hud completely
+/datum/atom_hud/proc/remove_atom_from_hud(atom/hud_atom_to_remove)
+ if(!hud_atom_to_remove || !hud_atoms_all_z_levels[hud_atom_to_remove])
+ return FALSE
+
+ //make sure we arent unregistering a hud atom thats also a hud user mob
+ if(!hud_users_all_z_levels[hud_atom_to_remove])
+ UnregisterSignal(hud_atom_to_remove, COMSIG_MOVABLE_Z_CHANGED)
+ UnregisterSignal(hud_atom_to_remove, COMSIG_PARENT_QDELETING)
+
+ for(var/mob/mob_to_remove as anything in hud_users_all_z_levels)
+ remove_atom_from_single_hud(mob_to_remove, hud_atom_to_remove)
+
+ hud_atoms_all_z_levels -= hud_atom_to_remove
+
+ var/turf/atom_turf = get_turf(hud_atom_to_remove)
+ if(!atom_turf)
+ return TRUE
+
+ hud_atoms[atom_turf.z] -= hud_atom_to_remove
+
+ return TRUE
+
+///adds a newly active hud category's image on a hud atom to every mob that could see it
+/datum/atom_hud/proc/add_single_hud_category_on_atom(atom/hud_atom, hud_category_to_add)
+ if(!hud_atom?.active_hud_list?[hud_category_to_add] || QDELING(hud_atom) || !(hud_category_to_add in hud_icons))
+ return FALSE
+
+ if(!hud_atoms_all_z_levels[hud_atom])
+ add_atom_to_hud(hud_atom)
+ return TRUE
+
+ var/turf/atom_turf = get_turf(hud_atom)
+ if(!atom_turf)
+ return FALSE
+
+ for(var/mob/hud_user as anything in get_hud_users_for_z_level(atom_turf.z))
+ if(!hud_user.client)
+ continue
+ if(!hud_exceptions[hud_user] || !(hud_atom in hud_exceptions[hud_user]))
+ hud_user.client.images |= hud_atom.active_hud_list[hud_category_to_add]
+
return TRUE
-/datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client this is used to show hud A to mob M
- if(!M || !M.client || !A)
+///removes the image or images in hud_atom.hud_list[hud_category_to_remove] from every mob that can see it but leaves every other image
+///from that atom there.
+/datum/atom_hud/proc/remove_single_hud_category_on_atom(atom/hud_atom, hud_category_to_remove)
+ if(QDELETED(hud_atom) || !(hud_category_to_remove in hud_icons) || !hud_atoms_all_z_levels[hud_atom])
+ return FALSE
+
+ if(!hud_atom.active_hud_list)
+ remove_atom_from_hud(hud_atom)
+ return TRUE
+
+ var/turf/atom_turf = get_turf(hud_atom)
+ if(!atom_turf)
+ return FALSE
+
+ for(var/mob/hud_user as anything in get_hud_users_for_z_level(atom_turf.z))
+ if(!hud_user.client)
+ continue
+ hud_user.client.images -= hud_atom.active_hud_list[hud_category_to_remove]//by this point it shouldnt be in active_hud_list
+
+ return TRUE
+
+///when a hud atom or hud user changes z levels this makes sure it gets the images it needs and removes the images it doesnt need.
+///because of how signals work we need the same proc to handle both use cases because being a hud atom and being a hud user arent mutually exclusive
+/datum/atom_hud/proc/on_atom_or_user_z_level_changed(atom/movable/moved_atom, old_z, new_z) //turf/old_turf, turf/new_turf
+ SIGNAL_HANDLER
+ if(old_z)
+ if(hud_users_all_z_levels[moved_atom])
+ hud_users[old_z] -= moved_atom
+
+ remove_all_atoms_from_single_hud(moved_atom, get_hud_atoms_for_z_level(old_z))
+
+ if(hud_atoms_all_z_levels[moved_atom])
+ hud_atoms[old_z] -= moved_atom
+
+ //this wont include moved_atom since its removed
+ remove_atom_from_all_huds(get_hud_users_for_z_level(old_z), moved_atom)
+
+ if(new_z)
+ if(hud_users_all_z_levels[moved_atom])
+ hud_users[new_z][moved_atom] = TRUE //hud users is associative, hud atoms isnt
+
+ add_all_atoms_to_single_mob_hud(moved_atom, get_hud_atoms_for_z_level( new_z))
+
+ if(hud_atoms_all_z_levels[moved_atom])
+ hud_atoms[ new_z] |= moved_atom
+
+ add_atom_to_all_mob_huds(get_hud_users_for_z_level(new_z), moved_atom)
+
+/// add just hud_atom's hud images (that are part of this atom_hud) to requesting_mob's client.images list
+/datum/atom_hud/proc/add_atom_to_single_mob_hud(mob/requesting_mob, atom/hud_atom) //unsafe, no sanity apart from client
+ if(!requesting_mob || !requesting_mob.client || !hud_atom)
+ return
+
+ for(var/hud_category in (hud_icons & hud_atom.active_hud_list))
+ if(!hud_exceptions[requesting_mob] || !(hud_atom in hud_exceptions[requesting_mob]))
+ requesting_mob.client.images |= hud_atom.active_hud_list[hud_category]
+
+/// all passed in hud_atoms's hud images (that are part of this atom_hud) to requesting_mob's client.images list
+/// optimization of [/datum/atom_hud/proc/add_atom_to_single_mob_hud] for hot cases, we assert that no nulls will be passed in via the list
+/datum/atom_hud/proc/add_all_atoms_to_single_mob_hud(mob/requesting_mob, list/atom/hud_atoms) //unsafe, no sanity apart from client
+ if(!requesting_mob || !requesting_mob.client)
+ return
+
+ // Hud entries this mob ignores
+ var/list/mob_exceptions = hud_exceptions[requesting_mob]
+
+ for(var/hud_category in hud_icons)
+ for(var/atom/hud_atom as anything in hud_atoms)
+ if(mob_exceptions && (hud_atom in hud_exceptions[requesting_mob]))
+ continue
+ var/image/output = hud_atom.active_hud_list?[hud_category]
+ // byond throws a fit if you try to add null to the images list
+ if(!output)
+ continue
+ requesting_mob.client.images |= output
+
+/// add just hud_atom's hud images (that are part of this atom_hud) to all the requesting_mobs's client.images list
+/// optimization of [/datum/atom_hud/proc/add_atom_to_single_mob_hud] for hot cases, we assert that no nulls will be passed in via the list
+/datum/atom_hud/proc/add_atom_to_all_mob_huds(list/mob/requesting_mobs, atom/hud_atom) //unsafe, no sanity apart from client
+ if(!hud_atom?.active_hud_list)
+ return
+
+ var/list/images_to_add = list()
+ for(var/hud_category in (hud_icons & hud_atom.active_hud_list))
+ images_to_add |= hud_atom.active_hud_list[hud_category]
+
+ // Cache for sonic speed, lists are structs
+ var/list/exceptions = hud_exceptions
+ for(var/mob/requesting_mob as anything in requesting_mobs)
+ if(!requesting_mob.client)
+ continue
+ if(!exceptions[requesting_mob] || !(hud_atom in exceptions[requesting_mob]))
+ requesting_mob.client.images |= images_to_add
+
+/// remove every hud image for this hud on atom_to_remove from client_mob's client.images list
+/datum/atom_hud/proc/remove_atom_from_single_hud(mob/client_mob, atom/atom_to_remove)
+ if(!client_mob || !client_mob.client || !atom_to_remove?.active_hud_list)
+ return
+ for(var/hud_image in hud_icons)
+ client_mob.client.images -= atom_to_remove.active_hud_list[hud_image]
+
+/// remove every hud image for this hud pulled from atoms_to_remove from client_mob's client.images list
+/// optimization of [/datum/atom_hud/proc/remove_atom_from_single_hud] for hot cases, we assert that no nulls will be passed in via the list
+/datum/atom_hud/proc/remove_all_atoms_from_single_hud(mob/client_mob, list/atom/atoms_to_remove)
+ if(!client_mob || !client_mob.client)
+ return
+ for(var/hud_image in hud_icons)
+ for(var/atom/atom_to_remove as anything in atoms_to_remove)
+ client_mob.client.images -= atom_to_remove.active_hud_list?[hud_image]
+
+/// remove every hud image for this hud on atom_to_remove from client_mobs's client.images list
+/// optimization of [/datum/atom_hud/proc/remove_atom_from_single_hud] for hot cases, we assert that no nulls will be passed in via the list
+/datum/atom_hud/proc/remove_atom_from_all_huds(list/mob/client_mobs, atom/atom_to_remove)
+ if(!atom_to_remove?.active_hud_list)
+ return
+
+ var/list/images_to_remove = list()
+ for(var/hud_image in hud_icons)
+ images_to_remove |= atom_to_remove.active_hud_list[hud_image]
+
+ for(var/mob/client_mob as anything in client_mobs)
+ if(!client_mob.client)
+ continue
+ client_mob.client.images -= images_to_remove
+
+/datum/atom_hud/proc/unregister_atom(datum/source, force)
+ SIGNAL_HANDLER
+ hide_from(source, TRUE)
+ remove_atom_from_hud(source)
+
+/datum/atom_hud/proc/hide_single_atomhud_from(mob/hud_user, atom/hidden_atom)
+
+ if(hud_users_all_z_levels[hud_user])
+ remove_atom_from_single_hud(hud_user, hidden_atom)
+
+ if(!hud_exceptions[hud_user])
+ hud_exceptions[hud_user] = list(hidden_atom)
+ else
+ hud_exceptions[hud_user] += hidden_atom
+
+/datum/atom_hud/proc/unhide_single_atomhud_from(mob/hud_user, atom/hidden_atom)
+ hud_exceptions[hud_user] -= hidden_atom
+
+ var/turf/hud_atom_turf = get_turf(hidden_atom)
+
+ if(!hud_atom_turf)
return
- for(var/i in hud_icons)
- if(A.hud_list[i])
- M.client.images |= A.hud_list[i]
+
+ if(hud_users[hud_atom_turf.z][hud_user])
+ add_atom_to_single_mob_hud(hud_user, hidden_atom)
+
+/datum/atom_hud/proc/show_hud_images_after_cooldown(mob/queued_hud_user)
+ if(!queued_to_see[queued_hud_user])
+ return
+
+ queued_to_see -= queued_hud_user
+ next_time_allowed[queued_hud_user] = world.time + ADD_HUD_TO_COOLDOWN
+
+ var/turf/user_turf = get_turf(queued_hud_user)
+ if(!user_turf)
+ return
+
+ for(var/atom/hud_atom_to_show as anything in get_hud_atoms_for_z_level(user_turf.z))
+ add_atom_to_single_mob_hud(queued_hud_user, hud_atom_to_show)
//MOB PROCS
-/mob/proc/reload_huds() // used when you need to readd all aplicable huds to user (for instance on reconnect)
+/mob/proc/reload_huds()
+ var/turf/our_turf = get_turf(src)
+ if(!our_turf)
+ return
+
for(var/datum/atom_hud/hud in GLOB.all_huds)
- if(hud && hud.hudusers[src])
- for(var/atom/A in hud.hudatoms)
- hud.add_to_single_hud(src, A)
+ if(hud?.hud_users_all_z_levels[src])
+ for(var/atom/hud_atom as anything in hud.get_hud_atoms_for_z_level(our_turf.z))
+ hud.add_atom_to_single_mob_hud(src, hud_atom)
/mob/dead/new_player/reload_huds()
return
diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm
index 6cef78249f57..1151a492cfa4 100644
--- a/code/datums/martial/cqc.dm
+++ b/code/datums/martial/cqc.dm
@@ -200,7 +200,7 @@
if(check_streak(A,D)) //if a combo is made no grab upgrade is done
return TRUE
if(D.grabbedby(A))
- D.Stun(15)
+ D.Stun(1.5 SECONDS)
if(A.grab_state < 1)
restraining = FALSE
return TRUE
@@ -252,7 +252,7 @@
playsound(get_turf(D), 'sound/weapons/cqchit1.ogg', 50, 1, -1)
if(I && D.temporarilyRemoveItemFromInventory(I))
A.put_in_hands(I)
- D.Jitter(2)
+ D.adjust_jitter(2 SECONDS)
D.apply_damage(A.get_punchdamagehigh()/2, STAMINA) //5 damage
else
D.visible_message(span_danger("[A] grabs at [D]'s arm, but misses!"), \
diff --git a/code/datums/martial/flying_fang.dm b/code/datums/martial/flying_fang.dm
index 283df1ae9f32..f2d0bfe1ed79 100644
--- a/code/datums/martial/flying_fang.dm
+++ b/code/datums/martial/flying_fang.dm
@@ -156,11 +156,11 @@
/datum/action/innate/lizard_leap
name = "Leap"
- icon_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "lizard_tackle"
background_icon_state = "bg_default"
desc = "Prepare to jump at a target, with a successful hit stunning them and preventing you from moving for a few seconds."
- check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS
+ check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS
var/datum/martial_art/flyingfang/linked_martial
/datum/action/innate/lizard_leap/New()
@@ -172,9 +172,9 @@
return ..()
/datum/action/innate/lizard_leap/process()
- UpdateButtonIcon() //keep the button updated
+ build_all_button_icons() //keep the button updated
-/datum/action/innate/lizard_leap/IsAvailable()
+/datum/action/innate/lizard_leap/IsAvailable(feedback = FALSE)
. = ..()
if(linked_martial.leaping || !linked_martial.can_use(owner))
return FALSE
@@ -196,7 +196,7 @@
active = FALSE
background_icon_state = "bg_default"
-/datum/action/innate/lizard_leap/proc/InterceptClickOn(mob/living/carbon/human/A, params, atom/target)
+/datum/action/innate/lizard_leap/InterceptClickOn(mob/living/carbon/human/A, params, atom/target)
if(linked_martial.leaping)
return
linked_martial.leaping = TRUE
@@ -205,12 +205,12 @@
A.Immobilize(3 SECONDS, TRUE, TRUE) //prevents you from breaking out of your pounce
A.throw_at(target, get_dist(A,target)+1, 1, A, FALSE, TRUE, callback = CALLBACK(src, PROC_REF(leap_end), A))
Deactivate()
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/lizard_leap/proc/leap_end(mob/living/carbon/human/A)
A.SetImmobilized(0, TRUE, TRUE)
linked_martial.leaping = FALSE
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/martial_art/flyingfang/handle_throw(atom/hit_atom, mob/living/carbon/human/A)
if(!leaping)
@@ -240,7 +240,7 @@
playsound(A, 'sound/weapons/punch2.ogg', 50, 1) // ow oof ouch my head
if(leaping)
leaping = FALSE
- linked_leap.UpdateButtonIcon()
+ linked_leap.build_all_button_icons()
linked_leap.Deactivate(TRUE)
return TRUE
diff --git a/code/datums/martial/hunterfu.dm b/code/datums/martial/hunterfu.dm
index 04cb6687a31d..e5ad59ae96ac 100644
--- a/code/datums/martial/hunterfu.dm
+++ b/code/datums/martial/hunterfu.dm
@@ -1,7 +1,7 @@
#define BODYSLAM_COMBO "GH"
-#define STAKESTAB_COMBO "HH"
+#define STAKESTAB_COMBO "HDH"
#define NECKSNAP_COMBO "GDH"
-#define HOLYKICK_COMBO "DG"
+#define HOLYKICK_COMBO "DHG"
// From CQC.dm
/datum/martial_art/hunterfu
@@ -206,7 +206,7 @@
D.grabbedby(A, 1)
if(old_grab_state == GRAB_PASSIVE)
D.drop_all_held_items()
- A.grab_state = GRAB_AGGRESSIVE // Instant agressive grab
+ A.setGrabState(GRAB_AGGRESSIVE) // Instant agressive grab
log_combat(A, D, "grabbed (Hunter-Fu)")
D.visible_message(
span_warning("[A] violently grabs [D]!"),
@@ -224,8 +224,8 @@
to_chat(usr, span_notice("You try to remember some of the basics of Hunter-Fu."))
to_chat(usr, span_notice("Body Slam: Grab Harm. Slam opponent into the ground, knocking you both down."))
- to_chat(usr, span_notice("Stake Stab: Harm Harm. Stabs opponent with your bare fist, as strong as a Stake."))
+ to_chat(usr, span_notice("Stake Stab: Harm Disarm Harm. Stabs opponent with your bare fist, as strong as a Stake."))
to_chat(usr, span_notice("Neck Snap: Grab Disarm Harm. Snaps an opponents neck, knocking them out."))
- to_chat(usr, span_notice("Holy Kick: Disarm Grab. Splashes the user with Holy Water, removing Cult Spells, while dealing stamina damage."))
+ to_chat(usr, span_notice("Holy Kick: Disarm Harm Grab. Splashes the user with Holy Water, removing Cult Spells, while dealing stamina damage."))
to_chat(usr, span_notice("In addition, by having your throw mode on, you take a defensive position, allowing you to block and sometimes even counter attacks done to you."))
diff --git a/code/datums/martial/krav_maga.dm b/code/datums/martial/krav_maga.dm
index 25001a7f55a8..a63200be235f 100644
--- a/code/datums/martial/krav_maga.dm
+++ b/code/datums/martial/krav_maga.dm
@@ -7,7 +7,7 @@
/datum/action/neck_chop
name = "Neck Chop - Injures the neck, stopping the victim from speaking for a while."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "neckchop"
/datum/action/neck_chop/Trigger()
@@ -24,7 +24,7 @@
/datum/action/leg_sweep
name = "Leg Sweep - Trips the victim, knocking them down for a brief moment."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "legsweep"
/datum/action/leg_sweep/Trigger()
@@ -41,7 +41,7 @@
/datum/action/lung_punch//referred to internally as 'quick choke'
name = "Lung Punch - Delivers a strong punch just above the victim's abdomen, constraining the lungs. The victim will be unable to breathe for a short time."
- icon_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "lungpunch"
/datum/action/lung_punch/Trigger()
diff --git a/code/datums/martial/plasma_fist.dm b/code/datums/martial/plasma_fist.dm
index 2f9f1c61a88a..4f58ace634e6 100644
--- a/code/datums/martial/plasma_fist.dm
+++ b/code/datums/martial/plasma_fist.dm
@@ -35,11 +35,11 @@
/datum/martial_art/plasma_fist/proc/Tornado(mob/living/carbon/human/A, mob/living/carbon/human/D)
A.say("TORNADO SWEEP!", forced="plasma fist")
TornadoAnimate(A)
- var/obj/effect/proc_holder/spell/aoe_turf/repulse/R = new(null)
- var/list/turfs = list()
- for(var/turf/T in range(1,A))
- turfs.Add(T)
- R.cast(turfs)
+
+ var/datum/action/cooldown/spell/aoe/repulse/tornado_spell = new(src)
+ tornado_spell.cast(A)
+ qdel(tornado_spell)
+
log_combat(A, D, "tornado sweeped(Plasma Fist)")
return
diff --git a/code/datums/martial/psychotic_brawl.dm b/code/datums/martial/psychotic_brawl.dm
index da33eb45a24d..046e97b00609 100644
--- a/code/datums/martial/psychotic_brawl.dm
+++ b/code/datums/martial/psychotic_brawl.dm
@@ -44,10 +44,10 @@
log_combat(A, D, "grabbed", addition="aggressively")
D.visible_message(span_warning("[A] violently grabs [D]!"), \
span_userdanger("[A] violently grabs you!"))
- A.grab_state = GRAB_AGGRESSIVE //Instant aggressive grab
+ A.setGrabState(GRAB_AGGRESSIVE) //Instant aggressive grab
else
log_combat(A, D, "grabbed", addition="passively")
- A.grab_state = GRAB_PASSIVE
+ A.setGrabState(GRAB_PASSIVE)
if(4)
A.do_attack_animation(D, ATTACK_EFFECT_PUNCH)
atk_verb = "headbutts"
diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm
index 34ed8ba8a36c..0f303e307b86 100644
--- a/code/datums/martial/sleeping_carp.dm
+++ b/code/datums/martial/sleeping_carp.dm
@@ -118,7 +118,7 @@
D.grabbedby(A, 1)
if(old_grab_state == GRAB_PASSIVE)
D.drop_all_held_items()
- A.grab_state = GRAB_AGGRESSIVE //Instant agressive grab if on grab intent
+ A.setGrabState(GRAB_AGGRESSIVE) //Instant agressive grab if on grab intent
log_combat(A, D, "grabbed", addition="aggressively")
D.visible_message(span_warning("[A] violently grabs [D]!"), \
span_userdanger("[A] violently grabs you!"))
diff --git a/code/datums/martial/worldshaker.dm b/code/datums/martial/worldshaker.dm
index 6b42bbabb816..a9246f501e79 100644
--- a/code/datums/martial/worldshaker.dm
+++ b/code/datums/martial/worldshaker.dm
@@ -416,15 +416,15 @@
/datum/action/cooldown/worldstomp
name = "Worldstomp"
desc = "Put all your weight and strength into a singular stomp."
- icon_icon = 'icons/mob/actions/humble/actions_humble.dmi'
+ button_icon = 'icons/mob/actions/humble/actions_humble.dmi'
button_icon_state = "lightning"
background_icon_state = "bg_default"
- check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_LYING | AB_CHECK_CONSCIOUS
+ check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_LYING | AB_CHECK_CONSCIOUS
var/datum/martial_art/worldshaker/linked_martial
cooldown_time = COOLDOWN_STOMP
var/charging = FALSE
-/datum/action/cooldown/worldstomp/IsAvailable()
+/datum/action/cooldown/worldstomp/IsAvailable(feedback = FALSE)
if(!linked_martial || !linked_martial.can_use(owner))
return FALSE
return ..()
diff --git a/code/datums/martial/wrestling.dm b/code/datums/martial/wrestling.dm
index 8782e6ea6e45..475970315b07 100644
--- a/code/datums/martial/wrestling.dm
+++ b/code/datums/martial/wrestling.dm
@@ -440,7 +440,7 @@
A.start_pulling(D)
D.visible_message(span_danger("[A] gets [D] in a cinch!"), \
span_userdanger("[A] gets [D] in a cinch!"))
- D.Stun(rand(60,100))
+ D.Stun(rand(6, 10) SECONDS)
log_combat(A, D, "cinched")
return 1
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 196557850663..fe4c30cd5e46 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -45,21 +45,22 @@
var/list/restricted_roles = list()
var/list/datum/objective/objectives = list()
- var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button.
-
var/linglink
var/datum/martial_art/martial_art
var/static/default_martial_art = new/datum/martial_art
var/miming = FALSE // Mime's vow of silence
var/list/antag_datums
- var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state
- var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD
+ /// this mind's ANTAG_HUD should have this icon_state
+ var/antag_hud_icon_state = null
+ ///this mind's antag HUD
+ var/datum/atom_hud/alternate_appearance/basic/antagonist_hud/antag_hud = null //this mind's antag HUD
var/damnation_type = 0
var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src
var/hasSoul = TRUE // If false, renders the character unable to sell their soul.
var/holy_role = NONE //is this person a chaplain or admin role allowed to use bibles, Any rank besides 'NONE' allows for this.
- var/mob/living/enslaved_to //If this mind's master is another mob (i.e. adamantine golems)
+ ///If this mind's master is another mob (i.e. adamantine golems). Weakref of a /living.
+ var/datum/weakref/enslaved_to
var/datum/language_holder/language_holder
var/unconvertable = FALSE
var/late_joiner = FALSE
@@ -138,12 +139,13 @@
if(new_character.mind) //disassociate any mind currently in our new body's mind variable
new_character.mind.current = null
- var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list
var/mob/living/old_current = current
if(current)
current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one
current = new_character //associate ourself with our new body
+ QDEL_NULL(antag_hud)
new_character.mind = src //and associate our new body with ourself
+ antag_hud = new_character.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/antagonist_hud, "combo_hud", src)
for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body
var/datum/antagonist/A = a
A.on_body_transfer(old_current, current)
@@ -155,8 +157,6 @@
var/mob/living/carbon/human/H = C
H.AddComponent(/datum/component/mood)
// Yogs End
- transfer_antag_huds(hud_to_transfer) //inherit the antag HUD
- transfer_actions(new_character)
transfer_martial_arts(new_character)
transfer_parasites()
RegisterSignal(new_character, COMSIG_GLOB_MOB_DEATH, PROC_REF(set_death_time))
@@ -168,6 +168,8 @@
new_character.client.init_verbs() // re-initialize character specific verbs
LAZYCLEARLIST(new_character.client.recent_examines)
current.update_atom_languages()
+ SEND_SIGNAL(src, COMSIG_MIND_TRANSFERRED, old_current)
+ SEND_SIGNAL(current, COMSIG_MOB_MIND_TRANSFERRED_INTO)
/datum/mind/proc/set_death_time()
last_death = world.time
@@ -252,7 +254,6 @@
/datum/mind/proc/remove_brother()
if(src in SSticker.mode.brothers)
remove_antag_datum(/datum/antagonist/brother)
- SSticker.mode.update_brother_icons_removed(src)
/datum/mind/proc/remove_nukeop()
var/datum/antagonist/nukeop/nuke = has_antag_datum(/datum/antagonist/nukeop,TRUE)
@@ -291,7 +292,6 @@
remove_wizard()
remove_cultist()
remove_rev()
- SSticker.mode.update_cult_icons_removed(src)
/datum/mind/proc/equip_traitor(employer = "The Syndicate", silent = FALSE, datum/antagonist/uplink_owner)
if(!current)
@@ -300,7 +300,7 @@
if (!istype(traitor_mob))
return
- var/list/all_contents = traitor_mob.GetAllContents()
+ var/list/all_contents = traitor_mob.get_all_contents()
var/obj/item/modular_computer/PDA = locate() in all_contents
var/obj/item/radio/R = locate() in all_contents
var/obj/item/pen/P
@@ -394,7 +394,7 @@
add_antag_datum(N,converter.nuke_team)
- enslaved_to = creator
+ enslaved_to = WEAKREF(creator)
current.faction |= creator.faction
creator.faction |= current.faction
@@ -634,7 +634,7 @@
obj_count++
/datum/mind/proc/find_syndicate_uplink()
- var/list/L = current.GetAllContents()
+ var/list/L = current.get_all_contents()
for (var/i in L)
var/atom/movable/I = i
. = I.GetComponent(/datum/component/uplink)
@@ -706,30 +706,9 @@
add_antag_datum(head)
special_role = ROLE_REV_HEAD
-/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S)
- spell_list += S
- S.action.Grant(current)
- S.on_gain(current)
-
/datum/mind/proc/owns_soul()
return soulOwner == src
-//To remove a specific spell from a mind
-/datum/mind/proc/RemoveSpell(obj/effect/proc_holder/spell/spell)
- if(!spell)
- return
- for(var/X in spell_list)
- var/obj/effect/proc_holder/spell/S = X
- if(istype(S, spell))
- spell_list -= S
- S.on_lose(current)
- qdel(S)
- current?.client << output(null, "statbrowser:check_spells")
-
-/datum/mind/proc/RemoveAllSpells()
- for(var/obj/effect/proc_holder/S in spell_list)
- RemoveSpell(S)
-
/datum/mind/proc/transfer_martial_arts(mob/living/new_character)
if(!ishuman(new_character))
return
@@ -739,37 +718,6 @@
else
martial_art.teach(new_character)
-/datum/mind/proc/transfer_actions(mob/living/new_character)
- if(current && current.actions)
- for(var/datum/action/A in current.actions)
- A.Grant(new_character)
- transfer_mindbound_actions(new_character)
-
-//Check if there is a specific spell in mind
-/datum/mind/proc/CheckSpell(var/obj/effect/proc_holder/spell/spell)
- if(!spell) return
- for(var/X in spell_list)
- var/obj/effect/proc_holder/spell/S = X
- if(istype(S, spell))
- return TRUE
- return FALSE
-
-/datum/mind/proc/transfer_mindbound_actions(mob/living/new_character)
- for(var/X in spell_list)
- var/obj/effect/proc_holder/spell/S = X
- S.action.Grant(new_character)
- S.on_gain(new_character)
-
-/datum/mind/proc/disrupt_spells(delay, list/exceptions = New())
- for(var/X in spell_list)
- var/obj/effect/proc_holder/spell/S = X
- for(var/type in exceptions)
- if(istype(S, type))
- continue
- S.charge_counter = delay
- S.updateButtonIcon()
- INVOKE_ASYNC(S, TYPE_PROC_REF(/obj/effect/proc_holder/spell, start_recharge))
-
/datum/mind/proc/get_ghost(even_if_they_cant_reenter, ghosts_with_clients)
for(var/mob/dead/observer/G in (ghosts_with_clients ? GLOB.player_list : GLOB.dead_mob_list))
if(G.mind == src)
@@ -831,6 +779,8 @@
if(!mind.name)
mind.name = real_name
mind.current = src
+ // There's nowhere else to set this up, mind code makes me depressed
+ mind.antag_hud = add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/antagonist_hud, "combo_hud", mind)
/mob/living/carbon/mind_initialize()
..()
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index 5a6a18837802..776fb7454288 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -30,7 +30,7 @@
/datum/mood_event/creampie
description = "I've been creamed. Tastes like pie flavor.\n"
mood_change = -2
- timeout = 1800
+ timeout = 3 MINUTES
/datum/mood_event/slipped
description = "I slipped. I should be more careful next time...\n"
diff --git a/code/datums/mutations.dm b/code/datums/mutations.dm
index a66bf80be075..4d3058f5d333 100644
--- a/code/datums/mutations.dm
+++ b/code/datums/mutations.dm
@@ -12,7 +12,8 @@
var/text_gain_indication = ""
var/text_lose_indication = ""
var/static/list/list/mutable_appearance/visual_indicators = list()
- var/obj/effect/proc_holder/spell/power
+ /// The path of action we grant to our user on mutation gain
+ var/datum/action/cooldown/spell/power_path
var/layer_used = MUTATIONS_LAYER //which mutation layer to use
var/list/species_allowed = list() //to restrict mutation to only certain species
var/health_req //minimum health required to acquire the mutation
@@ -81,9 +82,9 @@
owner.remove_overlay(layer_used)
owner.overlays_standing[layer_used] = mut_overlay
owner.apply_overlay(layer_used)
- grant_spell() //we do checks here so nothing about hulk getting magic
+ grant_power() //we do checks here so nothing about hulk getting magic
if(!modified)
- addtimer(CALLBACK(src, PROC_REF(modify), 5)) //gonna want children calling ..() to run first
+ addtimer(CALLBACK(src, PROC_REF(modify), 0.5 SECONDS)) //gonna want children calling ..() to run first
/datum/mutation/human/proc/get_visual_indicator()
return
@@ -109,8 +110,10 @@
mut_overlay.Remove(get_visual_indicator())
owner.overlays_standing[layer_used] = mut_overlay
owner.apply_overlay(layer_used)
- if(power)
- owner.RemoveSpell(power)
+ if(power_path)
+ // Any powers we made are linked to our mutation datum,
+ // so deleting ourself will also delete it and remove it
+ // ...Why don't all mutations delete on loss? Not sure.
qdel(src)
return 0
return 1
@@ -136,12 +139,21 @@
overlays_standing[CM.layer_used] = mut_overlay
apply_overlay(CM.layer_used)
+/**
+ * Called when a chromosome is applied so we can properly update some stats
+ * without having to remove and reapply the mutation from someone
+ *
+ * Returns `null` if no modification was done, and
+ * returns an instance of a power if modification was complete
+ */
/datum/mutation/human/proc/modify() //called when a genome is applied so we can properly update some stats without having to remove and reapply the mutation from someone
- if(modified || !power || !owner)
+ if(modified || !power_path || !owner)
return
- power.charge_max *= GET_MUTATION_ENERGY(src)
- power.charge_counter *= GET_MUTATION_ENERGY(src)
- modified = TRUE
+ var/datum/action/cooldown/spell/modified_power = locate(power_path) in owner.actions
+ if(!modified_power)
+ CRASH("Genetic mutation [type] called modify(), but could not find a action to modify!")
+ modified_power.cooldown_time *= GET_MUTATION_ENERGY(src) // Doesn't do anything for mutations with energy_coeff unset
+ return modified_power
/datum/mutation/human/proc/copy_mutation(datum/mutation/human/HM)
if(!HM)
@@ -170,15 +182,20 @@
else
qdel(src)
-/datum/mutation/human/proc/grant_spell()
- if(!ispath(power) || !owner)
+/datum/mutation/human/proc/grant_power()
+ if(!ispath(power_path) || !owner)
return FALSE
- power = new power()
- power.action_background_icon_state = "bg_tech_blue_on"
- power.panel = "Genetic"
- owner.AddSpell(power)
- return TRUE
+ var/datum/action/cooldown/new_power = new power_path(src)
+ new_power.background_icon_state = "bg_tech_blue"
+ new_power.base_background_icon_state = new_power.background_icon_state
+ new_power.active_background_icon_state = "[new_power.base_background_icon_state]_active"
+ new_power.overlay_icon_state = "bg_tech_blue_border"
+ new_power.active_overlay_icon_state = null
+ new_power.panel = "Genetic"
+ new_power.Grant(owner)
+
+ return new_power
// Runs through all the coefficients and uses this to determine which chromosomes the
// mutation can take. Stores these as text strings in a list.
diff --git a/code/datums/mutations/actions.dm b/code/datums/mutations/actions.dm
deleted file mode 100644
index 2eab0f87ac7f..000000000000
--- a/code/datums/mutations/actions.dm
+++ /dev/null
@@ -1,110 +0,0 @@
-/datum/mutation/human/telepathy
- name = "Telepathy"
- desc = "A rare mutation that allows the user to telepathically communicate to others."
- quality = POSITIVE
- text_gain_indication = span_notice("You can hear your own voice echoing in your mind!")
- text_lose_indication = span_notice("You don't hear your mind echo anymore.")
- difficulty = 12
- power = /obj/effect/proc_holder/spell/targeted/telepathy
- instability = 10
- energy_coeff = 1
-
-/datum/mutation/human/firebreath
- name = "Fire Breath"
- desc = "An ancient mutation that gives lizards breath of fire."
- quality = POSITIVE
- difficulty = 12
- locked = TRUE
- text_gain_indication = span_notice("Your throat is burning!")
- text_lose_indication = span_notice("Your throat is cooling down.")
- power = /obj/effect/proc_holder/spell/aimed/firebreath
- instability = 30
- energy_coeff = 1
- power_coeff = 1
- conflicts = list(/datum/mutation/human/cryokinesis)
-
-/datum/mutation/human/firebreath/modify()
- if(power)
- var/obj/effect/proc_holder/spell/aimed/firebreath/S = power
- S.strength = min(GET_MUTATION_POWER(src), S.strengthMax)
-
-/obj/effect/proc_holder/spell/aimed/firebreath
- name = "Fire Breath"
- desc = "You can breathe fire at a target."
- school = "evocation"
- charge_max = 450
- clothes_req = FALSE
- antimagic_allowed = TRUE
- range = 20
- projectile_type = /obj/item/projectile/magic/aoe/fireball/firebreath
- base_icon_state = "fireball"
- action_icon_state = "fireball0"
- sound = 'sound/magic/demon_dies.ogg' //horrifying lizard noises
- active_msg = "You build up heat in your mouth."
- deactive_msg = "You swallow the flame."
- var/strength = 1
- var/const/strengthMax = 3
-
-/obj/effect/proc_holder/spell/aimed/firebreath/before_cast(list/targets)
- . = ..()
- if(iscarbon(usr))
- var/mob/living/carbon/C = usr
- if(C.is_mouth_covered())
- C.adjust_fire_stacks(2)
- C.IgniteMob()
- to_chat(C,span_warning("Something in front of your mouth caught fire!"))
- return FALSE
-
-/obj/effect/proc_holder/spell/aimed/firebreath/ready_projectile(obj/item/projectile/P, atom/target, mob/user, iteration)
- if(!istype(P, /obj/item/projectile/magic/aoe/fireball))
- return
- var/obj/item/projectile/magic/aoe/fireball/F = P
- F.exp_fire += (strength - 1) * 4 // +0, +2 if empowered
-
-obj/effect/proc_holder/spell/aimed/firebreath/fire_projectile(mob/user)
- . = ..()
- message_admins("[ADMIN_LOOKUPFLW(user)] has shot firebreath at [ADMIN_VERBOSEJMP(user)]")
-
-/obj/item/projectile/magic/aoe/fireball/firebreath
- name = "fire breath"
- exp_heavy = 0
- exp_light = 0
- exp_flash = 0
- exp_fire= 3
-
-/datum/mutation/human/void
- name = "Void Magnet"
- desc = "A rare genome that attracts odd forces not usually observed."
- quality = MINOR_NEGATIVE //upsides and downsides
- text_gain_indication = span_notice("You feel a heavy, dull force just beyond the walls watching you.")
- instability = 30
- power = /obj/effect/proc_holder/spell/self/void
- energy_coeff = 1
- synchronizer_coeff = 1
-
-/datum/mutation/human/void/on_life()
- if(!isturf(owner.loc))
- return
- if(prob((0.5+((100-dna.stability)/20))) * GET_MUTATION_SYNCHRONIZER(src)) //very rare, but enough to annoy you hopefully. +0.5 probability for every 10 points lost in stability
- owner.apply_status_effect(STATUS_EFFECT_VOIDED)
-
-/obj/effect/proc_holder/spell/self/void
- name = "Convoke Void" //magic the gathering joke here
- desc = "A rare genome that attracts odd forces not usually observed. May sometimes pull you in randomly."
- school = "evocation"
- clothes_req = FALSE
- antimagic_allowed = TRUE
- charge_max = 600
- invocation = "DOOOOOOOOOOOOOOOOOOOOM!!!"
- invocation_type = SPELL_INVOCATION_SAY
- action_icon = 'icons/mob/actions/humble/actions_humble.dmi'
- action_icon_state = "void_magnet"
-
-/obj/effect/proc_holder/spell/self/void/can_cast(mob/living/user = usr)
- . = ..()
- if(!isturf(user.loc))
- return FALSE
-
-/obj/effect/proc_holder/spell/self/void/cast(mob/living/user = usr)
- . = ..()
- user.apply_status_effect(STATUS_EFFECT_VOIDED)
diff --git a/code/datums/mutations/antenna.dm b/code/datums/mutations/antenna.dm
index 22209cec639e..a211e2ec9748 100644
--- a/code/datums/mutations/antenna.dm
+++ b/code/datums/mutations/antenna.dm
@@ -34,60 +34,80 @@
quality = POSITIVE
text_gain_indication = span_notice("You hear distant voices at the corners of your mind.")
text_lose_indication = span_notice("The distant voices fade.")
- power = /obj/effect/proc_holder/spell/targeted/mindread
+ power_path = /datum/action/cooldown/spell/pointed/mindread
instability = 40
difficulty = 8
locked = TRUE
-/obj/effect/proc_holder/spell/targeted/mindread
+/datum/action/cooldown/spell/pointed/mindread
name = "Mindread"
desc = "Read the target's mind."
- charge_max = 50
- range = 7
- clothes_req = FALSE
- antimagic_allowed = TRUE
- action_icon_state = "mindread"
-
-/obj/effect/proc_holder/spell/targeted/mindread/cast(list/targets, mob/living/carbon/human/user = usr)
- for(var/mob/living/M in targets)
- if(usr.anti_magic_check(FALSE, FALSE, TRUE, 0) || M.anti_magic_check(FALSE, FALSE, TRUE, 0))
- to_chat(usr, span_warning("As you reach out with your mind, you're suddenly stopped by a vision of a massive tinfoil wall that stretches beyond visible range. It seems you've been foiled."))
- return
- if(M.stat == DEAD)
- to_chat(user, span_boldnotice("[M] is dead!"))
- return
- if(M.mind)
- to_chat(user, span_boldnotice("You plunge into [M]'s mind..."))
- if(prob(20))
- to_chat(M, span_danger("You feel something foreign enter your mind."))//chance to alert the read-ee
- var/list/recent_speech = list()
- var/list/say_log = list()
- var/log_source = M.logging
- for(var/log_type in log_source)//this whole loop puts the read-ee's say logs into say_log in an easy to access way
- var/nlog_type = text2num(log_type)
- if(nlog_type & LOG_SAY)
- var/list/reversed = log_source[log_type]
- if(islist(reversed))
- say_log = reverseRange(reversed.Copy())
- break
- if(LAZYLEN(say_log))
- for(var/spoken_memory in say_log)
- if(recent_speech.len >= 3)//up to 3 random lines of speech, favoring more recent speech
- break
- if(prob(50))
- recent_speech[spoken_memory] = say_log[spoken_memory]
- if(recent_speech.len)
- to_chat(user, span_boldnotice("You catch some drifting memories of their past conversations..."))
- for(var/spoken_memory in recent_speech)
- to_chat(user, span_notice("[recent_speech[spoken_memory]]"))
- if(iscarbon(M))
- var/mob/living/carbon/human/H = M
- to_chat(user, span_boldnotice("You find that their intent is to [H.a_intent]..."))
- var/datum/dna/the_dna = H.has_dna()
- if(the_dna)
- to_chat(user, span_boldnotice("You uncover that [H.p_their()] true identity is [the_dna.real_name]."))
- else
- to_chat(user, span_boldnotice("You can't find a mind to read inside of [M]."))
+ button_icon_state = "mindread"
+ cooldown_time = 5 SECONDS
+ spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC
+ antimagic_flags = MAGIC_RESISTANCE_MIND
+ ranged_mousepointer = 'icons/effects/mouse_pointers/mindswap_target.dmi'
+
+/datum/action/cooldown/spell/pointed/mindread/is_valid_target(atom/cast_on)
+ if(!isliving(cast_on))
+ return FALSE
+ var/mob/living/living_cast_on = cast_on
+ if(!living_cast_on.mind)
+ to_chat(owner, span_warning("[cast_on] has no mind to read!"))
+ return FALSE
+ if(living_cast_on.stat == DEAD)
+ to_chat(owner, span_warning("[cast_on] is dead!"))
+ return FALSE
+
+ return TRUE
+
+/datum/action/cooldown/spell/pointed/mindread/cast(mob/living/cast_on)
+ . = ..()
+ if(cast_on.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0))
+ to_chat(owner, span_warning("As you reach into [cast_on]'s mind, \
+ you are stopped by a mental blockage. It seems you've been foiled."))
+ return
+
+ if(cast_on == owner)
+ to_chat(owner, span_warning("You plunge into your mind... Yep, it's your mind."))
+ return
+
+ to_chat(owner, span_boldnotice("You plunge into [cast_on]'s mind..."))
+ if(prob(20))
+ // chance to alert the read-ee
+ to_chat(cast_on, span_danger("You feel something foreign enter your mind."))
+
+ var/list/recent_speech = list()
+ var/list/say_log = list()
+ var/log_source = cast_on.logging
+ //this whole loop puts the read-ee's say logs into say_log in an easy to access way
+ for(var/log_type in log_source)
+ var/nlog_type = text2num(log_type)
+ if(nlog_type & LOG_SAY)
+ var/list/reversed = log_source[log_type]
+ if(islist(reversed))
+ say_log = reverse_range(reversed.Copy())
+ break
+
+ for(var/spoken_memory in say_log)
+ //up to 3 random lines of speech, favoring more recent speech
+ if(length(recent_speech) >= 3)
+ break
+ if(prob(50))
+ continue
+ // log messages with tags like telepathy are displayed like "(Telepathy to Ckey/(target)) "greetings"""
+ // by splitting the text by using a " delimiter, we can grab JUST the greetings part
+ recent_speech[spoken_memory] = splittext(say_log[spoken_memory], "\"", 1, 0, TRUE)[3]
+
+ if(length(recent_speech))
+ to_chat(owner, span_boldnotice("You catch some drifting memories of their past conversations..."))
+ for(var/spoken_memory in recent_speech)
+ to_chat(owner, span_notice("[recent_speech[spoken_memory]]"))
+
+ if(iscarbon(cast_on))
+ var/mob/living/carbon/carbon_cast_on = cast_on
+ to_chat(owner, span_boldnotice("You find that their intent is to [carbon_cast_on.a_intent]..."))
+ to_chat(owner, span_boldnotice("You uncover that [carbon_cast_on.p_their()] true identity is [carbon_cast_on.mind.name]."))
/datum/mutation/human/mindreader/New(class_ = MUT_OTHER, timer, datum/mutation/human/copymut)
..()
diff --git a/code/datums/mutations/autotomy.dm b/code/datums/mutations/autotomy.dm
new file mode 100644
index 000000000000..fbf25ffa7e78
--- /dev/null
+++ b/code/datums/mutations/autotomy.dm
@@ -0,0 +1,42 @@
+/datum/mutation/human/self_amputation
+ name = "Autotomy"
+ desc = "Allows a creature to voluntary discard a random appendage."
+ quality = POSITIVE
+ text_gain_indication = span_notice("Your joints feel loose.")
+ instability = 30
+ power_path = /datum/action/cooldown/spell/self_amputation
+
+ energy_coeff = 1
+ synchronizer_coeff = 1
+
+/datum/action/cooldown/spell/self_amputation
+ name = "Drop a limb"
+ desc = "Concentrate to make a random limb pop right off your body."
+ button_icon_state = "autotomy"
+
+ cooldown_time = 10 SECONDS
+ spell_requirements = NONE
+
+/datum/action/cooldown/spell/self_amputation/is_valid_target(atom/cast_on)
+ return iscarbon(cast_on)
+
+/datum/action/cooldown/spell/self_amputation/cast(mob/living/carbon/cast_on)
+ . = ..()
+ if(HAS_TRAIT(cast_on, TRAIT_NODISMEMBER))
+ to_chat(cast_on, span_notice("You concentrate really hard, but nothing happens."))
+ return
+
+ var/list/parts = list()
+ for(var/obj/item/bodypart/to_remove as anything in cast_on.bodyparts)
+ if(to_remove.body_zone == BODY_ZONE_HEAD || to_remove.body_zone == BODY_ZONE_CHEST)
+ continue
+ if(!to_remove.dismemberable)
+ continue
+ parts += to_remove
+
+ if(!length(parts))
+ to_chat(cast_on, span_notice("You can't shed any more limbs!"))
+ return
+
+ var/obj/item/bodypart/to_remove = pick(parts)
+ to_remove.dismember()
\ No newline at end of file
diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm
index 50a6454494d4..1543df3d7b73 100644
--- a/code/datums/mutations/body.dm
+++ b/code/datums/mutations/body.dm
@@ -13,14 +13,15 @@
if(prob(1 * GET_MUTATION_SYNCHRONIZER(src)) && owner.stat == CONSCIOUS)
owner.visible_message(span_danger("[owner] starts having a seizure!"), span_userdanger("You have a seizure!"))
owner.Unconscious(200 * GET_MUTATION_POWER(src))
- owner.Jitter(1000 * GET_MUTATION_POWER(src))
+ owner.adjust_jitter(1000 * GET_MUTATION_POWER(src))
SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "epilepsy", /datum/mood_event/epilepsy)
addtimer(CALLBACK(src, PROC_REF(jitter_less)), 90)
/datum/mutation/human/epilepsy/proc/jitter_less()
- if(owner)
- owner.jitteriness = 10
+ if(QDELETED(owner))
+ return
+ owner.set_jitter(20 SECONDS)
//Unstable DNA induces random mutations!
/datum/mutation/human/bad_dna
@@ -77,7 +78,7 @@
if(prob(5) && owner.stat == CONSCIOUS)
owner.emote("scream")
if(prob(25))
- owner.hallucination += 20
+ owner.adjust_hallucinations(20 SECONDS)
//Dwarfism shrinks your body and lets you pass tables.
/datum/mutation/human/dwarfism
@@ -205,6 +206,7 @@
glowth = new(owner)
modify()
+// Override modify here without a parent call, because we don't actually give an action.
/datum/mutation/human/glow/modify()
if(!glowth)
return
@@ -321,7 +323,7 @@
/datum/mutation/human/fire/on_life()
if(prob((1+(100-dna.stability)/10)) * GET_MUTATION_SYNCHRONIZER(src))
owner.adjust_fire_stacks(2 * GET_MUTATION_POWER(src))
- owner.IgniteMob()
+ owner.ignite_mob()
/datum/mutation/human/fire/on_acquiring(mob/living/carbon/human/owner)
if(..())
diff --git a/code/datums/mutations/cold.dm b/code/datums/mutations/cold.dm
index 11bdf0d75898..a5af2739b14f 100644
--- a/code/datums/mutations/cold.dm
+++ b/code/datums/mutations/cold.dm
@@ -6,17 +6,16 @@
instability = 10
difficulty = 10
synchronizer_coeff = 1
- power = /obj/effect/proc_holder/spell/targeted/conjure_item/snow
+ power_path = /datum/action/cooldown/spell/conjure_item/snow
-/obj/effect/proc_holder/spell/targeted/conjure_item/snow
+/datum/action/cooldown/spell/conjure_item/snow
name = "Create Snow"
desc = "Concentrates cryokinetic forces to create snow, useful for snow-like construction."
- item_type = /obj/item/stack/sheet/mineral/snow
- charge_max = 50
+ button_icon_state = "snow"
+ cooldown_time = 5 SECONDS
+ spell_requirements = NONE
delete_old = FALSE
- antimagic_allowed = TRUE
- action_icon_state = "snow"
-
+ item_type = /obj/item/stack/sheet/mineral/snow
/datum/mutation/human/cryokinesis
name = "Cryokinesis"
@@ -26,21 +25,20 @@
instability = 20
difficulty = 12
synchronizer_coeff = 1
- power = /obj/effect/proc_holder/spell/aimed/cryo
+ power_path = /datum/action/cooldown/spell/pointed/projectile/cryo
conflicts = list(/datum/mutation/human/firebreath)
-/obj/effect/proc_holder/spell/aimed/cryo
+/datum/action/cooldown/spell/pointed/projectile/cryo
name = "Cryobeam"
desc = "This power fires a frozen bolt at a target."
- charge_max = 150
- cooldown_min = 150
- clothes_req = FALSE
- antimagic_allowed = TRUE
- range = 3
- projectile_type = /obj/item/projectile/temp/cryo
+ button_icon_state = "icebeam"
base_icon_state = "icebeam"
- action_icon_state = "icebeam"
+ active_overlay_icon_state = "bg_spell_border_active_blue"
+ cooldown_time = 16 SECONDS
+ spell_requirements = NONE
+ antimagic_flags = NONE
+
active_msg = "You focus your cryokinesis!"
deactive_msg = "You relax."
- active = FALSE
+ projectile_type = /obj/item/projectile/temp/cryo
diff --git a/code/datums/mutations/fire_breath.dm b/code/datums/mutations/fire_breath.dm
new file mode 100644
index 000000000000..066a8e8ef48b
--- /dev/null
+++ b/code/datums/mutations/fire_breath.dm
@@ -0,0 +1,95 @@
+/datum/mutation/human/firebreath
+ name = "Fire Breath"
+ desc = "An ancient mutation that gives lizards breath of fire."
+ quality = POSITIVE
+ difficulty = 12
+ locked = TRUE
+ text_gain_indication = span_notice("Your throat is burning!")
+ text_lose_indication = span_notice("Your throat is cooling down.")
+ power_path = /datum/action/cooldown/spell/cone/staggered/fire_breath
+ instability = 30
+ energy_coeff = 1
+ power_coeff = 1
+
+/datum/mutation/human/firebreath/modify()
+ . = ..()
+ var/datum/action/cooldown/spell/cone/staggered/fire_breath/to_modify = .
+ if(!istype(to_modify)) // null or invalid
+ return
+
+ if(GET_MUTATION_POWER(src) <= 1) // we only care about power from here on
+ return
+
+ to_modify.cone_levels += 2 // Cone fwooshes further, and...
+ to_modify.self_throw_range += 1 // the breath throws the user back more
+
+/datum/action/cooldown/spell/cone/staggered/fire_breath
+ name = "Fire Breath"
+ desc = "You breathe a cone of fire directly in front of you."
+ button_icon_state = "fireball0"
+ sound = 'sound/magic/demon_dies.ogg' //horrifying lizard noises
+
+ school = SCHOOL_EVOCATION
+ cooldown_time = 40 SECONDS
+ invocation_type = INVOCATION_NONE
+ spell_requirements = NONE
+ antimagic_flags = NONE
+
+ cone_levels = 3
+ respect_density = TRUE
+ /// The range our user is thrown backwards after casting the spell
+ var/self_throw_range = 1
+
+/datum/action/cooldown/spell/cone/staggered/fire_breath/before_cast(atom/cast_on)
+ . = ..()
+ if(. & SPELL_CANCEL_CAST)
+ return
+
+ if(!iscarbon(cast_on))
+ return
+
+ var/mob/living/carbon/our_lizard = cast_on
+ if(!our_lizard.is_mouth_covered())
+ return
+
+ our_lizard.adjust_fire_stacks(cone_levels)
+ our_lizard.ignite_mob()
+ to_chat(our_lizard, span_warning("Something in front of your mouth catches fire!"))
+
+/datum/action/cooldown/spell/cone/staggered/fire_breath/after_cast(atom/cast_on)
+ . = ..()
+ if(!isliving(cast_on))
+ return
+
+ var/mob/living/living_cast_on = cast_on
+ // When casting, throw the caster backwards a few tiles.
+ var/original_dir = living_cast_on.dir
+ living_cast_on.throw_at(
+ get_edge_target_turf(living_cast_on, turn(living_cast_on.dir, 180)),
+ range = self_throw_range,
+ speed = 2,
+ )
+ // Try to set us to our original direction after, so we don't end up backwards.
+ living_cast_on.setDir(original_dir)
+
+/datum/action/cooldown/spell/cone/staggered/fire_breath/calculate_cone_shape(current_level)
+ // This makes the cone shoot out into a 3 wide column of flames.
+ // You may be wondering, "that equation doesn't seem like it'd make a 3 wide column"
+ // well it does, and that's all that matters.
+ return (2 * current_level) - 1
+
+/datum/action/cooldown/spell/cone/staggered/fire_breath/do_turf_cone_effect(turf/target_turf, atom/caster, level)
+ // Further turfs experience less exposed_temperature and exposed_volume
+ new /obj/effect/hotspot(target_turf) // for style
+ target_turf.hotspot_expose(max(500, 900 - (100 * level)), max(50, 200 - (50 * level)), 1)
+
+/datum/action/cooldown/spell/cone/staggered/fire_breath/do_mob_cone_effect(mob/living/target_mob, atom/caster, level)
+ // Further out targets take less immediate burn damage and get less fire stacks.
+ // The actual burn damage application is not blocked by fireproofing, like space dragons.
+ target_mob.apply_damage(max(10, 40 - (5 * level)), BURN)
+ target_mob.adjust_fire_stacks(max(2, 5 - level))
+ target_mob.ignite_mob()
+
+/datum/action/cooldown/spell/cone/staggered/firebreath/do_obj_cone_effect(obj/target_obj, atom/caster, level)
+ // Further out objects experience less exposed_temperature and exposed_volume
+ target_obj.fire_act(max(500, 900 - (100 * level)), max(50, 200 - (50 * level)))
diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm
index d5f9b4a99bc7..51c555bc4a5a 100644
--- a/code/datums/mutations/hulk.dm
+++ b/code/datums/mutations/hulk.dm
@@ -74,7 +74,7 @@
text_gain_indication = span_notice("Your muscles hurt!")
health_req = 1
var/health_based = 0
- power = /obj/effect/proc_holder/spell/aoe_turf/repulse/hulk
+ power_path = /datum/action/cooldown/spell/aoe/repulse/hulk
/datum/mutation/human/active_hulk/on_acquiring(mob/living/carbon/human/owner)
if(..())
@@ -102,7 +102,7 @@
/datum/mutation/human/active_hulk/on_attack_hand(atom/target, proximity)
if(proximity) //no telekinetic hulk attack
if(prob(3))
- owner.Jitter(10)
+ owner.adjust_jitter(10 SECONDS)
owner.adjustStaminaLoss(-0.5)
return target.attack_hulk(owner)
@@ -130,3 +130,17 @@
message = "[replacetext(message, ".", "!")]!!"
wrapped_message[1] = message
return COMPONENT_UPPERCASE_SPEECH
+
+/datum/action/cooldown/spell/aoe/repulse/hulk
+ name = "Ground Smash"
+ desc = "Smash the ground to throw your enemies back!"
+ invocation = "HULK SMASH!!"
+ button_icon = 'icons/mob/actions.dmi'
+ button_icon_state = "green_hand"
+
+/datum/action/cooldown/spell/aoe/repulse/hulk/cast_on_thing_in_aoe(atom/movable/victim, atom/caster)
+ var/turf/open/floor/turf = get_turf(victim)
+ if(istype(turf))
+ turf.break_tile()
+ playsound(usr.loc, 'sound/effects/meteorimpact.ogg', 30, TRUE, 2)
+ return ..()
diff --git a/code/datums/mutations/olfaction.dm b/code/datums/mutations/olfaction.dm
index 4342bff4048d..3b9e3817b073 100644
--- a/code/datums/mutations/olfaction.dm
+++ b/code/datums/mutations/olfaction.dm
@@ -8,7 +8,7 @@
instability = 30
synchronizer_coeff = 1
- var/datum/action/bloodsucker/olfaction/acquire_scent/lesser/smelling
+ var/datum/action/cooldown/bloodsucker/olfaction/acquire_scent/lesser/smelling
/datum/mutation/human/olfaction/on_acquiring(mob/living/carbon/human/owner)
. = ..()
diff --git a/code/datums/mutations/speech.dm b/code/datums/mutations/speech.dm
index b5dbbafc58ba..f90c634993ce 100644
--- a/code/datums/mutations/speech.dm
+++ b/code/datums/mutations/speech.dm
@@ -9,7 +9,7 @@
/datum/mutation/human/nervousness/on_life()
if(prob(10))
- owner.stuttering = max(10, owner.stuttering)
+ owner.set_stutter_if_lower(20 SECONDS)
/datum/mutation/human/wacky
diff --git a/code/datums/mutations/telepathy.dm b/code/datums/mutations/telepathy.dm
new file mode 100644
index 000000000000..de01fff9fb30
--- /dev/null
+++ b/code/datums/mutations/telepathy.dm
@@ -0,0 +1,10 @@
+/datum/mutation/human/telepathy
+ name = "Telepathy"
+ desc = "A rare mutation that allows the user to telepathically communicate to others."
+ quality = POSITIVE
+ text_gain_indication = span_notice("You can hear your own voice echoing in your mind!")
+ text_lose_indication = span_notice("You don't hear your mind echo anymore.")
+ difficulty = 12
+ power_path = /datum/action/cooldown/spell/list_target/telepathy
+ instability = 10
+ energy_coeff = 1
\ No newline at end of file
diff --git a/code/datums/mutations/touch.dm b/code/datums/mutations/touch.dm
index a0d57e7a29eb..a694c69d324e 100644
--- a/code/datums/mutations/touch.dm
+++ b/code/datums/mutations/touch.dm
@@ -6,28 +6,54 @@
difficulty = 16
text_gain_indication = span_notice("You feel power flow through your hands.")
text_lose_indication = span_notice("The energy in your hands subsides.")
- power = /obj/effect/proc_holder/spell/targeted/touch/shock
+ power_path = /datum/action/cooldown/spell/touch/shock
instability = 20
locked = TRUE
conflicts = list(/datum/mutation/human/shock/far, /datum/mutation/human/insulated)
-/obj/effect/proc_holder/spell/targeted/touch/shock
+/datum/action/cooldown/spell/touch/shock
name = "Shock Touch"
desc = "Channel electricity to your hand to shock people with."
- drawmessage = "You channel electricity into your hand."
- dropmessage = "You let the electricity from your hand dissipate."
hand_path = /obj/item/melee/touch_attack/shock
- charge_max = 100
- clothes_req = FALSE
- antimagic_allowed = TRUE
- icon = 'icons/mob/actions/humble/actions_humble.dmi'
- action_icon_state = "zap"
+ button_icon_state = "zap"
+ sound = 'sound/weapons/zapbang.ogg'
+ cooldown_time = 10 SECONDS
+ invocation_type = INVOCATION_NONE
+ spell_requirements = NONE
+
+ button_icon = 'icons/mob/actions/humble/actions_humble.dmi'
+ draw_message = span_notice("You channel electricity into your hand.")
+ drop_message = span_notice("You let the electricity from your hand dissipate.")
+
+
+/datum/action/cooldown/spell/touch/shock/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
+ if(iscarbon(victim))
+ var/mob/living/carbon/carbon_victim = victim
+ if(carbon_victim.electrocute_act(15, caster, 1, stun = FALSE))//doesnt stun. never let this stun
+ carbon_victim.dropItemToGround(carbon_victim.get_active_held_item())
+ carbon_victim.dropItemToGround(carbon_victim.get_inactive_held_item())
+ carbon_victim.adjust_timed_status_effect(15 SECONDS, /datum/status_effect/confusion)
+ carbon_victim.visible_message(
+ span_danger("[caster] electrocutes [victim]!"),
+ span_userdanger("[caster] electrocutes you!"),
+ )
+ return TRUE
+
+ else if(isliving(victim))
+ var/mob/living/living_victim = victim
+ if(living_victim.electrocute_act(15, caster, 1, stun = FALSE))
+ living_victim.visible_message(
+ span_danger("[caster] electrocutes [victim]!"),
+ span_userdanger("[caster] electrocutes you!"),
+ )
+ return TRUE
+
+ to_chat(caster, span_warning("The electricity doesn't seem to affect [victim]..."))
+ return TRUE
/obj/item/melee/touch_attack/shock
name = "\improper shock touch"
desc = "This is kind of like when you rub your feet on a shag rug so you can zap your friends, only a lot less safe."
- catchphrase = null
- on_use_sound = 'sound/weapons/zapbang.ogg'
icon_state = "zapper"
item_state = "zapper"
var/far = FALSE
@@ -42,7 +68,7 @@
user.Beam(C, icon_state="red_lightning", time=15)
C.dropItemToGround(C.get_active_held_item())
C.dropItemToGround(C.get_inactive_held_item())
- C.confused += 15
+ C.adjust_confusion(15 SECONDS)
C.visible_message(span_danger("[user] electrocutes [target]!"),span_userdanger("[user] electrocutes you!"))
return ..()
else
diff --git a/code/datums/mutations/touchfar.dm b/code/datums/mutations/touchfar.dm
index 655b006ab4d3..883853f4d4d6 100644
--- a/code/datums/mutations/touchfar.dm
+++ b/code/datums/mutations/touchfar.dm
@@ -2,11 +2,11 @@
name = "Extended Shock Touch"
desc = "The affected can channel excess electricity through their hands without shocking themselves, allowing them to shock others at range."
instability = 30
- text_gain_indication = "You feel unlimited power flow through your hands."
- power = /obj/effect/proc_holder/spell/targeted/touch/shock/far
+ text_gain_indication = span_notice("You feel unlimited power flow through your hands.")
+ power_path = /datum/action/cooldown/spell/touch/shock/far
conflicts = list(/datum/mutation/human/shock, /datum/mutation/human/insulated)
-/obj/effect/proc_holder/spell/targeted/touch/shock/far
+/datum/action/cooldown/spell/touch/shock/far
name = "Extended Shock Touch"
hand_path = /obj/item/melee/touch_attack/shock/far
diff --git a/code/datums/mutations/void_magnet.dm b/code/datums/mutations/void_magnet.dm
new file mode 100644
index 000000000000..fd6e719914e5
--- /dev/null
+++ b/code/datums/mutations/void_magnet.dm
@@ -0,0 +1,43 @@
+/datum/mutation/human/void
+ name = "Void Magnet"
+ desc = "A rare genome that attracts odd forces not usually observed."
+ quality = MINOR_NEGATIVE //upsides and downsides
+ text_gain_indication = "You feel a heavy, dull force just beyond the walls watching you."
+ instability = 30
+ power_path = /datum/action/cooldown/spell/void
+ energy_coeff = 1
+ synchronizer_coeff = 1
+
+/datum/mutation/human/void/on_life(delta_time, times_fired)
+ // Move this onto the spell itself at some point?
+ var/datum/action/cooldown/spell/void/curse = locate(power_path) in owner
+ if(!curse)
+ remove()
+ return
+
+ if(!curse.is_valid_target(owner))
+ return
+
+ //very rare, but enough to annoy you hopefully. + 0.5 probability for every 10 points lost in stability
+ if(DT_PROB((0.25 + ((100 - dna.stability) / 40)) * GET_MUTATION_SYNCHRONIZER(src), delta_time))
+ curse.cast(owner)
+
+/datum/action/cooldown/spell/void
+ name = "Convoke Void" //magic the gathering joke here
+ desc = "A rare genome that attracts odd forces not usually observed. May sometimes pull you in randomly."
+ button_icon_state = "void_magnet"
+
+ school = SCHOOL_EVOCATION
+ cooldown_time = 1 MINUTES
+
+ invocation = "DOOOOOOOOOOOOOOOOOOOOM!!!"
+ invocation_type = INVOCATION_SHOUT
+ spell_requirements = NONE
+ antimagic_flags = NONE
+
+/datum/action/cooldown/spell/void/is_valid_target(atom/cast_on)
+ return isturf(cast_on.loc)
+
+/datum/action/cooldown/spell/void/cast(atom/cast_on)
+ . = ..()
+ new /obj/effect/immortality_talisman/void(get_turf(cast_on), cast_on)
\ No newline at end of file
diff --git a/code/datums/outfit.dm b/code/datums/outfit.dm
index 8038faf527d1..850e8d1ed837 100755
--- a/code/datums/outfit.dm
+++ b/code/datums/outfit.dm
@@ -183,7 +183,7 @@
if(id)
H.equip_to_slot_or_del(SSwardrobe.provide_type(id, H), SLOT_WEAR_ID, TRUE)
if(suit_store)
- H.equip_to_slot_or_del(SSwardrobe.provide_type(suit_store, H), SLOT_S_STORE, TRUE)
+ H.equip_to_slot_or_del(SSwardrobe.provide_type(suit_store, H), SLOT_SUIT_STORE, TRUE)
if(accessory)
var/obj/item/clothing/under/U = H.w_uniform
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs/buffs.dm
similarity index 97%
rename from code/datums/status_effects/buffs.dm
rename to code/datums/status_effects/buffs/buffs.dm
index 0fd5f0bc7fff..43e79bd43acc 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs/buffs.dm
@@ -382,36 +382,36 @@
STOP_PROCESSING(SSprocessing, src)
//Hippocratic Oath: Applied when the Rod of Asclepius is activated.
-/datum/status_effect/hippocraticOath
+/datum/status_effect/hippocratic_oath
id = "Hippocratic Oath"
status_type = STATUS_EFFECT_UNIQUE
duration = -1
- tick_interval = 25
- examine_text = span_notice("They seem to have an aura of healing and helpfulness about them.")
+ tick_interval = 2.5 SECONDS
alert_type = null
+
var/hand
var/deathTick = 0
var/efficiency = 1
var/rod_type = /obj/item/rod_of_asclepius
-/datum/status_effect/hippocraticOath/on_creation(mob/living/new_owner, _efficiency, _rod_type)
+/datum/status_effect/hippocratic_oath/on_creation(mob/living/new_owner, _efficiency, _rod_type)
efficiency = _efficiency
rod_type = _rod_type
. = ..()
-/datum/status_effect/hippocraticOath/on_apply()
+/datum/status_effect/hippocratic_oath/on_apply()
//Makes the user passive, it's in their oath not to harm!
ADD_TRAIT(owner, TRAIT_PACIFISM, "hippocraticOath")
var/datum/atom_hud/H = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- H.add_hud_to(owner)
+ H.show_to(owner)
return ..()
-/datum/status_effect/hippocraticOath/on_remove()
+/datum/status_effect/hippocratic_oath/on_remove()
REMOVE_TRAIT(owner, TRAIT_PACIFISM, "hippocraticOath")
var/datum/atom_hud/H = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- H.remove_hud_from(owner)
+ H.hide_from(owner)
-/datum/status_effect/hippocraticOath/tick()
+/datum/status_effect/hippocratic_oath/tick()
if(owner.stat == DEAD)
if(deathTick < 4)
deathTick += 1
@@ -488,9 +488,10 @@
status_type = STATUS_EFFECT_REFRESH
/datum/status_effect/good_music/tick()
- owner.dizziness = max(0, owner.dizziness - 2)
- owner.jitteriness = max(0, owner.jitteriness - 2)
- owner.confused = max(0, owner.confused - 1)
+ if(owner.can_hear())
+ owner.adjust_dizzy(-4 SECONDS)
+ owner.adjust_jitter(-4 SECONDS)
+ owner.adjust_confusion(-1 SECONDS)
SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "goodmusic", /datum/mood_event/goodmusic)
/atom/movable/screen/alert/status_effect/regenerative_core
@@ -550,7 +551,7 @@
return
src.darkspawn = darkspawn
-/datum/status_effect/creep/process()
+/datum/status_effect/creep/tick()
if(!darkspawn)
qdel(src)
return
diff --git a/code/datums/status_effects/debuffs/confusion.dm b/code/datums/status_effects/debuffs/confusion.dm
new file mode 100644
index 000000000000..b4d4b29b65c3
--- /dev/null
+++ b/code/datums/status_effects/debuffs/confusion.dm
@@ -0,0 +1,48 @@
+/// The threshold in which all of our movements are fully randomized, in seconds.
+#define CONFUSION_FULL_THRESHOLD 40
+/// A multiplier applied on how much time is left (in seconds) that determines the chance of moving sideways randomly
+#define CONFUSION_SIDEWAYS_MOVE_PROB_PER_SECOND 1.5
+/// A multiplier applied on how much time is left (in seconds) that determines the chance of moving diagonally randomly
+#define CONFUSION_DIAGONAL_MOVE_PROB_PER_SECOND 3
+
+/// A status effect used for adding confusion to a mob.
+/datum/status_effect/confusion
+ id = "confusion"
+ alert_type = null
+ remove_on_fullheal = TRUE
+
+/datum/status_effect/confusion/on_creation(mob/living/new_owner, duration = 10 SECONDS)
+ src.duration = duration
+ return ..()
+
+/datum/status_effect/confusion/on_apply()
+ RegisterSignal(owner, COMSIG_MOB_CLIENT_PRE_MOVE, PROC_REF(on_move))
+ return TRUE
+
+/datum/status_effect/confusion/on_remove()
+ UnregisterSignal(owner, COMSIG_MOB_CLIENT_PRE_MOVE)
+
+/// Signal proc for [COMSIG_MOB_CLIENT_PRE_MOVE]. We have a chance to mix up our movement pre-move with confusion.
+/datum/status_effect/confusion/proc/on_move(datum/source, direction)
+ SIGNAL_HANDLER
+
+ // How much time is left in the duration, in seconds.
+ var/time_left = (duration - world.time) / 10
+ var/new_dir
+
+ if(time_left > CONFUSION_FULL_THRESHOLD)
+ new_dir = pick(GLOB.alldirs)
+
+ else if(prob(time_left * CONFUSION_SIDEWAYS_MOVE_PROB_PER_SECOND))
+ new_dir = angle2dir(dir2angle(direction) + pick(90, -90))
+
+ else if(prob(time_left * CONFUSION_DIAGONAL_MOVE_PROB_PER_SECOND))
+ new_dir = angle2dir(dir2angle(direction) + pick(45, -45))
+
+ if(!isnull(new_dir))
+ source = get_step(owner, new_dir)
+ direction = new_dir
+
+#undef CONFUSION_FULL_THRESHOLD
+#undef CONFUSION_SIDEWAYS_MOVE_PROB_PER_SECOND
+#undef CONFUSION_DIAGONAL_MOVE_PROB_PER_SECOND
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm
similarity index 84%
rename from code/datums/status_effects/debuffs.dm
rename to code/datums/status_effects/debuffs/debuffs.dm
index 9b3b010013c4..46c3bdda96fd 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs/debuffs.dm
@@ -4,78 +4,223 @@
tick_interval = 0
status_type = STATUS_EFFECT_REPLACE
alert_type = null
+ remove_on_fullheal = TRUE
+ //heal_flag_necessary = HEAL_CC_STATUS
var/needs_update_stat = FALSE
/datum/status_effect/incapacitating/on_creation(mob/living/new_owner, set_duration, updating_canmove)
if(isnum(set_duration))
duration = set_duration
. = ..()
- if(.)
- if(updating_canmove)
- owner.update_mobility()
- if(needs_update_stat || issilicon(owner))
- owner.update_stat()
+ if(. && updating_canmove)
+ owner.update_mobility()
+ if(needs_update_stat || issilicon(owner))
+ owner.update_stat()
+
/datum/status_effect/incapacitating/on_remove()
owner.update_mobility()
if(needs_update_stat || issilicon(owner)) //silicons need stat updates in addition to normal canmove updates
owner.update_stat()
+ return ..()
//STUN
/datum/status_effect/incapacitating/stun
id = "stun"
+/datum/status_effect/incapacitating/stun/on_apply()
+ . = ..()
+ if(!.)
+ return
+ owner.add_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), TRAIT_STATUS_EFFECT(id))
+
+/datum/status_effect/incapacitating/stun/on_remove()
+ owner.remove_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), TRAIT_STATUS_EFFECT(id))
+ return ..()
+
+
//KNOCKDOWN
/datum/status_effect/incapacitating/knockdown
id = "knockdown"
+/datum/status_effect/incapacitating/knockdown/on_apply()
+ . = ..()
+ if(!.)
+ return
+ ADD_TRAIT(owner, TRAIT_FLOORED, TRAIT_STATUS_EFFECT(id))
+
+/datum/status_effect/incapacitating/knockdown/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_FLOORED, TRAIT_STATUS_EFFECT(id))
+ return ..()
+
+
//IMMOBILIZED
/datum/status_effect/incapacitating/immobilized
id = "immobilized"
+/datum/status_effect/incapacitating/immobilized/on_apply()
+ . = ..()
+ if(!.)
+ return
+ ADD_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id))
+
+/datum/status_effect/incapacitating/immobilized/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id))
+ return ..()
+
+
+//PARALYZED
/datum/status_effect/incapacitating/paralyzed
id = "paralyzed"
+/datum/status_effect/incapacitating/paralyzed/on_apply()
+ . = ..()
+ if(!.)
+ return
+ owner.add_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_FLOORED, TRAIT_HANDS_BLOCKED), TRAIT_STATUS_EFFECT(id))
+
+/datum/status_effect/incapacitating/paralyzed/on_remove()
+ owner.remove_traits(list(TRAIT_INCAPACITATED, TRAIT_IMMOBILIZED, TRAIT_FLOORED, TRAIT_HANDS_BLOCKED), TRAIT_STATUS_EFFECT(id))
+ return ..()
+
+//INCAPACITATED
+/// This status effect represents anything that leaves a character unable to perform basic tasks (interrupting do-afters, for example), but doesn't incapacitate them further than that (no stuns etc..)
+/datum/status_effect/incapacitating/incapacitated
+ id = "incapacitated"
+
+// What happens when you get the incapacitated status. You get TRAIT_INCAPACITATED added to you for the duration of the status effect.
+/datum/status_effect/incapacitating/incapacitated/on_apply()
+ . = ..()
+ if(!.)
+ return
+ ADD_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id))
+
+// When the status effect runs out, your TRAIT_INCAPACITATED is removed.
+/datum/status_effect/incapacitating/incapacitated/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id))
+ return ..()
+
+
//UNCONSCIOUS
/datum/status_effect/incapacitating/unconscious
id = "unconscious"
needs_update_stat = TRUE
+/datum/status_effect/incapacitating/unconscious/on_apply()
+ . = ..()
+ if(!.)
+ return
+ ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
+
+/datum/status_effect/incapacitating/unconscious/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
+ return ..()
+
/datum/status_effect/incapacitating/unconscious/tick()
if(owner.getStaminaLoss())
owner.adjustStaminaLoss(-0.3) //reduce stamina loss by 0.3 per tick, 6 per 2 seconds
+
//SLEEPING
/datum/status_effect/incapacitating/sleeping
id = "sleeping"
alert_type = /atom/movable/screen/alert/status_effect/asleep
needs_update_stat = TRUE
- var/mob/living/carbon/carbon_owner
- var/mob/living/carbon/human/human_owner
+ tick_interval = 2 SECONDS
-/datum/status_effect/incapacitating/sleeping/on_creation(mob/living/new_owner, updating_canmove)
+/datum/status_effect/incapacitating/sleeping/on_apply()
. = ..()
- if(.)
- if(iscarbon(owner)) //to avoid repeated istypes
- carbon_owner = owner
- if(ishuman(owner))
- human_owner = owner
-
-/datum/status_effect/incapacitating/sleeping/Destroy()
- carbon_owner = null
- human_owner = null
+ if(!.)
+ return
+ if(!HAS_TRAIT(owner, TRAIT_SLEEPIMMUNE))
+ ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
+ tick_interval = -1
+ RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_SLEEPIMMUNE), PROC_REF(on_owner_insomniac))
+ RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_SLEEPIMMUNE), PROC_REF(on_owner_sleepy))
+
+/datum/status_effect/incapacitating/sleeping/on_remove()
+ UnregisterSignal(owner, list(SIGNAL_ADDTRAIT(TRAIT_SLEEPIMMUNE), SIGNAL_REMOVETRAIT(TRAIT_SLEEPIMMUNE)))
+ if(!HAS_TRAIT(owner, TRAIT_SLEEPIMMUNE))
+ REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
+ tick_interval = initial(tick_interval)
return ..()
+///If the mob is sleeping and gain the TRAIT_SLEEPIMMUNE we remove the TRAIT_KNOCKEDOUT and stop the tick() from happening
+/datum/status_effect/incapacitating/sleeping/proc/on_owner_insomniac(mob/living/source)
+ SIGNAL_HANDLER
+ REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
+ tick_interval = -1
+
+///If the mob has the TRAIT_SLEEPIMMUNE but somehow looses it we make him sleep and restart the tick()
+/datum/status_effect/incapacitating/sleeping/proc/on_owner_sleepy(mob/living/source)
+ SIGNAL_HANDLER
+ ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
+ tick_interval = initial(tick_interval)
+
+#define HEALING_SLEEP_DEFAULT 0.2
+
/datum/status_effect/incapacitating/sleeping/tick()
- if(owner.getStaminaLoss())
- owner.adjustStaminaLoss(-0.5) //reduce stamina loss by 0.5 per tick, 10 per 2 seconds
- if(human_owner && human_owner.drunkenness)
- human_owner.drunkenness *= 0.997 //reduce drunkenness by 0.3% per tick, 6% per 2 seconds
- if(prob(20))
- if(carbon_owner)
- carbon_owner.handle_dreams()
- if(prob(10) && owner.health > owner.crit_threshold)
- owner.emote("snore")
+ if(owner.maxHealth)
+ var/health_ratio = owner.health / owner.maxHealth
+ var/healing = HEALING_SLEEP_DEFAULT
+
+ // having high spirits helps us recover
+ var/datum/component/mood/mob_mood = owner.GetComponent(/datum/component/mood)
+ if(mob_mood)
+ switch(mob_mood.sanity_level)
+ if(SANITY_GREAT)
+ healing = 0.2
+ if(SANITY_NEUTRAL)
+ healing = 0.1
+ if(SANITY_DISTURBED)
+ healing = 0
+ if(SANITY_UNSTABLE)
+ healing = 0
+ if(SANITY_CRAZY)
+ healing = -0.1
+ if(SANITY_INSANE)
+ healing = -0.2
+
+ var/turf/rest_turf = get_turf(owner)
+ var/is_sleeping_in_darkness = rest_turf.get_lumcount() <= LIGHTING_TILE_IS_DARK
+
+ // sleeping with a blindfold or in the dark helps us rest
+ if(is_blind(owner) || is_sleeping_in_darkness)
+ healing += 0.1
+
+ // sleeping in silence is always better
+ if(HAS_TRAIT(owner, TRAIT_DEAF))
+ healing += 0.1
+
+ // check for beds
+ if((locate(/obj/structure/bed) in owner.loc))
+ healing += 0.2
+ else if((locate(/obj/structure/table) in owner.loc))
+ healing += 0.1
+
+ // don't forget the bedsheet
+ for(var/obj/item/bedsheet/bedsheet in range(owner.loc,0))
+ if(bedsheet.loc != owner.loc) //bedsheets in your backpack/neck don't give you comfort
+ continue
+ healing += 0.1
+ break //Only count the first bedsheet
+
+ if(healing > 0 && health_ratio > 0.8)
+ owner.adjustBruteLoss(-1 * healing, required_status = BODYPART_ORGANIC)
+ owner.adjustFireLoss(-1 * healing, required_status = BODYPART_ORGANIC)
+ owner.adjustToxLoss(-1 * healing * 0.5, TRUE, TRUE)
+ owner.adjustStaminaLoss(min(-1 * healing, -1 * HEALING_SLEEP_DEFAULT))
+ // Drunkenness gets reduced by 0.3% per tick (6% per 2 seconds)
+ owner.set_drunk_effect(owner.get_drunk_amount() * 0.997)
+
+ if(iscarbon(owner))
+ var/mob/living/carbon/carbon_owner = owner
+ carbon_owner.handle_dreams()
+
+ if(prob(2) && owner.health > owner.crit_threshold)
+ owner.emote("snore")
+
+#undef HEALING_SLEEP_DEFAULT
/atom/movable/screen/alert/status_effect/asleep
name = "Asleep"
@@ -87,7 +232,7 @@
/datum/status_effect/incapacitating/stasis
id = "stasis"
duration = -1
- tick_interval = 10
+ tick_interval = 1 SECONDS
alert_type = /atom/movable/screen/alert/status_effect/stasis
var/last_dead_time
/// What is added to the *life_tickrate*, -1 to freeze the ticks
@@ -105,16 +250,25 @@
/datum/status_effect/incapacitating/stasis/on_creation(mob/living/new_owner, set_duration, updating_canmove, new_stasis_mod)
. = ..()
- stasis_mod = new_stasis_mod
- new_owner.life_tickrate += stasis_mod
- update_time_of_death()
- if(stasis_mod == -1)
+ if(.)
+ update_time_of_death()
+ stasis_mod = new_stasis_mod
+ new_owner.life_tickrate += stasis_mod
owner.reagents?.end_metabolization(owner, FALSE)
+/datum/status_effect/incapacitating/stasis/on_apply()
+ . = ..()
+ if(!.)
+ return
+ ADD_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id))
+ ADD_TRAIT(owner, TRAIT_HANDS_BLOCKED, TRAIT_STATUS_EFFECT(id))
+
/datum/status_effect/incapacitating/stasis/tick()
update_time_of_death()
/datum/status_effect/incapacitating/stasis/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id))
+ REMOVE_TRAIT(owner, TRAIT_HANDS_BLOCKED, TRAIT_STATUS_EFFECT(id))
update_time_of_death()
owner.life_tickrate -= stasis_mod
return ..()
@@ -308,14 +462,10 @@
else if(prob(severity * 2))
warned_outofsight = FALSE
if(is_servant) //heals servants of braindamage, hallucination, druggy, dizziness, and confusion
- if(owner.hallucination)
- owner.hallucination = 0
- if(owner.druggy)
- owner.adjust_drugginess(-owner.druggy)
- if(owner.dizziness)
- owner.dizziness = 0
- if(owner.confused)
- owner.confused = 0
+ owner.remove_status_effect(/datum/status_effect/hallucination)
+ owner.remove_status_effect(/datum/status_effect/drugginess)
+ owner.remove_status_effect(/datum/status_effect/drowsiness)
+ owner.remove_status_effect(/datum/status_effect/confusion)
severity = 0
else if(!owner.anti_magic_check(chargecost = 0) && owner.stat != DEAD && severity)
var/static/hum = get_sfx('sound/effects/screech.ogg') //same sound for every proc call
@@ -329,13 +479,10 @@
if(prob(severity * 0.15))
to_chat(owner, "\"[text2ratvar(pick(mania_messages))]\"")
owner.playsound_local(get_turf(motor), hum, severity, 1)
- owner.adjust_drugginess(clamp(max(severity * 0.075, 1), 0, max(0, 50 - owner.druggy))) //7.5% of severity per second, minimum 1
- if(owner.hallucination < 50)
- owner.hallucination = min(owner.hallucination + max(severity * 0.075, 1), 50) //7.5% of severity per second, minimum 1
- if(owner.dizziness < 50)
- owner.dizziness = min(owner.dizziness + round(severity * 0.05, 1), 50) //5% of severity per second above 10 severity
- if(owner.confused < 25)
- owner.confused = min(owner.confused + round(severity * 0.025, 1), 25) //2.5% of severity per second above 20 severity
+ owner.adjust_drugginess(clamp(max(severity * 0.075, 1), 0, max(0, 50 SECONDS))) //7.5% of severity per second, minimum 1
+ owner.adjust_hallucinations_up_to(max(severity * 0.075, 1) SECONDS, 50 SECONDS) //7.5% of severity per second, minimum 1
+ owner.adjust_dizzy_up_to(round(severity * 0.05, 1) SECONDS, 50 SECONDS) //5% of severity per second above 10 severity
+ owner.adjust_confusion_up_to(round(severity * 0.025, 1) SECONDS, 25 SECONDS) //2.5% of severity per second above 20 severity
owner.adjustToxLoss(severity * 0.02, TRUE, TRUE) //2% of severity per second
severity--
@@ -372,13 +519,13 @@
/datum/status_effect/the_shadow/tick()
var/turf/T = get_turf(owner)
var/light_amount = T.get_lumcount()
- if(light_amount > 0.2)
+ if(light_amount > LIGHTING_TILE_IS_DARK)
to_chat(owner, span_notice("As the light reaches the shadows, they dissipate!"))
qdel(src)
if(owner.stat == DEAD)
qdel(src)
- owner.hallucination += 2
- owner.confused += 2
+ owner.adjust_hallucinations(2 SECONDS)
+ owner.adjust_confusion(2 SECONDS)
owner.adjustEarDamage(0, 5)
/datum/status_effect/the_shadow/Destroy()
@@ -619,16 +766,16 @@
id = "kindle"
status_type = STATUS_EFFECT_UNIQUE
tick_interval = 5
- duration = 100
+ duration = 10 SECONDS
alert_type = /atom/movable/screen/alert/status_effect/kindle
var/old_health
/datum/status_effect/kindle/tick()
- owner.Paralyze(15)
+ owner.Paralyze(1.5 SECONDS)
if(iscarbon(owner))
var/mob/living/carbon/C = owner
- C.silent = max(2, C.silent)
- C.stuttering = max(5, C.stuttering)
+ C.silent += 2
+ C.adjust_stutter(5 SECONDS)
if(!old_health)
old_health = owner.health
var/health_difference = old_health - owner.health
@@ -708,8 +855,8 @@
/datum/status_effect/trance/tick()
if(stun)
- owner.Stun(60, TRUE, TRUE)
- owner.dizziness = 20
+ owner.Stun(6 SECONDS, TRUE, TRUE)
+ owner.adjust_dizzy(20 SECONDS)
/datum/status_effect/trance/on_apply()
if(!iscarbon(owner))
@@ -730,7 +877,7 @@
/datum/status_effect/trance/on_remove()
UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
REMOVE_TRAIT(owner, TRAIT_MUTE, "trance")
- owner.dizziness = 0
+ owner.remove_status_effect(/datum/status_effect/dizziness)
if(!owner.has_quirk(/datum/quirk/monochromatic))
owner.remove_client_colour(/datum/client_colour/monochrome)
to_chat(owner, span_warning("You snap out of your trance!"))
@@ -986,7 +1133,7 @@
var/mob/living/carbon/carbon_owner = owner
carbon_owner.adjustFireLoss(5 * repetitions)
carbon_owner.adjust_fire_stacks(2)
- carbon_owner.IgniteMob()
+ carbon_owner.ignite_mob()
for(var/mob/living/carbon/victim in range(1,carbon_owner))
if(IS_HERETIC(victim) || victim == carbon_owner)
continue
@@ -1028,7 +1175,7 @@
if(0 to 19)
H.vomit()
if(20 to 29)
- H.Dizzy(10)
+ H.adjust_dizzy(10)
if(30 to 39)
H.adjustOrganLoss(ORGAN_SLOT_LIVER,5)
if(40 to 49)
diff --git a/code/datums/status_effects/debuffs/dizziness.dm b/code/datums/status_effects/debuffs/dizziness.dm
new file mode 100644
index 000000000000..ea820c80a5c6
--- /dev/null
+++ b/code/datums/status_effects/debuffs/dizziness.dm
@@ -0,0 +1,79 @@
+/datum/status_effect/dizziness
+ id = "dizziness"
+ tick_interval = 2 SECONDS
+ alert_type = null
+ remove_on_fullheal = TRUE
+
+/datum/status_effect/dizziness/on_creation(mob/living/new_owner, duration = 10 SECONDS)
+ src.duration = duration
+ return ..()
+
+/datum/status_effect/dizziness/on_apply()
+ RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(clear_dizziness))
+ return TRUE
+
+/datum/status_effect/dizziness/on_remove()
+ UnregisterSignal(owner, COMSIG_LIVING_DEATH)
+ // In case our client's offset is somewhere wacky from the dizziness effect
+ owner.client?.pixel_x = initial(owner.client?.pixel_x)
+ owner.client?.pixel_y = initial(owner.client?.pixel_y)
+
+/// Signal proc that self deletes our dizziness effect
+/datum/status_effect/dizziness/proc/clear_dizziness(datum/source)
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+/datum/status_effect/dizziness/tick()
+ // How much time is left, in seconds
+ var/amount = (duration - world.time) / 10
+ if(amount <= 0)
+ return
+
+ // How strong the dizziness effect is on us.
+ // If we're resting, the effect is 5x as strong, but also decays 5x fast.
+ // Meaning effectively, 1 tick is actually dizziness_strength ticks of duration
+ var/dizziness_strength = owner.resting ? 5 : 1
+ var/time_between_ticks = initial(tick_interval)
+
+ // How much time will be left, in seconds, next tick
+ var/next_amount = max((amount - (dizziness_strength * time_between_ticks * 0.1)), 0)
+
+ // If we have a dizziness strength > 1, we will subtract ticks off of the total duration
+ if(remove_duration((dizziness_strength - 1) * time_between_ticks))
+ return
+
+ // Now we can do the actual dizzy effects.
+ // Don't bother animating if they're clientless.
+ if(!owner.client)
+ return
+
+ // Want to be able to offset things by the time the animation should be "playing" at
+ var/time = world.time
+ var/delay = 0
+ var/pixel_x_diff = 0
+ var/pixel_y_diff = 0
+
+ // This shit is annoying at high strengthvar/pixel_x_diff = 0
+ var/list/view_range_list = getviewsize(owner.client.view)
+ var/view_range = view_range_list[1]
+ var/amplitude = amount * (sin(amount * (time)) + 1)
+ var/x_diff = clamp(amplitude * sin(amount * time), -view_range, view_range)
+ var/y_diff = clamp(amplitude * cos(amount * time), -view_range, view_range)
+ pixel_x_diff += x_diff
+ pixel_y_diff += y_diff
+ // Brief explanation. We're basically snapping between different pixel_x/ys instantly, with delays between
+ // Doing this with relative changes. This way we don't override any existing pixel_x/y values
+ // We use EASE_OUT here for similar reasons, we want to act at the end of the delay, not at its start
+ // Relative animations are weird, so we do actually need this
+ animate(owner.client, pixel_x = x_diff, pixel_y = y_diff, 3, easing = JUMP_EASING | EASE_OUT, flags = ANIMATION_RELATIVE)
+ delay += 0.3 SECONDS // This counts as a 0.3 second wait, so we need to shift the sine wave by that much
+
+ x_diff = amplitude * sin(next_amount * (time + delay))
+ y_diff = amplitude * cos(next_amount * (time + delay))
+ pixel_x_diff += x_diff
+ pixel_y_diff += y_diff
+ animate(pixel_x = x_diff, pixel_y = y_diff, 3, easing = JUMP_EASING | EASE_OUT, flags = ANIMATION_RELATIVE)
+
+ // Now we reset back to our old pixel_x/y, since these animates are relative
+ animate(pixel_x = -pixel_x_diff, pixel_y = -pixel_y_diff, 3, easing = JUMP_EASING | EASE_OUT, flags = ANIMATION_RELATIVE)
diff --git a/code/datums/status_effects/debuffs/drowsiness.dm b/code/datums/status_effects/debuffs/drowsiness.dm
new file mode 100644
index 000000000000..d2707049a8c2
--- /dev/null
+++ b/code/datums/status_effects/debuffs/drowsiness.dm
@@ -0,0 +1,42 @@
+/datum/status_effect/drowsiness
+ id = "drowsiness"
+ tick_interval = 2 SECONDS
+ alert_type = null
+ remove_on_fullheal = TRUE
+
+/datum/status_effect/drowsiness/on_creation(mob/living/new_owner, duration = 10 SECONDS)
+ src.duration = duration
+ return ..()
+
+/datum/status_effect/drowsiness/on_apply()
+ if(HAS_TRAIT(owner, TRAIT_SLEEPIMMUNE) || !(owner.status_flags & CANUNCONSCIOUS))
+ return FALSE
+ // Do robots dream of electric sheep?
+ if(issilicon(owner))
+ return FALSE
+
+ RegisterSignal(owner, COMSIG_COMPONENT_CLEAN_FACE_ACT, PROC_REF(on_face_clean))
+ return TRUE
+
+/datum/status_effect/drowsiness/on_remove()
+ UnregisterSignal(owner, COMSIG_COMPONENT_CLEAN_FACE_ACT)
+
+/// Signal proc for [COMSIG_COMPONENT_CLEAN_FACE_ACT]. When we wash our face, reduce drowsiness by a bit.
+/datum/status_effect/drowsiness/proc/on_face_clean(datum/source)
+ SIGNAL_HANDLER
+
+ remove_duration(rand(4 SECONDS, 6 SECONDS))
+
+/datum/status_effect/drowsiness/tick(delta_time)
+ // You do not feel drowsy while unconscious or in stasis
+ if(owner.stat >= UNCONSCIOUS || IS_IN_STASIS(owner))
+ return
+
+ // Resting helps against drowsiness
+ // While resting, we lose 4 seconds of duration (2 additional ticks) per tick
+ if(owner.resting && remove_duration(2 * initial(tick_interval)))
+ return
+
+ owner.blur_eyes(4 SECONDS)
+ if(prob(5))
+ owner.AdjustSleeping(10 SECONDS)
diff --git a/code/datums/status_effects/debuffs/drugginess.dm b/code/datums/status_effects/debuffs/drugginess.dm
new file mode 100644
index 000000000000..318267c4c063
--- /dev/null
+++ b/code/datums/status_effects/debuffs/drugginess.dm
@@ -0,0 +1,36 @@
+/// Drugginess / "high" effect, makes your screen rainbow
+/datum/status_effect/drugginess
+ id = "drugged"
+ alert_type = /atom/movable/screen/alert/status_effect/high
+ remove_on_fullheal = TRUE
+
+/datum/status_effect/drugginess/on_creation(mob/living/new_owner, duration = 10 SECONDS)
+ src.duration = duration
+ return ..()
+
+/datum/status_effect/drugginess/on_apply()
+ RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(remove_drugginess))
+
+ SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, id, /datum/mood_event/high)
+ owner.overlay_fullscreen(id, /atom/movable/screen/fullscreen/high)
+ owner.grant_language(/datum/language/beachbum, TRUE, TRUE, id)
+ return TRUE
+
+/datum/status_effect/drugginess/on_remove()
+ UnregisterSignal(owner, COMSIG_LIVING_DEATH)
+
+ SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, id)
+ owner.clear_fullscreen(id)
+ owner.remove_language(/datum/language/beachbum, TRUE, TRUE, id)
+
+/// Removes all of our drugginess (self delete) on signal
+/datum/status_effect/drugginess/proc/remove_drugginess(datum/source, admin_revive)
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+/// The status effect for "drugginess"
+/atom/movable/screen/alert/status_effect/high
+ name = "High"
+ desc = "Whoa man, you're tripping balls! Careful you don't get addicted... if you aren't already."
+ icon_state = "high"
diff --git a/code/datums/status_effects/debuffs/drunk.dm b/code/datums/status_effects/debuffs/drunk.dm
new file mode 100644
index 000000000000..06c7d96e89c7
--- /dev/null
+++ b/code/datums/status_effects/debuffs/drunk.dm
@@ -0,0 +1,210 @@
+// Defines for the ballmer peak.
+#define BALLMER_PEAK_LOW_END 12.9
+#define BALLMER_PEAK_HIGH_END 13.8
+#define BALLMER_PEAK_WINDOWS_ME 26
+
+/// The threshld which determine if someone is tipsy vs drunk
+#define TIPSY_THRESHOLD 6
+
+/**
+ * The drunk status effect.
+ * Slowly decreases in drunk_value over time, causing effects based on that value.
+ */
+/datum/status_effect/inebriated
+ id = "drunk"
+ tick_interval = 2 SECONDS
+ status_type = STATUS_EFFECT_REPLACE
+ remove_on_fullheal = TRUE
+ /// The level of drunkness we are currently at.
+ var/drunk_value = 0
+
+/datum/status_effect/inebriated/on_creation(mob/living/new_owner, drunk_value = 0)
+ . = ..()
+ set_drunk_value(drunk_value)
+
+/datum/status_effect/inebriated/get_examine_text()
+ // Dead people don't look drunk
+ if(owner.stat == DEAD || HAS_TRAIT(owner, TRAIT_FAKEDEATH))
+ return null
+
+ // Having your face covered conceals your drunkness
+ if(iscarbon(owner))
+ var/mob/living/carbon/carbon_owner = owner
+ if(carbon_owner.wear_mask?.flags_inv & HIDEFACE)
+ return null
+ if(carbon_owner.head?.flags_inv & HIDEFACE)
+ return null
+
+ // .01s are used in case the drunk value ends up to be a small decimal.
+ switch(drunk_value)
+ if(11 to 21)
+ return span_warning("[owner.p_they(TRUE)] [owner.p_are()] slightly flushed.")
+ if(21.01 to 41)
+ return span_warning("[owner.p_they(TRUE)] [owner.p_are()] flushed.")
+ if(41.01 to 51)
+ return span_warning("[owner.p_they(TRUE)] [owner.p_are()] quite flushed and [owner.p_their()] breath smells of alcohol.")
+ if(51.01 to 61)
+ return span_warning("[owner.p_they(TRUE)] [owner.p_are()] very flushed and [owner.p_their()] movements jerky, with breath reeking of alcohol.")
+ if(61.01 to 91)
+ return span_warning("[owner.p_they(TRUE)] look[owner.p_s()] like a drunken mess.")
+ if(91.01 to INFINITY)
+ return span_warning("[owner.p_they(TRUE)] [owner.p_are()] a shitfaced, slobbering wreck.")
+
+ return null
+
+/// Sets the drunk value to set_to, deleting if the value drops to 0 or lower
+/datum/status_effect/inebriated/proc/set_drunk_value(set_to)
+ if(!isnum(set_to))
+ CRASH("[type] - invalid value passed to set_drunk_value. (Got: [set_to])")
+
+ drunk_value = set_to
+ if(drunk_value <= 0)
+ qdel(src)
+
+/datum/status_effect/inebriated/tick()
+ // Drunk value does not decrease while dead or in stasis
+ if(owner.stat == DEAD || IS_IN_STASIS(owner))
+ return
+
+ // Every tick, the drunk value decrases by
+ // 4% the current drunk_value + 0.01
+ // (until it reaches 0 and terminates)
+ set_drunk_value(drunk_value - (0.01 + drunk_value * 0.04))
+ if(QDELETED(src))
+ return
+
+ on_tick_effects()
+
+/// Side effects done by this level of drunkness on tick.
+/datum/status_effect/inebriated/proc/on_tick_effects()
+ return
+
+/**
+ * Stage 1 of drunk, applied at drunk values between 0 and 6.
+ * Basically is the "drunk but no side effects" stage.
+ */
+/datum/status_effect/inebriated/tipsy
+ alert_type = null
+
+/datum/status_effect/inebriated/tipsy/set_drunk_value(set_to)
+ . = ..()
+ if(QDELETED(src))
+ return
+
+ // Become fully drunk at over than 6 drunk value
+ if(drunk_value >= TIPSY_THRESHOLD)
+ owner.apply_status_effect(/datum/status_effect/inebriated/drunk, drunk_value)
+
+/**
+ * Stage 2 of being drunk, applied at drunk values between 6 and onward.
+ * Has all the main side effects of being drunk, scaling up as they get more drunk.
+ */
+/datum/status_effect/inebriated/drunk
+ alert_type = /atom/movable/screen/alert/status_effect/drunk
+
+/datum/status_effect/inebriated/drunk/on_apply()
+ . = ..()
+ SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, id, /datum/mood_event/drunk)
+
+/datum/status_effect/inebriated/drunk/on_remove()
+ clear_effects()
+ return ..()
+
+// Going from "drunk" to "tipsy" should remove effects like on_remove
+/datum/status_effect/inebriated/drunk/be_replaced()
+ clear_effects()
+ return ..()
+
+/// Clears any side effects we set due to being drunk.
+/datum/status_effect/inebriated/drunk/proc/clear_effects()
+ SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, id)
+
+/datum/status_effect/inebriated/drunk/set_drunk_value(set_to)
+ . = ..()
+ if(QDELETED(src))
+ return
+
+ // Return to "tipsyness" when we're below 6.
+ if(drunk_value < TIPSY_THRESHOLD)
+ owner.apply_status_effect(/datum/status_effect/inebriated/tipsy, drunk_value)
+
+/datum/status_effect/inebriated/drunk/on_tick_effects()
+ // Handle the Ballmer Peak.
+ // If our owner is a scientist (has the trait "TRAIT_BALLMER_SCIENTIST"), there's a 5% chance
+ // that they'll say one of the special "ballmer message" lines, depending their drunk-ness level.
+ REMOVE_TRAIT(owner, TRAIT_SURGERY_PREPARED, "drunk")
+ var/obj/item/organ/liver/liver_organ = owner.getorganslot(ORGAN_SLOT_LIVER)
+ if(liver_organ && HAS_TRAIT(liver_organ, TRAIT_BALLMER_SCIENTIST) && prob(5))
+ if(drunk_value >= BALLMER_PEAK_LOW_END && drunk_value <= BALLMER_PEAK_HIGH_END)
+ owner.say(pick_list_replacements(VISTA_FILE, "ballmer_good_msg"), forced = "ballmer")
+
+ if(drunk_value > BALLMER_PEAK_WINDOWS_ME) // by this point you're into windows ME territory
+ owner.say(pick_list_replacements(VISTA_FILE, "ballmer_windows_me_msg"), forced = "ballmer")
+
+ // There's always a 30% chance to gain some drunken slurring
+ if(prob(30))
+ owner.adjust_slurring(4 SECONDS)
+
+ // And drunk people will always lose jitteriness
+ owner.adjust_jitter(-6 SECONDS)
+
+ // Over 11, we will constantly gain slurring up to 10 seconds of slurring.
+ if(drunk_value >= 11)
+ owner.adjust_slurring_up_to(2.4 SECONDS, 10 SECONDS)
+
+ // Over 41, we have a 30% chance to gain confusion, and we will always have 20 seconds of dizziness.
+ if(drunk_value >= 41)
+ if(prob(30))
+ owner.adjust_confusion(2 SECONDS)
+
+ owner.set_dizzy_if_lower(20 SECONDS)
+ ADD_TRAIT(src, TRAIT_SURGERY_PREPARED, "drunk")
+
+ // Over 51, we have a 3% chance to gain a lot of confusion and vomit, and we will always have 50 seconds of dizziness
+ if(drunk_value >= 51)
+ owner.set_dizzy_if_lower(50 SECONDS)
+ if(prob(3))
+ owner.adjust_confusion(15 SECONDS)
+ if(iscarbon(owner))
+ var/mob/living/carbon/carbon_owner = owner
+ carbon_owner.vomit() // Vomiting clears toxloss - consider this a blessing
+
+ // Over 71, we will constantly have blurry eyes
+ if(drunk_value >= 71)
+ owner.blur_eyes(drunk_value * 2 SECONDS - 140 SECONDS)
+
+ // Over 81, we will gain constant toxloss
+ if(drunk_value >= 81)
+ owner.adjustToxLoss(1)
+ if(owner.stat == CONSCIOUS && prob(5))
+ to_chat(owner, span_warning("Maybe you should lie down for a bit..."))
+
+ // Over 91, we gain even more toxloss, brain damage, and have a chance of dropping into a long sleep
+ if(drunk_value >= 91)
+ owner.adjustToxLoss(1)
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.4)
+ if(owner.stat == CONSCIOUS && prob(20))
+ // Don't put us in a deep sleep if the shuttle's here. QoL, mainly.
+ if(SSshuttle.emergency.mode == SHUTTLE_DOCKED && is_station_level(owner.z))
+ to_chat(owner, span_warning("You're so tired... but you can't miss that shuttle..."))
+
+ else
+ to_chat(owner, span_warning("Just a quick nap..."))
+ owner.Sleeping(90 SECONDS)
+
+ // And finally, over 100 - let's be honest, you shouldn't be alive by now.
+ if(drunk_value >= 101)
+ owner.adjustToxLoss(2)
+
+/// Status effect for being fully drunk (not tipsy).
+/atom/movable/screen/alert/status_effect/drunk
+ name = "Drunk"
+ desc = "All that alcohol you've been drinking is impairing your speech, \
+ motor skills, and mental cognition. Make sure to act like it."
+ icon_state = "drunk"
+
+#undef BALLMER_PEAK_LOW_END
+#undef BALLMER_PEAK_HIGH_END
+#undef BALLMER_PEAK_WINDOWS_ME
+
+#undef TIPSY_THRESHOLD
diff --git a/code/datums/status_effects/debuffs/hallucination.dm b/code/datums/status_effects/debuffs/hallucination.dm
new file mode 100644
index 000000000000..e788d6b21379
--- /dev/null
+++ b/code/datums/status_effects/debuffs/hallucination.dm
@@ -0,0 +1,117 @@
+/// Hallucination status effect. How most hallucinations end up happening.
+/// Hallucinations are drawn from the global weighted list, random_hallucination_weighted_list
+/datum/status_effect/hallucination
+ id = "hallucination"
+ alert_type = null
+ tick_interval = 2 SECONDS
+ remove_on_fullheal = TRUE
+ /// The lower range of when the next hallucination will trigger after one occurs.
+ var/lower_tick_interval = 10 SECONDS
+ /// The upper range of when the next hallucination will trigger after one occurs.
+ var/upper_tick_interval = 60 SECONDS
+ /// The cooldown for when the next hallucination can occur
+ COOLDOWN_DECLARE(hallucination_cooldown)
+
+/datum/status_effect/hallucination/on_creation(mob/living/new_owner, duration)
+ if(isnum(duration))
+ src.duration = duration
+ return ..()
+
+/datum/status_effect/hallucination/on_apply()
+ if(owner.mob_biotypes == NO_HALLUCINATION_BIOTYPES)
+ return FALSE
+
+ RegisterSignal(owner, COMSIG_LIVING_HEALTHSCAN, PROC_REF(on_health_scan))
+ if(iscarbon(owner))
+ RegisterSignal(owner, COMSIG_CARBON_CHECKING_BODYPART, PROC_REF(on_check_bodypart))
+ RegisterSignal(owner, COMSIG_CARBON_BUMPED_AIRLOCK_OPEN, PROC_REF(on_bump_airlock))
+
+ return TRUE
+
+/datum/status_effect/hallucination/on_remove()
+ UnregisterSignal(owner, list(
+ COMSIG_LIVING_HEALTHSCAN,
+ COMSIG_CARBON_CHECKING_BODYPART,
+ COMSIG_CARBON_BUMPED_AIRLOCK_OPEN,
+ ))
+
+/// Signal proc for [COMSIG_LIVING_HEALTHSCAN]. Show we're hallucinating to (advanced) scanners.
+/datum/status_effect/hallucination/proc/on_health_scan(datum/source, list/render_list, advanced, mob/user, mode)
+ SIGNAL_HANDLER
+
+ if(!advanced)
+ return
+
+ render_list += "Subject is hallucinating.\n"
+
+/// Signal proc for [COMSIG_CARBON_CHECKING_BODYPART],
+/// checking bodyparts while hallucinating can cause them to appear more damaged than they are
+/datum/status_effect/hallucination/proc/on_check_bodypart(mob/living/carbon/source, obj/item/bodypart/examined, list/check_list, list/limb_damage)
+ SIGNAL_HANDLER
+
+ if(prob(30))
+ limb_damage[BRUTE] += rand(30, 40)
+ if(prob(30))
+ limb_damage[BURN] += rand(30, 40)
+
+/// Signal proc for [COMSIG_CARBON_BUMPED_AIRLOCK_OPEN], bumping an airlock can cause a fake zap.
+/// This only happens on airlock bump, future TODO - make this chance roll for attack_hand opening airlocks too
+/datum/status_effect/hallucination/proc/on_bump_airlock(mob/living/carbon/source, obj/machinery/door/airlock/bumped)
+ SIGNAL_HANDLER
+
+ // 1% chance to fake a shock.
+ if(prob(99) || !source.should_electrocute() || bumped.operating)
+ return
+
+ source.cause_hallucination(/datum/hallucination/shock, "hallucinated shock from [bumped]",)
+ return STOP_BUMP
+
+/datum/status_effect/hallucination/tick(delta_time, times_fired)
+ if(owner.stat == DEAD)
+ return
+ if(!COOLDOWN_FINISHED(src, hallucination_cooldown))
+ return
+
+ var/datum/hallucination/picked_hallucination = pickweight(GLOB.random_hallucination_weighted_list)
+ owner.cause_hallucination(picked_hallucination, "[id] status effect")
+ COOLDOWN_START(src, hallucination_cooldown, rand(lower_tick_interval, upper_tick_interval))
+
+// Sanity related hallucinations
+/datum/status_effect/hallucination/sanity
+ id = "low sanity"
+ status_type = STATUS_EFFECT_REFRESH
+ duration = -1 // This lasts "forever", only goes away with sanity gain
+
+/datum/status_effect/hallucination/sanity/on_apply()
+ var/datum/component/mood/mob_mood = owner.GetComponent(/datum/component/mood)
+ if(!mob_mood)
+ return FALSE
+
+ update_intervals()
+ return ..()
+
+/datum/status_effect/hallucination/sanity/refresh(...)
+ update_intervals()
+
+/datum/status_effect/hallucination/sanity/tick(delta_time, times_fired)
+ // Using psicodine / happiness / whatever to become fearless will stop sanity based hallucinations
+ if(HAS_TRAIT(owner, TRAIT_FEARLESS))
+ return
+
+ return ..()
+
+/// Updates our upper and lower intervals based on our owner's current sanity level.
+/datum/status_effect/hallucination/sanity/proc/update_intervals()
+ var/datum/component/mood/mob_mood = owner.GetComponent(/datum/component/mood)
+ switch(mob_mood.sanity_level)
+ if(SANITY_CRAZY)
+ upper_tick_interval = 8 MINUTES
+ lower_tick_interval = 4 MINUTES
+
+ if(SANITY_INSANE)
+ upper_tick_interval = 4 MINUTES
+ lower_tick_interval = 2 MINUTES
+
+ else
+ stack_trace("[type] was assigned a mob which was not crazy or insane. (was: [mob_mood.sanity_level])")
+ qdel(src)
diff --git a/code/datums/status_effects/debuffs/jitteriness.dm b/code/datums/status_effects/debuffs/jitteriness.dm
new file mode 100644
index 000000000000..82d360aaef76
--- /dev/null
+++ b/code/datums/status_effects/debuffs/jitteriness.dm
@@ -0,0 +1,62 @@
+/datum/status_effect/jitter
+ id = "jitter"
+ tick_interval = 2 SECONDS
+ alert_type = null
+ remove_on_fullheal = TRUE
+
+/datum/status_effect/jitter/on_creation(mob/living/new_owner, duration = 10 SECONDS)
+ src.duration = duration
+ return ..()
+
+/datum/status_effect/jitter/on_apply()
+ // If we're being applied to a dead person, don't make the status effect.
+ // Just do a bit of jitter animation and be done.
+ if(owner.stat == DEAD)
+ owner.do_jitter_animation(duration / 10)
+ return FALSE
+
+ RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(remove_jitter))
+ SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, id, /datum/mood_event/jittery)
+ return TRUE
+
+/datum/status_effect/jitter/on_remove()
+ UnregisterSignal(owner, COMSIG_LIVING_DEATH)
+ SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, id)
+ // juuust in case, reset our x and y's from our jittering
+ owner.pixel_x = 0
+ owner.pixel_y = 0
+
+/datum/status_effect/jitter/get_examine_text()
+ switch(duration - world.time)
+ if(5 MINUTES to INFINITY)
+ return span_boldwarning("[owner.p_they(TRUE)] [owner.p_are()] convulsing violently!")
+ if(3 MINUTES to 5 MINUTES)
+ return span_warning("[owner.p_they(TRUE)] [owner.p_are()] extremely jittery.")
+ if(1 MINUTES to 3 MINUTES)
+ return span_warning("[owner.p_they(TRUE)] [owner.p_are()] twitching ever so slightly.")
+
+ return null
+
+/// Removes all of our jitteriness on a signal
+/datum/status_effect/jitter/proc/remove_jitter(datum/source)
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+/datum/status_effect/jitter/tick()
+ // Resting helps against jitter
+ // While resting, we lose 8 seconds of duration (4 additional ticks) per tick
+ if(owner.resting && remove_duration(4 * initial(tick_interval)))
+ return
+
+ var/time_left_in_seconds = (duration - world.time) / 10
+ owner.do_jitter_animation(time_left_in_seconds)
+
+/// Helper proc that causes the mob to do a jittering animation by jitter_amount.
+/// jitter_amount will only apply up to 300 (maximum jitter effect).
+/mob/living/proc/do_jitter_animation(jitter_amount = 100)
+ var/amplitude = min(4, (jitter_amount / 100) + 1)
+ var/pixel_x_diff = rand(-amplitude, amplitude)
+ var/pixel_y_diff = rand(-amplitude / 3, amplitude / 3)
+ animate(src, pixel_x = pixel_x_diff, pixel_y = pixel_y_diff , time = 0.2 SECONDS, loop = 6, flags = ANIMATION_RELATIVE|ANIMATION_PARALLEL)
+ animate(pixel_x = -pixel_x_diff , pixel_y = -pixel_y_diff , time = 0.2 SECONDS, flags = ANIMATION_RELATIVE)
diff --git a/code/datums/status_effects/knuckleroot.dm b/code/datums/status_effects/debuffs/knuckleroot.dm
similarity index 100%
rename from code/datums/status_effects/knuckleroot.dm
rename to code/datums/status_effects/debuffs/knuckleroot.dm
diff --git a/code/datums/status_effects/debuffs/speech_debuffs.dm b/code/datums/status_effects/debuffs/speech_debuffs.dm
new file mode 100644
index 000000000000..7298696bfc1e
--- /dev/null
+++ b/code/datums/status_effects/debuffs/speech_debuffs.dm
@@ -0,0 +1,216 @@
+/datum/status_effect/speech
+ id = null
+ alert_type = null
+ remove_on_fullheal = TRUE
+
+/datum/status_effect/speech/on_creation(mob/living/new_owner, duration = 10 SECONDS)
+ src.duration = duration
+ return ..()
+
+/datum/status_effect/speech/on_apply()
+ RegisterSignal(owner, COMSIG_LIVING_TREAT_MESSAGE, PROC_REF(handle_message))
+ return TRUE
+
+/datum/status_effect/speech/on_remove()
+ UnregisterSignal(owner, COMSIG_LIVING_TREAT_MESSAGE)
+
+/**
+ * Signal proc for [COMSIG_LIVING_TREAT_MESSAGE]
+ *
+ * Iterates over all of the characters in the passed message
+ * and calls apply_speech() on each.
+ */
+/datum/status_effect/speech/proc/handle_message(datum/source, list/message_args)
+ SIGNAL_HANDLER
+
+ var/phrase = html_decode(message_args[TREAT_MESSAGE_MESSAGE])
+ if(!length(phrase))
+ return
+
+ var/final_phrase = ""
+ var/original_char = ""
+
+ for(var/i = 1, i <= length(phrase), i += length(original_char))
+ original_char = phrase[i]
+
+ final_phrase += apply_speech(original_char, original_char)
+
+ message_args[TREAT_MESSAGE_MESSAGE] = sanitize(final_phrase)
+
+/**
+ * Applies the speech effects on the past character, changing
+ * the original_char into the modified_char.
+ *
+ * Return the modified_char to be reapplied to the message.
+ */
+/datum/status_effect/speech/proc/apply_speech(original_char, modified_char)
+ stack_trace("[type] didn't implement apply_speech.")
+ return original_char
+
+/datum/status_effect/speech/stutter
+ id = "stutter"
+ /// The probability of adding a stutter to any character
+ var/stutter_prob = 80
+ /// Regex of characters we won't apply a stutter to
+ var/static/regex/no_stutter
+
+/datum/status_effect/speech/stutter/on_creation(mob/living/new_owner, ...)
+ . = ..()
+ if(!.)
+ return
+ if(!no_stutter)
+ no_stutter = regex(@@[aeiouAEIOU ""''()[\]{}.!?,:;_`~-]@)
+
+/datum/status_effect/speech/stutter/apply_speech(original_char, modified_char)
+ if(prob(stutter_prob) && !no_stutter.Find(original_char))
+ if(prob(10))
+ modified_char = "[modified_char]-[modified_char]-[modified_char]-[modified_char]"
+ else if(prob(20))
+ modified_char = "[modified_char]-[modified_char]-[modified_char]"
+ else if(prob(95))
+ modified_char = "[modified_char]-[modified_char]"
+ else
+ modified_char = ""
+
+ return modified_char
+
+/datum/status_effect/speech/stutter/derpspeech
+ id = "derp_stutter"
+ /// The probability of making our message entirely uppercase + adding exclamations
+ var/capitalize_prob = 50
+ /// The probability of adding a stutter to the entire message, if we're not already stuttering
+ var/message_stutter_prob = 15
+
+/datum/status_effect/speech/stutter/derpspeech/handle_message(datum/source, list/message_args)
+
+ var/message = html_decode(message_args[TREAT_MESSAGE_MESSAGE])
+
+ message = replacetext(message, " am ", " ")
+ message = replacetext(message, " is ", " ")
+ message = replacetext(message, " are ", " ")
+ message = replacetext(message, "you", "u")
+ message = replacetext(message, "help", "halp")
+ message = replacetext(message, "grief", "grife")
+ message = replacetext(message, "space", "spess")
+ message = replacetext(message, "carp", "crap")
+ message = replacetext(message, "reason", "raisin")
+
+ if(prob(capitalize_prob))
+ var/exclamation = pick("!", "!!", "!!!")
+ message = uppertext(message)
+ message += "[apply_speech(exclamation, exclamation)]"
+
+ message_args[1] = message
+
+ var/mob/living/living_source = source
+ if(!isliving(source) || living_source.has_status_effect(/datum/status_effect/speech/stutter))
+ return
+
+ // If we're not stuttering, we have a chance of calling parent here, adding stutter effects
+ if(prob(message_stutter_prob))
+ return ..()
+
+ // Otherwise just return and don't call parent, we already modified our speech
+ return
+
+/datum/status_effect/speech/slurring
+ /// The chance that any given character in a message will be replaced with a common character
+ var/common_prob = 25
+ /// The chance that any given character in a message will be replaced with an uncommon character
+ var/uncommon_prob = 10
+ /// The chance that any given character will be entirely replaced with a new string / will have a string appended onto it
+ var/replacement_prob = 5
+ /// The chance that any given character will be doubled, or even tripled
+ var/doubletext_prob = 0
+
+ /// The file we pull text modifications from
+ var/text_modification_file = ""
+
+ /// Common replacements for characters - populated in on_creation
+ var/list/common_replacements
+ /// Uncommon replacements for characters - populated in on_creation
+ var/list/uncommon_replacements
+ /// Strings that fully replace a character - populated in on_creation
+ var/list/string_replacements
+ /// Strings that are appended to a character - populated in on_creation
+ var/list/string_additions
+
+/datum/status_effect/speech/slurring/on_creation(mob/living/new_owner, duration = 10 SECONDS)
+ . = ..()
+ if(!.)
+ return
+
+ if(!text_modification_file)
+ CRASH("[type] was created without a text modification file.")
+
+ var/list/speech_changes = strings(text_modification_file, "replacements")
+ common_replacements = speech_changes["characters"]["common"]
+ uncommon_replacements = speech_changes["characters"]["uncommon"]
+ string_replacements = speech_changes["string_replacements"]
+ string_additions = speech_changes["string_additions"]
+
+/datum/status_effect/speech/slurring/apply_speech(original_char, modified_char)
+
+ var/lower_char = lowertext(modified_char)
+ if(prob(common_prob) && (lower_char in common_replacements))
+ var/to_replace = common_replacements[lower_char]
+ if(islist(to_replace))
+ modified_char = pick(to_replace)
+ else
+ modified_char = to_replace
+
+ if(prob(uncommon_prob) && (modified_char in uncommon_replacements))
+ var/to_replace = uncommon_replacements[modified_char]
+ if(islist(to_replace))
+ modified_char = pick(to_replace)
+ else
+ modified_char = to_replace
+
+ if(prob(replacement_prob))
+ var/replacements_len = length(string_replacements)
+ var/additions_len = length(string_additions)
+ if(replacements_len && additions_len)
+ // Calculate the probability of grabbing a replacement vs an addition
+ var/weight = (replacements_len + additions_len) / replacements_len * 100
+ if(prob(weight))
+ modified_char = pick(string_replacements)
+ else
+ modified_char += pick(string_additions)
+
+ else if(replacements_len)
+ modified_char = pick(string_replacements)
+
+ else if(additions_len)
+ modified_char += pick(string_additions)
+
+ if(prob(doubletext_prob))
+ if(prob(50))
+ modified_char += "[modified_char]"
+ else
+ modified_char += "[modified_char][modified_char]"
+
+ return modified_char
+
+/datum/status_effect/speech/slurring/drunk
+ id = "drunk_slurring"
+ common_prob = 33
+ uncommon_prob = 5
+ replacement_prob = 5
+ doubletext_prob = 10
+ text_modification_file = "slurring_drunk_text.json"
+
+/datum/status_effect/speech/slurring/cult
+ id = "cult_slurring"
+ common_prob = 50
+ uncommon_prob = 25
+ replacement_prob = 33
+ doubletext_prob = 0
+ text_modification_file = "slurring_cult_text.json"
+
+/datum/status_effect/speech/slurring/heretic
+ id = "heretic_slurring"
+ common_prob = 50
+ uncommon_prob = 20
+ replacement_prob = 30
+ doubletext_prob = 5
+ text_modification_file = "slurring_heretic_text.json"
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index e27616fab82c..78d592e69a37 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -98,7 +98,7 @@
/datum/status_effect/bounty/on_apply()
to_chat(owner, "[span_boldnotice("You hear something behind you talking...")] [span_notice("You have been marked for death by [rewarded]. If you die, they will be rewarded.")]")
- playsound(owner, 'sound/weapons/shotgunpump.ogg', 75, 0)
+ playsound(owner, 'sound/weapons/shotgunpump.ogg', 75, FALSE)
return ..()
/datum/status_effect/bounty/tick()
@@ -109,12 +109,10 @@
/datum/status_effect/bounty/proc/rewards()
if(rewarded && rewarded.mind && rewarded.stat != DEAD)
to_chat(owner, "[span_boldnotice("You hear something behind you talking...")] [span_notice("Bounty claimed.")]")
- playsound(owner, 'sound/weapons/shotgunshot.ogg', 75, 0)
+ playsound(owner, 'sound/weapons/shotgunshot.ogg', 75, FALSE)
to_chat(rewarded, span_greentext("You feel a surge of mana flow into you!"))
- for(var/obj/effect/proc_holder/spell/spell in rewarded.mind.spell_list)
- spell.charge_counter = spell.charge_max
- spell.recharging = FALSE
- spell.update_icon()
+ for(var/datum/action/cooldown/spell/spell in rewarded.actions)
+ spell.reset_spell_cooldown()
rewarded.adjustBruteLoss(-25)
rewarded.adjustFireLoss(-25)
rewarded.adjustToxLoss(-25)
@@ -168,7 +166,7 @@
playsound(owner, 'yogstation/sound/magic/devour_will_form.ogg', 50, TRUE)
owner.setDir(SOUTH)
-/datum/status_effect/tagalong/process()
+/datum/status_effect/tagalong/tick()
if(!shadowing)
owner.forceMove(cached_location)
qdel(src)
@@ -178,7 +176,7 @@
owner.forceMove(cached_location)
shadowing.visible_message(span_warning("[owner] suddenly appears from the dark!"))
to_chat(owner, span_warning("You are forced out of [shadowing]'s shadow!"))
- owner.Knockdown(30)
+ owner.Knockdown(3 SECONDS)
qdel(src)
var/obj/item/I = owner.get_active_held_item()
if(I)
@@ -229,4 +227,4 @@
id = "notscared"
duration = 600
status_type = STATUS_EFFECT_UNIQUE
- alert_type = null
\ No newline at end of file
+ alert_type = null
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index 5eb5aed711a1..36d3c975a4a6 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -1,86 +1,174 @@
-//Status effects are used to apply temporary or permanent effects to mobs. Mobs are aware of their status effects at all times.
-//This file contains their code, plus code for applying and removing them.
-//When making a new status effect, add a define to status_effects.dm in __DEFINES for ease of use!
-
+/// Status effects are used to apply temporary or permanent effects to mobs.
+/// This file contains their code, plus code for applying and removing them.
/datum/status_effect
- var/id = "effect" //Used for screen alerts.
- var/duration = -1 //How long the status effect lasts in DECISECONDS. Enter -1 for an effect that never ends unless removed through some means.
- var/tick_interval = 10 //How many deciseconds between ticks, approximately. Leave at 10 for every second.
- var/mob/living/owner //The mob affected by the status effect.
- var/status_type = STATUS_EFFECT_UNIQUE //How many of the effect can be on one mob, and what happens when you try to add another
- var/on_remove_on_mob_delete = FALSE //if we call on_remove() when the mob is deleted
- var/examine_text //If defined, this text will appear when the mob is examined - to use he, she etc. use "SUBJECTPRONOUN" and replace it in the examines themselves
- var/alert_type = /atom/movable/screen/alert/status_effect //the alert thrown by the status effect, contains name and description
- var/atom/movable/screen/alert/status_effect/linked_alert = null //the alert itself, if it exists
+ /// The ID of the effect. ID is used in adding and removing effects to check for duplicates, among other things.
+ var/id = "effect"
+ /// When set initially / in on_creation, this is how long the status effect lasts in deciseconds.
+ /// While processing, this becomes the world.time when the status effect will expire.
+ /// -1 = infinite duration.
+ var/duration = -1
+ /// When set initially / in on_creation, this is how long between [proc/tick] calls in deciseconds.
+ /// While processing, this becomes the world.time when the next tick will occur.
+ /// -1 = will stop processing, if duration is also unlimited (-1).
+ var/tick_interval = 1 SECONDS
+ /// The mob affected by the status effect.
+ var/mob/living/owner
+ /// How many of the effect can be on one mob, and/or what happens when you try to add a duplicate.
+ var/status_type = STATUS_EFFECT_UNIQUE
+ /// If TRUE, we call [proc/on_remove] when owner is deleted. Otherwise, we call [proc/be_replaced].
+ var/on_remove_on_mob_delete = FALSE
+ /// The typepath to the alert thrown by the status effect when created.
+ /// Status effect "name"s and "description"s are shown to the owner here.
+ var/alert_type = /atom/movable/screen/alert/status_effect
+ /// The alert itself, created in [proc/on_creation] (if alert_type is specified).
+ var/atom/movable/screen/alert/status_effect/linked_alert
+ /// Used to define if the status effect should be using SSfastprocess or SSprocessing
+ var/processing_speed = STATUS_EFFECT_FAST_PROCESS
+ /// Do we self-terminate when a fullheal is called?
+ var/remove_on_fullheal = FALSE
+ // If remove_on_fullheal is TRUE, what flag do we need to be removed?
+ //var/heal_flag_necessary = HEAL_STATUS
+ ///If defined, this text will appear when the mob is examined - to use he, she etc. use "SUBJECTPRONOUN" and replace it in the examines themselves
+ var/examine_text
/datum/status_effect/New(list/arguments)
on_creation(arglist(arguments))
+/// Called from New() with any supplied status effect arguments.
+/// Not guaranteed to exist by the end.
+/// Returning FALSE from on_apply will stop on_creation and self-delete the effect.
/datum/status_effect/proc/on_creation(mob/living/new_owner, ...)
if(new_owner)
owner = new_owner
- if(owner)
- LAZYADD(owner.status_effects, src)
- if(!owner || !on_apply())
+ if(QDELETED(owner) || !on_apply())
qdel(src)
return
+ if(owner)
+ LAZYADD(owner.status_effects, src)
+ RegisterSignal(owner, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(remove_effect_on_heal))
+
if(duration != -1)
duration = world.time + duration
tick_interval = world.time + tick_interval
+
if(alert_type)
- var/atom/movable/screen/alert/status_effect/A = owner.throw_alert(id, alert_type)
- A.attached_effect = src //so the alert can reference us, if it needs to
- linked_alert = A //so we can reference the alert, if we need to
+ var/atom/movable/screen/alert/status_effect/new_alert = owner.throw_alert(id, alert_type)
+ new_alert.attached_effect = src //so the alert can reference us, if it needs to
+ linked_alert = new_alert //so we can reference the alert, if we need to
+
if(duration > 0 || initial(tick_interval) > 0) //don't process if we don't care
- START_PROCESSING(SSfastprocess, src)
+ switch(processing_speed)
+ if(STATUS_EFFECT_FAST_PROCESS)
+ START_PROCESSING(SSfastprocess, src)
+ if(STATUS_EFFECT_NORMAL_PROCESS)
+ START_PROCESSING(SSprocessing, src)
+
return TRUE
/datum/status_effect/Destroy()
- STOP_PROCESSING(SSfastprocess, src)
+ switch(processing_speed)
+ if(STATUS_EFFECT_FAST_PROCESS)
+ STOP_PROCESSING(SSfastprocess, src)
+ if(STATUS_EFFECT_NORMAL_PROCESS)
+ STOP_PROCESSING(SSprocessing, src)
if(owner)
+ linked_alert = null
owner.clear_alert(id)
LAZYREMOVE(owner.status_effects, src)
on_remove()
+ UnregisterSignal(owner, COMSIG_LIVING_POST_FULLY_HEAL)
owner = null
return ..()
-/datum/status_effect/process()
- if(!owner)
+// Status effect process. Handles adjusting its duration and ticks.
+// If you're adding processed effects, put them in [proc/tick]
+// instead of extending / overriding the process() proc.
+/datum/status_effect/process(delta_time, times_fired)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(QDELETED(owner))
qdel(src)
return
if(tick_interval < world.time)
- tick()
+ tick(delta_time, times_fired)
tick_interval = world.time + initial(tick_interval)
if(duration != -1 && duration < world.time)
qdel(src)
/datum/status_effect/proc/on_apply() //Called whenever the buff is applied; returning FALSE will cause it to autoremove itself.
return TRUE
-/datum/status_effect/proc/tick() //Called every tick.
-/datum/status_effect/proc/on_remove() //Called whenever the buff expires or is removed; do note that at the point this is called, it is out of the owner's status_effects but owner is not yet null
-/datum/status_effect/proc/be_replaced() //Called instead of on_remove when a status effect is replaced by itself or when a status effect with on_remove_on_mob_delete = FALSE has its mob deleted
+
+/// Gets and formats examine text associated with our status effect.
+/// Return 'null' to have no examine text appear (default behavior).
+/datum/status_effect/proc/get_examine_text()
+ return null
+
+/// Called every tick from process().
+/datum/status_effect/proc/tick(delta_time, times_fired)
+ return
+
+/// Called whenever the buff expires or is removed (qdeleted)
+/// Note that at the point this is called, it is out of the
+/// owner's status_effects list, but owner is not yet null
+/datum/status_effect/proc/on_remove()
+ return
+
+/// Called instead of on_remove when a status effect
+/// of status_type STATUS_EFFECT_REPLACE is replaced by itself,
+/// or when a status effect with on_remove_on_mob_delete
+/// set to FALSE has its mob deleted
+/datum/status_effect/proc/be_replaced()
+ linked_alert = null
owner.clear_alert(id)
LAZYREMOVE(owner.status_effects, src)
owner = null
qdel(src)
-/datum/status_effect/proc/refresh()
+/// Called before being fully removed (before on_remove)
+/// Returning FALSE will cancel removal
+/datum/status_effect/proc/before_remove()
+ return TRUE
+
+/// Called when a status effect of status_type STATUS_EFFECT_REFRESH
+/// has its duration refreshed in apply_status_effect - is passed New() args
+/datum/status_effect/proc/refresh(effect, ...)
var/original_duration = initial(duration)
if(original_duration == -1)
return
duration = world.time + original_duration
-//do_after modifier!
-/datum/status_effect/proc/interact_speed_modifier()
+/// Adds nextmove modifier multiplicatively to the owner while applied
+/datum/status_effect/proc/nextmove_modifier()
return 1
-//clickdelay/nextmove modifiers!
-/datum/status_effect/proc/nextmove_modifier()
+/datum/status_effect/proc/interact_speed_modifier()
return 1
+/// Adds nextmove adjustment additiviely to the owner while applied
/datum/status_effect/proc/nextmove_adjust()
return 0
+/// Signal proc for [COMSIG_LIVING_POST_FULLY_HEAL] to remove us on fullheal
+/datum/status_effect/proc/remove_effect_on_heal(datum/source, heal_flags)
+ SIGNAL_HANDLER
+
+ if(!remove_on_fullheal)
+ return
+
+// if(!heal_flag_necessary || (heal_flags & heal_flag_necessary))
+ qdel(src)
+
+/// Remove [seconds] of duration from the status effect, qdeling / ending if we eclipse the current world time.
+/datum/status_effect/proc/remove_duration(seconds)
+ if(duration == -1) // Infinite duration
+ return FALSE
+
+ duration -= seconds
+ if(duration <= world.time)
+ qdel(src)
+ return TRUE
+
+ return FALSE
+
////////////////
// ALERT HOOK //
////////////////
@@ -94,45 +182,89 @@
// HELPER PROCS //
//////////////////
-/mob/living/proc/apply_status_effect(effect, ...) //applies a given status effect to this mob, returning the effect if it was successful
- . = FALSE
- var/datum/status_effect/S1 = effect
- LAZYINITLIST(status_effects)
- for(var/datum/status_effect/S in status_effects)
- if(S.id == initial(S1.id) && S.status_type)
- if(S.status_type == STATUS_EFFECT_REPLACE)
- S.be_replaced()
- else if(S.status_type == STATUS_EFFECT_REFRESH)
- S.refresh()
- return
- else
- return
+/mob/living/proc/apply_status_effect(datum/status_effect/new_effect, ...)
+ RETURN_TYPE(/datum/status_effect)
+
+ // The arguments we pass to the start effect. The 1st argument is this mob.
var/list/arguments = args.Copy()
arguments[1] = src
- S1 = new effect(arguments)
- . = S1
-/mob/living/proc/remove_status_effect(effect) //removes all of a given status effect from this mob, returning TRUE if at least one was removed
- . = FALSE
- if(status_effects)
- var/datum/status_effect/S1 = effect
- for(var/datum/status_effect/S in status_effects)
- if(initial(S1.id) == S.id)
- qdel(S)
- . = TRUE
-
-/mob/living/proc/has_status_effect(effect) //returns the effect if the mob calling the proc owns the given status effect
+ // If the status effect we're applying doesn't allow multiple effects, we need to handle it
+ if(initial(new_effect.status_type) != STATUS_EFFECT_MULTIPLE)
+ for(var/datum/status_effect/existing_effect as anything in status_effects)
+ if(existing_effect.id != initial(new_effect.id))
+ continue
+
+ switch(existing_effect.status_type)
+ // Multiple are allowed, continue as normal. (Not normally reachable)
+ if(STATUS_EFFECT_MULTIPLE)
+ break
+ // Only one is allowed of this type - early return
+ if(STATUS_EFFECT_UNIQUE)
+ return
+ // Replace the existing instance (deletes it).
+ if(STATUS_EFFECT_REPLACE)
+ existing_effect.be_replaced()
+ // Refresh the existing type, then early return
+ if(STATUS_EFFECT_REFRESH)
+ existing_effect.refresh(arglist(arguments))
+ return
+
+ // Create the status effect with our mob + our arguments
+ var/datum/status_effect/new_instance = new new_effect(arguments)
+ if(!QDELETED(new_instance))
+ return new_instance
+
+/mob/living/proc/remove_status_effect(datum/status_effect/removed_effect, ...)
+ var/list/arguments = args.Copy(2)
+
. = FALSE
- if(status_effects)
- var/datum/status_effect/S1 = effect
- for(var/datum/status_effect/S in status_effects)
- if(initial(S1.id) == S.id)
- return S
-
-/mob/living/proc/has_status_effect_list(effect) //returns a list of effects with matching IDs that the mod owns; use for effects there can be multiple of
- . = list()
- if(status_effects)
- var/datum/status_effect/S1 = effect
- for(var/datum/status_effect/S in status_effects)
- if(initial(S1.id) == S.id)
- . += S
+ for(var/datum/status_effect/existing_effect as anything in status_effects)
+ if(existing_effect.id == initial(removed_effect.id) && existing_effect.before_remove(arguments))
+ qdel(existing_effect)
+ . = TRUE
+
+ return .
+
+/**
+ * Checks if this mob has a status effect that shares the passed effect's ID
+ *
+ * checked_effect - TYPEPATH of a status effect to check for. Checks for its ID, not it's typepath
+ *
+ * Returns an instance of a status effect, or NULL if none were found.
+ */
+/mob/proc/has_status_effect(datum/status_effect/checked_effect)
+ // Yes I'm being cringe and putting this on the mob level even though status effects only apply to the living level
+ // There's quite a few places (namely examine and, bleh, cult code) where it's easier to not need to cast to living before checking
+ // for an effect such as blindness
+ return null
+
+/mob/living/has_status_effect(datum/status_effect/checked_effect)
+ RETURN_TYPE(/datum/status_effect)
+
+ for(var/datum/status_effect/present_effect as anything in status_effects)
+ if(present_effect.id == initial(checked_effect.id))
+ return present_effect
+
+ return null
+
+/**
+ * Returns a list of all status effects that share the passed effect type's ID
+ *
+ * checked_effect - TYPEPATH of a status effect to check for. Checks for its ID, not it's typepath
+ *
+ * Returns a list
+ */
+/mob/proc/has_status_effect_list(datum/status_effect/checked_effect)
+ // See [/mob/proc/has_status_effect] for reason behind having this on the mob level
+ return null
+
+/mob/living/has_status_effect_list(datum/status_effect/checked_effect)
+ RETURN_TYPE(/list)
+
+ var/list/effects_found = list()
+ for(var/datum/status_effect/present_effect as anything in status_effects)
+ if(present_effect.id == initial(checked_effect.id))
+ effects_found += present_effect
+
+ return effects_found
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index 2671af248dec..d1c8a46df27d 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -420,7 +420,7 @@
break
if(prob(max(5,(nearby_people*12.5*moodmod)))) //Minimum 1/20 chance of stutter
// Add a short stutter, THEN treat our word
- quirker.stuttering += max(3, quirker.stuttering)
+ quirker.adjust_stutter(0.5 SECONDS)
new_message += quirker.treat_message(word)
else
@@ -463,10 +463,10 @@
switch(rand(1,3))
if(1)
- quirker.Jitter(5)
+ quirker.adjust_jitter(5 SECONDS)
msg += "causing you to start fidgeting!"
if(2)
- quirker.stuttering = max(3, quirker.stuttering)
+ quirker.adjust_stutter(3 SECONDS)
msg += "causing you to start stuttering!"
if(3)
quirker.Stun(2 SECONDS)
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
index 5a95ab53874b..370d5a6b997d 100644
--- a/code/datums/traits/neutral.dm
+++ b/code/datums/traits/neutral.dm
@@ -306,7 +306,7 @@
heirloom.AddComponent(/datum/component/heirloom, quirk_holder.mind, family_name)
/datum/quirk/family_heirloom/on_process()
- if(heirloom in quirk_holder.GetAllContents())
+ if(heirloom in quirk_holder.get_all_contents())
SEND_SIGNAL(quirk_holder, COMSIG_CLEAR_MOOD_EVENT, "family_heirloom_missing")
SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "family_heirloom", /datum/mood_event/family_heirloom)
else
diff --git a/code/datums/view.dm b/code/datums/view.dm
index 6600536de3e2..7279df7cf8a2 100644
--- a/code/datums/view.dm
+++ b/code/datums/view.dm
@@ -16,11 +16,13 @@
default = string
apply()
-/datum/viewData/proc/safeApplyFormat()
+/datum/viewData/proc/afterViewChange()
if(isZooming())
assertFormat()
- return
- resetFormat()
+ else
+ resetFormat()
+ if(chief?.mob)
+ SEND_SIGNAL(chief.mob, COMSIG_VIEWDATA_UPDATE, getView())
/datum/viewData/proc/assertFormat()//T-Pose
winset(chief, "mapwindow.map", "zoom=0")
@@ -79,7 +81,7 @@
/datum/viewData/proc/apply()
chief.change_view(getView())
- safeApplyFormat()
+ afterViewChange()
/datum/viewData/proc/supress()
is_suppressed = TRUE
diff --git a/code/datums/weakrefs.dm b/code/datums/weakrefs.dm
index 31e0c3501b78..27bd5d943306 100644
--- a/code/datums/weakrefs.dm
+++ b/code/datums/weakrefs.dm
@@ -1,15 +1,57 @@
+/// Creates a weakref to the given input.
+/// See /datum/weakref's documentation for more information.
/proc/WEAKREF(datum/input)
if(istype(input) && !QDELETED(input))
- if(istype(input, /datum/weakref))
+ if(isweakref(input))
return input
if(!input.weak_reference)
input.weak_reference = new /datum/weakref(input)
return input.weak_reference
-/datum/proc/create_weakref() //Forced creation for admin proccalls
+/datum/proc/create_weakref() //Forced creation for admin proccalls
return WEAKREF(src)
+/**
+ * A weakref holds a non-owning reference to a datum.
+ * The datum can be referenced again using `resolve()`.
+ *
+ * To figure out why this is important, you must understand how deletion in
+ * BYOND works.
+ *
+ * Imagine a datum as a TV in a living room. When one person enters to watch
+ * TV, they turn it on. Others can come into the room and watch the TV.
+ * When the last person leaves the room, they turn off the TV because it's
+ * no longer being used.
+ *
+ * A datum being deleted tells everyone who's watching the TV to stop.
+ * If everyone leaves properly (AKA cleaning up their references), then the
+ * last person will turn off the TV, and everything is well.
+ * However, if someone is resistant (holds a hard reference after deletion),
+ * then someone has to walk in, drag them away, and turn off the TV forecefully.
+ * This process is very slow, and it's known as hard deletion.
+ *
+ * This is where weak references come in. Weak references don't count as someone
+ * watching the TV. Thus, when what it's referencing is destroyed, it will
+ * hopefully clean up properly, and limit hard deletions.
+ *
+ * A common use case for weak references is holding onto what created itself.
+ * For example, if a machine wanted to know what its last user was, it might
+ * create a `var/mob/living/last_user`. However, this is a strong reference to
+ * the mob, and thus will force a hard deletion when that mob is deleted.
+ * It is often better in this case to instead create a weakref to the user,
+ * meaning this type definition becomes `var/datum/weakref/last_user`.
+ *
+ * A good rule of thumb is that you should hold strong references to things
+ * that you *own*. For example, a dog holding a chew toy would be the owner
+ * of that chew toy, and thus a `var/obj/item/chew_toy` reference is fine
+ * (as long as it is cleaned up properly).
+ * However, a chew toy does not own its dog, so a `var/mob/living/dog/owner`
+ * might be inferior to a weakref.
+ * This is also a good rule of thumb to avoid circular references, such as the
+ * chew toy example. A circular reference that doesn't clean itself up properly
+ * will always hard delete.
+ */
/datum/weakref
var/reference
@@ -17,13 +59,50 @@
reference = REF(thing)
/datum/weakref/Destroy(force)
- if(!force)
- return QDEL_HINT_LETMELIVE //Let BYOND autoGC thiswhen nothing is using it anymore.
var/datum/target = resolve()
+ qdel(target)
+
+ if(!force)
+ return QDEL_HINT_LETMELIVE //Let BYOND autoGC thiswhen nothing is using it anymore.
target?.weak_reference = null
return ..()
+/**
+ * Retrieves the datum that this weakref is referencing.
+ *
+ * This will return `null` if the datum was deleted. This MUST be respected.
+ */
/datum/weakref/proc/resolve()
var/datum/D = locate(reference)
return (!QDELETED(D) && D.weak_reference == src) ? D : null
+/**
+ * SERIOUSLY READ THE AUTODOC COMMENT FOR THIS PROC BEFORE EVEN THINKING ABOUT USING IT
+ *
+ * Like resolve, but doesn't care if the datum is being qdeleted but hasn't been deleted yet.
+ *
+ * The return value of this proc leaves hanging references if the datum is being qdeleted but hasn't been deleted yet.
+ *
+ * Do not do anything that would create a lasting reference to the return value, such as giving it a tag, putting it on the map,
+ * adding it to an atom's contents or vis_contents, giving it a key (if it's a mob), attaching it to an atom (if it's an image),
+ * or assigning it to a datum or list referenced somewhere other than a temporary value.
+ *
+ * Unless you're resolving a weakref to a datum in a COMSIG_PARENT_QDELETING signal handler registered on that very same datum,
+ * just use resolve instead.
+ */
+/datum/weakref/proc/hard_resolve()
+ var/datum/D = locate(reference)
+ return (D?.weak_reference == src) ? D : null
+
+/datum/weakref/vv_get_dropdown()
+ . = ..()
+ VV_DROPDOWN_OPTION(VV_HK_WEAKREF_RESOLVE, "Go to reference")
+
+/datum/weakref/vv_do_topic(list/href_list)
+ . = ..()
+ if(href_list[VV_HK_WEAKREF_RESOLVE])
+ if(!check_rights(NONE))
+ return
+ var/datum/R = resolve()
+ if(R)
+ usr.client.debug_variables(R)
diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm
index a8ff9535fb12..ff76b566b175 100644
--- a/code/datums/wounds/_wounds.dm
+++ b/code/datums/wounds/_wounds.dm
@@ -133,6 +133,7 @@
set_limb(L)
LAZYADD(victim.all_wounds, src)
LAZYADD(limb.wounds, src)
+ update_descriptions()
limb.update_wounds()
if(status_effect_type)
linked_status_effect = victim.apply_status_effect(status_effect_type, src)
@@ -163,6 +164,10 @@
wound_injury(old_wound, attack_direction = attack_direction)
second_wind()
+// Updates descriptive texts for the wound, in case it can get altered for whatever reason
+/datum/wound/proc/update_descriptions()
+ return
+
/// Remove the wound from whatever it's afflicting, and cleans up whateverstatus effects it had or modifiers it had on interaction times. ignore_limb is used for detachments where we only want to forget the victim
/datum/wound/proc/remove_wound(ignore_limb, replaced = FALSE)
//TODO: have better way to tell if we're getting removed without replacement (full heal) scar stuff
diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm
index 37e9e4f390f7..24520151f986 100644
--- a/code/datums/wounds/bones.dm
+++ b/code/datums/wounds/bones.dm
@@ -374,7 +374,7 @@
to_chat(victim, span_userdanger("[user] finishes applying [I] to your [limb.name], and you can feel the bones exploding with pain as they begin melting and reforming!"))
else
var/painkiller_bonus = 0
- if(victim.drunkenness > 10)
+ if(victim.get_drunk_amount() > 10)
painkiller_bonus += 10
if(victim.reagents?.has_reagent(/datum/reagent/medicine/morphine))
painkiller_bonus += 20
diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm
index 248bd418e877..664e1458ef10 100644
--- a/code/datums/wounds/slash.dm
+++ b/code/datums/wounds/slash.dm
@@ -280,3 +280,12 @@
status_effect_type = /datum/status_effect/wound/slash/critical
scar_keyword = "slashcritical"
wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | MANGLES_FLESH)
+
+// Subtype for cleave (heretic spell)
+/datum/wound/slash/critical/cleave
+ name = "Burning Avulsion"
+ examine_desc = "is ruptured, spraying blood wildly"
+ clot_rate = 0.01
+
+/datum/wound/slash/critical/cleave/update_descriptions()
+ occur_text = "is ruptured"
diff --git a/code/game/alternate_appearance.dm b/code/game/alternate_appearance.dm
index cfac8a79a819..78a481a97dd8 100644
--- a/code/game/alternate_appearance.dm
+++ b/code/game/alternate_appearance.dm
@@ -9,7 +9,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
for(var/K in alternate_appearances)
var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K]
if(AA.appearance_key == key)
- AA.remove_from_hud(src)
+ AA.remove_atom_from_hud(src)
break
/atom/proc/add_alt_appearance(type, key, ...)
@@ -17,6 +17,9 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
return
if(alternate_appearances && alternate_appearances[key])
return
+ if(!ispath(type, /datum/atom_hud/alternate_appearance))
+ CRASH("Invalid type passed in: [type]")
+
var/list/arguments = args.Copy(2)
return new type(arglist(arguments))
@@ -25,9 +28,16 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
var/transfer_overlays = FALSE
/datum/atom_hud/alternate_appearance/New(key)
+ // We use hud_icons to register our hud, so we need to do this before the parent call
+ appearance_key = key
+ hud_icons = list(appearance_key)
..()
+
GLOB.active_alternate_appearances += src
- appearance_key = key
+
+ for(var/mob in GLOB.player_list)
+ if(mobShouldSee(mob))
+ show_to(mob)
/datum/atom_hud/alternate_appearance/Destroy()
GLOB.active_alternate_appearances -= src
@@ -35,18 +45,18 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
/datum/atom_hud/alternate_appearance/proc/onNewMob(mob/M)
if(mobShouldSee(M))
- add_hud_to(M)
+ show_to(M)
/datum/atom_hud/alternate_appearance/proc/mobShouldSee(mob/M)
return FALSE
-/datum/atom_hud/alternate_appearance/add_to_hud(atom/A, image/I)
+/datum/atom_hud/alternate_appearance/add_atom_to_hud(atom/A, image/I)
. = ..()
if(.)
LAZYINITLIST(A.alternate_appearances)
A.alternate_appearances[appearance_key] = src
-/datum/atom_hud/alternate_appearance/remove_from_hud(atom/A)
+/datum/atom_hud/alternate_appearance/remove_atom_from_hud(atom/A)
. = ..()
if(.)
LAZYREMOVE(A.alternate_appearances, appearance_key)
@@ -57,22 +67,24 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
//an alternate appearance that attaches a single image to a single atom
/datum/atom_hud/alternate_appearance/basic
var/atom/target
- var/image/theImage
+ var/image/image
var/add_ghost_version = FALSE
- var/ghost_appearance
+ var/datum/atom_hud/alternate_appearance/basic/observers/ghost_appearance
+ uses_global_hud_category = FALSE
/datum/atom_hud/alternate_appearance/basic/New(key, image/I, options = AA_TARGET_SEE_APPEARANCE)
..()
transfer_overlays = options & AA_MATCH_TARGET_OVERLAYS
- theImage = I
+ image = I
target = I.loc
if(transfer_overlays)
I.copy_overlays(target)
- hud_icons = list(appearance_key)
- add_to_hud(target, I)
+ add_atom_to_hud(target)
+ target.set_hud_image_active(appearance_key, exclusive_hud = src)
+
if((options & AA_TARGET_SEE_APPEARANCE) && ismob(target))
- add_hud_to(target)
+ show_to(target)
if(add_ghost_version)
var/image/ghost_image = image(icon = I.icon , icon_state = I.icon_state, loc = I.loc)
ghost_image.override = FALSE
@@ -81,43 +93,34 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
/datum/atom_hud/alternate_appearance/basic/Destroy()
. = ..()
+ QDEL_NULL(image)
+ target = null
if(ghost_appearance)
QDEL_NULL(ghost_appearance)
-/datum/atom_hud/alternate_appearance/basic/add_to_hud(atom/A)
+/datum/atom_hud/alternate_appearance/basic/add_atom_to_hud(atom/A)
LAZYINITLIST(A.hud_list)
- A.hud_list[appearance_key] = theImage
+ A.hud_list[appearance_key] = image
. = ..()
-/datum/atom_hud/alternate_appearance/basic/remove_from_hud(atom/A)
+/datum/atom_hud/alternate_appearance/basic/remove_atom_from_hud(atom/A)
. = ..()
- A.hud_list -= appearance_key
+ LAZYREMOVE(A.hud_list, appearance_key)
+ A.set_hud_image_inactive(appearance_key)
if(. && !QDELETED(src))
qdel(src)
/datum/atom_hud/alternate_appearance/basic/copy_overlays(atom/other, cut_old)
- theImage.copy_overlays(other, cut_old)
+ image.copy_overlays(other, cut_old)
/datum/atom_hud/alternate_appearance/basic/everyone
add_ghost_version = TRUE
-/datum/atom_hud/alternate_appearance/basic/everyone/New()
- ..()
- for(var/mob in GLOB.mob_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
/datum/atom_hud/alternate_appearance/basic/everyone/mobShouldSee(mob/M)
- return !isobserver(M)
+ return !isdead(M)
/datum/atom_hud/alternate_appearance/basic/silicons
-/datum/atom_hud/alternate_appearance/basic/silicons/New()
- ..()
- for(var/mob in GLOB.silicon_mobs)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
/datum/atom_hud/alternate_appearance/basic/silicons/mobShouldSee(mob/M)
if(issilicon(M))
return TRUE
@@ -126,23 +129,11 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
/datum/atom_hud/alternate_appearance/basic/observers
add_ghost_version = FALSE //just in case, to prevent infinite loops
-/datum/atom_hud/alternate_appearance/basic/observers/New()
- ..()
- for(var/mob in GLOB.dead_mob_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
/datum/atom_hud/alternate_appearance/basic/observers/mobShouldSee(mob/M)
return isobserver(M)
/datum/atom_hud/alternate_appearance/basic/noncult
-/datum/atom_hud/alternate_appearance/basic/noncult/New()
- ..()
- for(var/mob in GLOB.player_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
/datum/atom_hud/alternate_appearance/basic/noncult/mobShouldSee(mob/M)
if(!iscultist(M))
return TRUE
@@ -150,46 +141,34 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
/datum/atom_hud/alternate_appearance/basic/cult
-/datum/atom_hud/alternate_appearance/basic/cult/New()
- ..()
- for(var/mob in GLOB.player_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
/datum/atom_hud/alternate_appearance/basic/cult/mobShouldSee(mob/M)
if(iscultist(M))
return TRUE
return FALSE
-/datum/atom_hud/alternate_appearance/basic/blessedAware
-
-/datum/atom_hud/alternate_appearance/basic/blessedAware/New()
- ..()
- for(var/mob in GLOB.mob_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
+/datum/atom_hud/alternate_appearance/basic/blessed_aware
-/datum/atom_hud/alternate_appearance/basic/blessedAware/mobShouldSee(mob/M)
- if(M.mind && (M.mind.assigned_role == "Chaplain"))
+/datum/atom_hud/alternate_appearance/basic/blessed_aware/mobShouldSee(mob/M)
+ if(M.mind?.holy_role)
return TRUE
if (istype(M, /mob/living/simple_animal/hostile/construct/wraith))
return TRUE
- if(isrevenant(M) || iseminence(M) || iswizard(M))
+ if(isrevenant(M) || iswizard(M))
return TRUE
return FALSE
-datum/atom_hud/alternate_appearance/basic/onePerson
+/datum/atom_hud/alternate_appearance/basic/one_person
var/mob/seer
-/datum/atom_hud/alternate_appearance/basic/onePerson/mobShouldSee(mob/M)
+/datum/atom_hud/alternate_appearance/basic/one_person/mobShouldSee(mob/M)
if(M == seer)
return TRUE
return FALSE
-/datum/atom_hud/alternate_appearance/basic/onePerson/New(key, image/I, mob/living/M)
+/datum/atom_hud/alternate_appearance/basic/one_person/New(key, image/I, mob/living/M)
..(key, I, FALSE)
seer = M
- add_hud_to(seer)
+ show_to(seer)
/datum/atom_hud/alternate_appearance/basic/scent_hunter
@@ -197,7 +176,7 @@ datum/atom_hud/alternate_appearance/basic/onePerson
..()
for(var/mob in GLOB.player_list)
if(mobShouldSee(mob))
- add_hud_to(mob)
+ show_to(mob)
/datum/atom_hud/alternate_appearance/basic/scent_hunter/mobShouldSee(mob/M)
if(isliving(M))
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index fa710dd02093..8dbcb4c875d6 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -23,7 +23,7 @@
/// There is a risk of this and contained_turfs leaking, so a subsystem will run it down to 0 incrementally if it gets too large
var/list/turf/turfs_to_uncontain = list()
- var/area_flags = 0
+ var/area_flags = NONE
var/map_name // Set in New(); preserves the name set by the map maker, even if renamed by the Blueprints.
@@ -673,7 +673,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if(!L.client.played)
SEND_SOUND(L, sound(sound, repeat = 0, wait = 0, volume = 25, channel = CHANNEL_AMBIENCE))
L.client.played = TRUE
- addtimer(CALLBACK(L.client, /client/proc/ResetAmbiencePlayed), 600)
+ addtimer(CALLBACK(L.client, /client/proc/ResetAmbiencePlayed), 1 MINUTES)
/**
* Called when an atom exits an area
diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm
index bc9e18a1db21..6e3f25170c6b 100644
--- a/code/game/area/areas/shuttles.dm
+++ b/code/game/area/areas/shuttles.dm
@@ -139,6 +139,7 @@
/area/shuttle/supply
name = "Supply Shuttle"
blob_allowed = FALSE
+ area_flags = NOTELEPORT
/area/shuttle/ai
name = "AI Ship Shuttle"
@@ -152,10 +153,12 @@
/area/shuttle/escape/luxury
name = "Luxurious Emergency Shuttle"
+ area_flags = NOTELEPORT
/area/shuttle/escape/arena
name = "The Arena"
noteleport = TRUE
+ area_flags = NOTELEPORT
/area/shuttle/escape/meteor
name = "\proper a meteor with engines strapped to it"
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 9861c96b06a4..023e82831b3b 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -20,8 +20,12 @@
///Reagents holder
var/datum/reagents/reagents = null
- ///This atom's HUD (med/sec, etc) images. Associative list.
+ ///all of this atom's HUD (med/sec, etc) images. Associative list of the form: list(hud category = hud image or images for that category).
+ ///most of the time hud category is associated with a single image, sometimes its associated with a list of images.
+ ///not every hud in this list is actually used. for ones available for others to see, look at active_hud_list.
var/list/image/hud_list = null
+ ///all of this atom's HUD images which can actually be seen by players with that hud
+ var/list/image/active_hud_list = null
///HUD images that this atom can provide.
var/list/hud_possible
@@ -78,6 +82,10 @@
var/chat_color_darkened // A luminescence-shifted value of the last color calculated for chatmessage overlays
+ ///Default pixel x shifting for the atom's icon.
+ var/base_pixel_x = 0
+ ///Default pixel y shifting for the atom's icon.
+ var/base_pixel_y = 0
///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy()
var/list/targeted_by
@@ -208,7 +216,7 @@
if(alternate_appearances)
for(var/current_alternate_appearance in alternate_appearances)
var/datum/atom_hud/alternate_appearance/selected_alternate_appearance = alternate_appearances[current_alternate_appearance]
- selected_alternate_appearance.remove_from_hud(src)
+ selected_alternate_appearance.remove_atom_from_hud(src)
if(reagents)
qdel(reagents)
@@ -510,6 +518,24 @@
SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .)
+/**
+ * Shows any and all examine text related to any status effects the user has.
+ */
+/mob/living/proc/get_status_effect_examinations()
+ var/list/examine_list = list()
+
+ for(var/datum/status_effect/effect as anything in status_effects)
+ var/effect_text = effect.get_examine_text()
+ if(!effect_text)
+ continue
+
+ examine_list += effect_text
+
+ if(!length(examine_list))
+ return
+
+ return examine_list.Join("\n")
+
/**
* Called when a mob examines (shift click or verb) this atom twice (or more) within EXAMINE_MORE_WINDOW (default 1.5 seconds)
*
@@ -1316,7 +1342,7 @@
. = ..()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.build_all_button_icons()
/atom/movable/proc/get_filter(name)
if(filter_data && filter_data[name])
@@ -1350,6 +1376,14 @@
for(var/comp_mat in material_comp)
.[comp_mat] += material_comp[comp_mat]
+///Setter for the `density` variable to append behavior related to its changing.
+/atom/proc/set_density(new_value)
+ SHOULD_CALL_PARENT(TRUE)
+ if(density == new_value)
+ return
+ . = density
+ density = new_value
+
/**
* Causes effects when the atom gets hit by a rust effect from heretics
*
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index c45a85ed8098..de0baf8b49cc 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -37,6 +37,8 @@
var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc.
var/atom/movable/pulling
var/grab_state = 0
+ // The strongest grab we can acomplish
+ var/max_grab = GRAB_KILL
var/throwforce = 0
var/datum/component/orbiter/orbiting
var/can_be_z_moved = TRUE
@@ -202,10 +204,10 @@
return FALSE
return ..()
-/atom/movable/proc/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE)
- if(QDELETED(AM))
+/atom/movable/proc/start_pulling(atom/movable/pulled_atom, state, force = move_force, supress_message = FALSE)
+ if(QDELETED(pulled_atom))
return FALSE
- if(!(AM.can_be_pulled(src, state, force)))
+ if(!(pulled_atom.can_be_pulled(src, state, force)))
return FALSE
// If we're pulling something then drop what we're currently pulling and pull this instead.
@@ -214,79 +216,84 @@
stop_pulling()
return FALSE
// Are we trying to pull something we are already pulling? Then enter grab cycle and end.
- if(AM == pulling)
- grab_state = state
- if(istype(AM,/mob/living))
- var/mob/living/AMob = AM
- AMob.grabbedby(src)
+ if(pulled_atom == pulling)
+ setGrabState(state)
+ if(isliving(pulled_atom))
+ var/mob/living/pulled_mob = pulled_atom
+ pulled_mob.grabbedby(src)
return TRUE
stop_pulling()
- if(AM.pulledby)
- log_combat(AM, AM.pulledby, "pulled from", src)
- AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.
- pulling = AM
- AM.pulledby = src
- grab_state = state
- if(ismob(AM))
- var/mob/M = AM
- log_combat(src, M, "grabbed", addition="passive grab")
+
+ if(pulled_atom.pulledby)
+ log_combat(pulled_atom, pulled_atom.pulledby, "pulled from", src)
+ pulled_atom.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.
+ pulling = pulled_atom
+ pulled_atom.set_pulledby(src)
+ SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, pulled_atom, state, force)
+ setGrabState(state)
+ if(ismob(pulled_atom))
+ var/mob/pulled_mob = pulled_atom
+ log_combat(src, pulled_mob, "grabbed", addition="passive grab")
if(!supress_message)
- visible_message(span_warning("[src] has grabbed [M] passively!"))
+ pulled_mob.visible_message(span_warning("[src] grabs [pulled_mob] passively."), \
+ span_danger("[src] grabs you passively."))
return TRUE
/atom/movable/proc/stop_pulling()
- if(pulling)
- pulling.pulledby = null
- var/mob/living/ex_pulled = pulling
- pulling = null
- grab_state = 0
- if(isliving(ex_pulled))
- var/mob/living/L = ex_pulled
- L.update_mobility()// mob gets up if it was lyng down in a chokehold
-
-/atom/movable/proc/Move_Pulled(atom/A)
+ if(!pulling)
+ return
+ pulling.set_pulledby(null)
+ setGrabState(GRAB_PASSIVE)
+ var/atom/movable/old_pulling = pulling
+ pulling = null
+ SEND_SIGNAL(old_pulling, COMSIG_ATOM_NO_LONGER_PULLED, src)
+ SEND_SIGNAL(src, COMSIG_ATOM_NO_LONGER_PULLING, old_pulling)
+
+///Reports the event of the change in value of the pulledby variable.
+/atom/movable/proc/set_pulledby(new_pulledby)
+ if(new_pulledby == pulledby)
+ return FALSE //null signals there was a change, be sure to return FALSE if none happened here.
+ . = pulledby
+ pulledby = new_pulledby
+
+/atom/movable/proc/Move_Pulled(atom/moving_atom)
if(!pulling)
return FALSE
- if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src))
+ if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src, src, pulling))
stop_pulling()
return FALSE
if(isliving(pulling))
- var/mob/living/L = pulling
- if(L.buckled && L.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it
+ var/mob/living/pulling_mob = pulling
+ if(pulling_mob.buckled && pulling_mob.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it
stop_pulling()
return FALSE
- if(A == loc && pulling.density)
+ if(moving_atom == loc && pulling.density)
return FALSE
- var/move_dir = get_dir(pulling.loc, A)
+ var/move_dir = get_dir(pulling.loc, moving_atom)
if(!Process_Spacemove(move_dir))
return FALSE
pulling.Move(get_step(pulling.loc, move_dir), move_dir, glide_size)
return TRUE
-/mob/living/Move_Pulled(atom/A)
+/mob/living/Move_Pulled(atom/moving_atom)
. = ..()
- if(!. || !isliving(A))
+ if(!. || !isliving(moving_atom))
return
- var/mob/living/L = A
- set_pull_offsets(L, grab_state)
+ var/mob/living/pulled_mob = moving_atom
+ set_pull_offsets(pulled_mob, grab_state)
-/atom/movable/proc/check_pulling()
+/atom/movable/proc/check_pulling(only_pulling = FALSE, z_allowed = FALSE)
if(pulling)
- var/atom/movable/pullee = pulling
- if(pullee && get_dist(src, pullee) > 1)
+ if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed))
stop_pulling()
- return
- if(!isturf(loc))
+ else if(!isturf(loc))
stop_pulling()
- return
- if(pullee && !isturf(pullee.loc) && pullee.loc != loc) //to be removed once all code that changes an object's loc uses forceMove().
- log_game("DEBUG:[src]'s pull on [pullee] wasn't broken despite [pullee] being in [pullee.loc]. Pull stopped manually.")
+ else if(pulling && !isturf(pulling.loc) && pulling.loc != loc) //to be removed once all code that changes an object's loc uses forceMove().
+ log_game("DEBUG:[src]'s pull on [pulling] wasn't broken despite [pulling] being in [pulling.loc]. Pull stopped manually.")
stop_pulling()
- return
- if(pulling.anchored || pulling.move_resist > move_force)
+ else if(pulling.anchored || pulling.move_resist > move_force)
stop_pulling()
- return
- if(pulledby && moving_diagonally != FIRST_DIAG_STEP && get_dist(src, pulledby) > 1) //separated from our puller and not in the middle of a diagonal move.
+ if(!only_pulling && pulledby && moving_diagonally != FIRST_DIAG_STEP && (get_dist(src, pulledby) > 1 || z != pulledby.z)) //separated from our puller and not in the middle of a diagonal move.
pulledby.stop_pulling()
/atom/movable/proc/set_glide_size(target = 8)
@@ -948,3 +955,30 @@
if(force < (move_resist * MOVE_FORCE_PULL_RATIO))
return FALSE
return TRUE
+
+/**
+ * Updates the grab state of the movable
+ *
+ * This exists to act as a hook for behaviour
+ */
+/atom/movable/proc/setGrabState(newstate)
+ if(newstate == grab_state)
+ return
+ SEND_SIGNAL(src, COMSIG_MOVABLE_SET_GRAB_STATE, newstate)
+ . = grab_state
+ grab_state = newstate
+ switch(grab_state) // Current state.
+ if(GRAB_PASSIVE)
+ REMOVE_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT)
+ REMOVE_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT)
+ if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher.
+ REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
+ if(GRAB_AGGRESSIVE)
+ if(. >= GRAB_NECK) // Grab got downgraded.
+ REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
+ else // Grab got upgraded from a passive one.
+ ADD_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT)
+ ADD_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT)
+ if(GRAB_NECK, GRAB_KILL)
+ if(. <= GRAB_AGGRESSIVE)
+ ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index 3278a10c77bc..38b25f98b593 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -9,38 +9,37 @@
/atom/proc/add_to_all_human_data_huds()
for(var/datum/atom_hud/data/human/hud in GLOB.huds)
- hud.add_to_hud(src)
+ hud.add_atom_to_hud(src)
/atom/proc/remove_from_all_data_huds()
for(var/datum/atom_hud/data/hud in GLOB.huds)
- hud.remove_from_hud(src)
+ hud.remove_atom_from_hud(src)
/datum/atom_hud/data
/datum/atom_hud/data/human/medical
- hud_icons = list(STATUS_HUD, HEALTH_HUD, NANITE_HUD)
+ hud_icons = list(STATUS_HUD, HEALTH_HUD)
/datum/atom_hud/data/human/medical/basic
/datum/atom_hud/data/human/medical/basic/proc/check_sensors(mob/living/carbon/human/H)
if(!istype(H))
- return 0
+ return FALSE
var/obj/item/clothing/under/U = H.w_uniform
if(!istype(U))
- return 0
+ return FALSE
if(U.sensor_mode <= SENSOR_VITALS)
- return 0
- return 1
+ return FALSE
+ return TRUE
-/datum/atom_hud/data/human/medical/basic/add_to_single_hud(mob/M, mob/living/carbon/H)
+/datum/atom_hud/data/human/medical/basic/add_atom_to_single_mob_hud(mob/M, mob/living/carbon/H)
if(check_sensors(H))
..()
/datum/atom_hud/data/human/medical/basic/proc/update_suit_sensors(mob/living/carbon/H)
- check_sensors(H) ? add_to_hud(H) : remove_from_hud(H)
+ check_sensors(H) ? add_atom_to_hud(H) : remove_atom_from_hud(H)
/datum/atom_hud/data/human/medical/advanced
- do_silicon_check = TRUE
/datum/atom_hud/data/human/security
@@ -49,7 +48,6 @@
/datum/atom_hud/data/human/security/advanced
hud_icons = list(ID_HUD, IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD, WANTED_HUD, NANITE_HUD)
- do_silicon_check = TRUE
/datum/atom_hud/data/human/security/advanced/hos
hud_icons = list(ID_HUD, IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD, WANTED_HUD, NANITE_HUD, STATUS_HUD, HEALTH_HUD)
@@ -57,13 +55,14 @@
/datum/atom_hud/data/diagnostic
/datum/atom_hud/data/diagnostic/basic
- hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_CIRCUIT_HUD, DIAG_TRACK_HUD, DIAG_AIRLOCK_HUD, DIAG_NANITE_FULL_HUD, DIAG_LAUNCHPAD_HUD)
- do_silicon_check = TRUE
+ hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_TRACK_HUD, DIAG_AIRLOCK_HUD, DIAG_LAUNCHPAD_HUD)
/datum/atom_hud/data/diagnostic/advanced
- hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_CIRCUIT_HUD, DIAG_TRACK_HUD, DIAG_AIRLOCK_HUD, DIAG_NANITE_FULL_HUD, DIAG_LAUNCHPAD_HUD, DIAG_PATH_HUD)
+ hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_TRACK_HUD, DIAG_AIRLOCK_HUD, DIAG_LAUNCHPAD_HUD, DIAG_PATH_HUD)
/datum/atom_hud/data/bot_path
+ // This hud exists so the bot can see itself, that's all
+ uses_global_hud_category = FALSE
hud_icons = list(DIAG_PATH_HUD)
/datum/atom_hud/abductor
@@ -75,12 +74,12 @@
/datum/atom_hud/ai_detector
hud_icons = list(AI_DETECT_HUD)
-/datum/atom_hud/ai_detector/add_hud_to(mob/M)
+/datum/atom_hud/ai_detector/show_to(mob/new_viewer)
..()
- if(M && (hudusers.len == 1))
- for(var/V in GLOB.aiEyes)
- var/mob/camera/aiEye/E = V
- E.update_ai_detect_hud()
+ if(!new_viewer || hud_users.len != 1)
+ return
+ for(var/mob/camera/aiEye/eye as anything in GLOB.aiEyes)
+ eye.update_ai_detect_hud()
/* MED/SEC/DIAG HUD HOOKS */
@@ -89,7 +88,7 @@
*/
/***********************************************
- Medical HUD! Basic mode needs suit sensors on.
+Medical HUD! Basic mode needs suit sensors on.
************************************************/
//HELPERS
@@ -129,7 +128,10 @@
//called when a living mob changes health
/mob/living/proc/med_hud_set_health()
- var/image/holder = hud_list[HEALTH_HUD]
+ var/image/holder = hud_list?[HEALTH_HUD]
+ if (isnull(holder))
+ return
+
holder.icon_state = "hud[RoundHealth(src)]"
var/icon/I = icon(icon, icon_state, dir)
holder.pixel_y = I.Height() - world.icon_size
@@ -140,7 +142,10 @@
//called when a carbon changes stat, virus or XENO_HOST
/mob/living/proc/med_hud_set_status()
- var/image/holder = hud_list[STATUS_HUD]
+ var/image/holder = hud_list?[STATUS_HUD]
+ if (isnull(holder))
+ return
+
var/icon/I = icon(icon, icon_state, dir)
holder.pixel_y = I.Height() - world.icon_size
if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH)))
@@ -149,7 +154,10 @@
holder.icon_state = "hudhealthy"
/mob/living/carbon/med_hud_set_status()
- var/image/holder = hud_list[STATUS_HUD]
+ var/image/holder = hud_list?[STATUS_HUD]
+ if (isnull(holder))
+ return
+
var/icon/I = icon(icon, icon_state, dir)
var/virus_threat = check_virus()
holder.pixel_y = I.Height() - world.icon_size
@@ -186,7 +194,7 @@
/***********************************************
- Security HUDs! Basic mode shows only the job.
+Security HUDs! Basic mode shows only the job.
************************************************/
//HOOKS
@@ -205,54 +213,64 @@
for(var/i in list(IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD))
holder = hud_list[i]
holder.icon_state = null
+ set_hud_image_inactive(i)
+
for(var/obj/item/implant/I in implants)
if(istype(I, /obj/item/implant/tracking))
holder = hud_list[IMPTRACK_HUD]
var/icon/IC = icon(icon, icon_state, dir)
holder.pixel_y = IC.Height() - world.icon_size
holder.icon_state = "hud_imp_tracking"
+ set_hud_image_active(IMPTRACK_HUD)
+
else if(istype(I, /obj/item/implant/chem))
holder = hud_list[IMPCHEM_HUD]
var/icon/IC = icon(icon, icon_state, dir)
holder.pixel_y = IC.Height() - world.icon_size
holder.icon_state = "hud_imp_chem"
+ set_hud_image_active(IMPCHEM_HUD)
+
if(HAS_TRAIT(src, TRAIT_MINDSHIELD))
holder = hud_list[IMPLOYAL_HUD]
var/icon/IC = icon(icon, icon_state, dir)
holder.pixel_y = IC.Height() - world.icon_size
holder.icon_state = "hud_imp_loyal"
+ set_hud_image_active(IMPLOYAL_HUD)
/mob/living/carbon/human/proc/sec_hud_set_security_status()
var/image/holder = hud_list[WANTED_HUD]
- var/icon/I = icon(icon, icon_state, dir)
- holder.pixel_y = I.Height() - world.icon_size
- var/perpname = get_face_name(get_id_name(""))
- if(perpname && GLOB.data_core)
- var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security)
- if(R)
- switch(R.fields["criminal"])
- if("*Arrest*")
- holder.icon_state = "hudwanted"
- return
- if("Search")
- holder.icon_state = "hudsearch"
- return
- if("Incarcerated")
- holder.icon_state = "hudincarcerated"
- return
- if("Suspected")
- holder.icon_state = "hudsuspected"
- return
- if("Paroled")
- holder.icon_state = "hudparolled"
- return
- if("Discharged")
- holder.icon_state = "huddischarged"
- return
- holder.icon_state = null
+ var/icon/sec_icon = icon(icon, icon_state, dir)
+ holder.pixel_y = sec_icon.Height() - world.icon_size
+ var/perp_name = get_face_name(get_id_name(""))
+
+ if(!perp_name || !GLOB.data_core)
+ holder.icon_state = null
+ set_hud_image_inactive(WANTED_HUD)
+ return
+
+
+ var/datum/data/record/target = find_record("name", perp_name, GLOB.data_core.security)
+ if(!target || target.fields["criminal"] == WANTED_NONE)
+ holder.icon_state = null
+ set_hud_image_inactive(WANTED_HUD)
+ return
+
+ switch(target.fields["criminal"])
+ if(WANTED_ARREST)
+ holder.icon_state = "hudwanted"
+ if(WANTED_PRISONER)
+ holder.icon_state = "hudincarcerated"
+ if(WANTED_SUSPECT)
+ holder.icon_state = "hudsuspected"
+ if(WANTED_PAROLE)
+ holder.icon_state = "hudparolled"
+ if(WANTED_DISCHARGED)
+ holder.icon_state = "huddischarged"
+
+ set_hud_image_active(WANTED_HUD)
/***********************************************
- Diagnostic HUDs!
+Diagnostic HUDs!
************************************************/
/mob/living/proc/hud_set_nanite_indicator()
@@ -321,10 +339,13 @@
holder.pixel_y = I.Height() - world.icon_size
if(!shell) //Not an AI shell
holder.icon_state = null
+ set_hud_image_inactive(DIAG_TRACK_HUD)
+ return
else if(deployed) //AI shell in use by an AI
holder.icon_state = "hudtrackingai"
- else //Empty AI shell
+ else //Empty AI shell
holder.icon_state = "hudtracking"
+ set_hud_image_active(DIAG_TRACK_HUD)
//AI side tracking of AI shell control
/mob/living/silicon/ai/proc/diag_hud_set_deployed() //Shows tracking beacons on the mech
@@ -333,8 +354,10 @@
holder.pixel_y = I.Height() - world.icon_size
if(!deployed_shell)
holder.icon_state = null
+ set_hud_image_inactive(DIAG_TRACK_HUD)
else //AI is currently controlling a shell
holder.icon_state = "hudtrackingai"
+ set_hud_image_active(DIAG_TRACK_HUD)
/*~~~~~~~~~~~~~~~~~~~~
BIG STOMPY MECHS
@@ -356,16 +379,19 @@
else
holder.icon_state = "hudnobatt"
-
/obj/mecha/proc/diag_hud_set_mechstat()
var/image/holder = hud_list[DIAG_STAT_HUD]
var/icon/I = icon(icon, icon_state, dir)
holder.pixel_y = I.Height() - world.icon_size
- holder.icon_state = null
if(internal_damage)
holder.icon_state = "hudwarn"
+ set_hud_image_active(DIAG_STAT_HUD)
+ return
+ holder.icon_state = null
+ set_hud_image_inactive(DIAG_STAT_HUD)
-/obj/mecha/proc/diag_hud_set_mechtracking() //Shows tracking beacons on the mech
+///Shows tracking beacons on the mech
+/obj/mecha/proc/diag_hud_set_mechtracking()
var/image/holder = hud_list[DIAG_TRACK_HUD]
var/icon/I = icon(icon, icon_state, dir)
holder.pixel_y = I.Height() - world.icon_size
@@ -420,12 +446,24 @@
else
holder.icon_state = ""
+/mob/living/simple_animal/bot/mulebot/proc/diag_hud_set_mulebotcell()
+ var/image/holder = hud_list[DIAG_BATT_HUD]
+ var/icon/I = icon(icon, icon_state, dir)
+ holder.pixel_y = I.Height() - world.icon_size
+ if(cell)
+ var/chargelvl = (cell.charge/cell.maxcharge)
+ holder.icon_state = "hudbatt[RoundDiagBar(chargelvl)]"
+ else
+ holder.icon_state = "hudnobatt"
+
/*~~~~~~~~~~~~
Airlocks!
~~~~~~~~~~~~~*/
/obj/machinery/door/airlock/proc/diag_hud_set_electrified()
+ if(secondsElectrified == MACHINE_NOT_ELECTRIFIED)
+ set_hud_image_inactive(DIAG_AIRLOCK_HUD)
+ return
+
var/image/holder = hud_list[DIAG_AIRLOCK_HUD]
- if(secondsElectrified != MACHINE_NOT_ELECTRIFIED)
- holder.icon_state = "electrified"
- else
- holder.icon_state = ""
+ holder.icon_state = "electrified"
+ set_hud_image_active(DIAG_AIRLOCK_HUD)
diff --git a/code/game/gamemodes/bloodsuckers/bloodsucker.dm b/code/game/gamemodes/bloodsuckers/bloodsucker.dm
index 60b56bb1354d..7ca2f485636a 100644
--- a/code/game/gamemodes/bloodsuckers/bloodsucker.dm
+++ b/code/game/gamemodes/bloodsuckers/bloodsucker.dm
@@ -38,14 +38,8 @@
if(!antag_candidates.len)
break
var/datum/mind/bloodsucker = antag_pick(antag_candidates)
- //Yogs start -- fixes plasmaman vampires
- var/species_type = bloodsucker?.current?.client.prefs.read_preference(/datum/preference/choiced/species)
- var/datum/species/species = new species_type
- var/noblood = (NOBLOOD in species.species_traits)
- qdel(species)
-
- if(noblood)
+ if(!bloodsucker.prepare_bloodsucker(bloodsucker))
antag_candidates -= bloodsucker // kinda need to do this to prevent some edge-case infinite loop or whatever
i-- // to undo the imminent increment
continue
diff --git a/code/game/gamemodes/bloodsuckers/traitorsuckers.dm b/code/game/gamemodes/bloodsuckers/traitorsuckers.dm
index dae0657ceb6e..d8ba9b27550a 100644
--- a/code/game/gamemodes/bloodsuckers/traitorsuckers.dm
+++ b/code/game/gamemodes/bloodsuckers/traitorsuckers.dm
@@ -19,7 +19,7 @@
var/list/possible_bloodsuckers = list()
var/list/bloodsuckers = list()
- var/const/bloodsucker_amount = 2
+ var/const/bloodsucker_amount = 3
/datum/game_mode/traitor/bloodsucker/can_start()
. = ..()
diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm
index 124129bcb5ac..fc7417dd8e6a 100644
--- a/code/game/gamemodes/brother/traitor_bro.dm
+++ b/code/game/gamemodes/brother/traitor_bro.dm
@@ -61,13 +61,3 @@
/datum/game_mode/traitor/bros/generate_report()
return "It's Syndicate recruiting season. Be alert for potential Syndicate infiltrators, but also watch out for disgruntled employees trying to defect. Unlike Nanotrasen, the Syndicate prides itself in teamwork and will only recruit pairs that share a brotherly trust."
-
-/datum/game_mode/proc/update_brother_icons_added(datum/mind/brother_mind)
- var/datum/atom_hud/antag/brotherhud = GLOB.huds[ANTAG_HUD_BROTHER]
- brotherhud.join_hud(brother_mind.current)
- set_antag_hud(brother_mind.current, "brother")
-
-/datum/game_mode/proc/update_brother_icons_removed(datum/mind/brother_mind)
- var/datum/atom_hud/antag/brotherhud = GLOB.huds[ANTAG_HUD_BROTHER]
- brotherhud.leave_hud(brother_mind.current)
- set_antag_hud(brother_mind.current, null)
diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm
index 325af7376ea7..5bb0f89f4219 100644
--- a/code/game/gamemodes/changeling/changeling.dm
+++ b/code/game/gamemodes/changeling/changeling.dm
@@ -1,6 +1,6 @@
GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega"))
GLOBAL_LIST_INIT(slots, list("head", "wear_mask", "back", "wear_suit", "w_uniform", "shoes", "belt", "gloves", "glasses", "ears", "wear_id", "s_store"))
-GLOBAL_LIST_INIT(slot2slot, list("head" = SLOT_HEAD, "wear_mask" = SLOT_WEAR_MASK, "neck" = SLOT_NECK, "back" = SLOT_BACK, "wear_suit" = SLOT_WEAR_SUIT, "w_uniform" = SLOT_W_UNIFORM, "shoes" = SLOT_SHOES, "belt" = SLOT_BELT, "gloves" = SLOT_GLOVES, "glasses" = SLOT_GLASSES, "ears" = SLOT_EARS, "wear_id" = SLOT_WEAR_ID, "s_store" = SLOT_S_STORE))
+GLOBAL_LIST_INIT(slot2slot, list("head" = SLOT_HEAD, "wear_mask" = SLOT_WEAR_MASK, "neck" = SLOT_NECK, "back" = SLOT_BACK, "wear_suit" = SLOT_WEAR_SUIT, "w_uniform" = SLOT_W_UNIFORM, "shoes" = SLOT_SHOES, "belt" = SLOT_BELT, "gloves" = SLOT_GLOVES, "glasses" = SLOT_GLASSES, "ears" = SLOT_EARS, "wear_id" = SLOT_WEAR_ID, "s_store" = SLOT_SUIT_STORE))
GLOBAL_LIST_INIT(slot2type, list("head" = /obj/item/clothing/head/changeling, "wear_mask" = /obj/item/clothing/mask/changeling, "back" = /obj/item/changeling, "wear_suit" = /obj/item/clothing/suit/changeling, "w_uniform" = /obj/item/clothing/under/changeling, "shoes" = /obj/item/clothing/shoes/changeling, "belt" = /obj/item/changeling, "gloves" = /obj/item/clothing/gloves/changeling, "glasses" = /obj/item/clothing/glasses/changeling, "ears" = /obj/item/changeling, "wear_id" = /obj/item/changeling, "s_store" = /obj/item/changeling))
GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our this objective to all lings
diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm
index acd79fc0bd7d..9f6690940f99 100644
--- a/code/game/gamemodes/clock_cult/clock_cult.dm
+++ b/code/game/gamemodes/clock_cult/clock_cult.dm
@@ -55,7 +55,8 @@ Credit where due:
if(M.mind)
if(ishuman(M) && (M.mind.assigned_role in list("Captain", "Chaplain")))
return FALSE
- if(M.mind.enslaved_to && !is_servant_of_ratvar(M.mind.enslaved_to))
+ var/mob/living/master = M.mind.enslaved_to?.resolve()
+ if(master && !iscultist(master))
return FALSE
if(M.mind.unconvertable)
return FALSE
@@ -260,18 +261,6 @@ Credit where due:
working for this entity and utilizing highly-advanced technology to cross the great distance at will. If they should turn out to be a credible threat, the task falls on you and \
your crew to dispatch it in a timely manner."
-/datum/game_mode/proc/update_servant_icons_added(datum/mind/M)
- var/datum/atom_hud/antag/A = GLOB.huds[ANTAG_HUD_CLOCKWORK]
- A.join_hud(M.current)
- set_antag_hud(M.current, "clockwork")
-
-/datum/game_mode/proc/update_servant_icons_removed(datum/mind/M)
- var/datum/atom_hud/antag/A = GLOB.huds[ANTAG_HUD_CLOCKWORK]
- A.leave_hud(M.current)
- set_antag_hud(M.current, null)
-
-
-
//Servant of Ratvar outfit
/datum/outfit/servant_of_ratvar
name = "Servant of Ratvar"
diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm
index a0b3e4f553ae..9dff69a19a67 100644
--- a/code/game/gamemodes/cult/cult.dm
+++ b/code/game/gamemodes/cult/cult.dm
@@ -26,7 +26,8 @@
return FALSE
if(specific_cult && specific_cult.is_sacrifice_target(M.mind))
return FALSE
- if(M.mind.enslaved_to && !iscultist(M.mind.enslaved_to))
+ var/mob/living/master = M.mind.enslaved_to?.resolve()
+ if(master && !iscultist(master))
return FALSE
if(M.mind.unconvertable)
return FALSE
@@ -137,16 +138,6 @@
cult_mind.current.Unconscious(100)
return TRUE
-/datum/game_mode/proc/update_cult_icons_added(datum/mind/cult_mind)
- var/datum/atom_hud/antag/culthud = GLOB.huds[ANTAG_HUD_CULT]
- culthud.join_hud(cult_mind.current)
- set_antag_hud(cult_mind.current, "cult")
-
-/datum/game_mode/proc/update_cult_icons_removed(datum/mind/cult_mind)
- var/datum/atom_hud/antag/culthud = GLOB.huds[ANTAG_HUD_CULT]
- culthud.leave_hud(cult_mind.current)
- set_antag_hud(cult_mind.current, null)
-
/datum/game_mode/cult/proc/check_cult_victory()
return main_cult.check_cult_victory()
@@ -244,7 +235,7 @@
cultist.health *= 0.75
else
cultist.Stun(20)
- cultist.confused += 15 //30 seconds of confusion
+ cultist.adjust_confusion(15 SECONDS)
to_chat(cultist, span_narsiesmall("Your mind is flooded with pain as the last bloodstone is destroyed!"))
/datum/game_mode/proc/cult_loss_anchor()
@@ -263,9 +254,9 @@
cultist.maxHealth *= 0.5
cultist.health *= 0.5
else
- cultist.Stun(40)
- cultist.confused += 30 //one minute of confusion
- to_chat(cultist, span_narsiesmall("You feel a bleakness as the destruction of the anchor cuts off your connection to Nar'sie!"))
+ cultist.Stun(4 SECONDS)
+ cultist.adjust_confusion(1 MINUTES)
+ to_chat(cultist, span_narsiesmall("You feel a bleakness as the destruction of the anchor cuts off your connection to Nar-Sie!"))
/datum/game_mode/proc/disable_bloodstone_cooldown()
bloodstone_cooldown = FALSE
diff --git a/code/game/gamemodes/devil/game_mode.dm b/code/game/gamemodes/devil/game_mode.dm
index 208a24f3e518..eca95a011310 100644
--- a/code/game/gamemodes/devil/game_mode.dm
+++ b/code/game/gamemodes/devil/game_mode.dm
@@ -14,23 +14,3 @@
validtypes -= type //prevent duplicate objectives, EXCEPT for buy_target.
else
objective.find_target()
-
-/datum/game_mode/proc/update_devil_icons_added(datum/mind/devil_mind)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_DEVIL]
- hud.join_hud(devil_mind.current)
- set_antag_hud(devil_mind.current, "devil")
-
-/datum/game_mode/proc/update_devil_icons_removed(datum/mind/devil_mind)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_DEVIL]
- hud.leave_hud(devil_mind.current)
- set_antag_hud(devil_mind.current, null)
-
-/datum/game_mode/proc/update_soulless_icons_added(datum/mind/soulless_mind)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_SOULLESS]
- hud.join_hud(soulless_mind.current)
- set_antag_hud(soulless_mind.current, "soulless")
-
-/datum/game_mode/proc/update_soulless_icons_removed(datum/mind/soulless_mind)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_SOULLESS]
- hud.leave_hud(soulless_mind.current)
- set_antag_hud(soulless_mind.current, null)
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index 720ffffb76ab..d5dd024038f8 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -751,7 +751,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
rule.candidates = list(newPlayer)
rule.trim_candidates()
- if(!rule.candidates || isemptylist(rule.candidates))
+ if(!rule.candidates || !length(rule.candidates))
continue
if (rule.ready())
drafted_rules[rule] = rule.get_weight()
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
index db55db17b687..ba24abca5971 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
@@ -268,7 +268,7 @@
assigned -= selected_player
message_admins("[ADMIN_LOOKUPFLW(selected_player)] was selected by the [name] ruleset, but couldn't be made into a Bloodsucker.")
return FALSE
- sucker.bloodsucker_level_unspent = rand(2,3)
+ sucker.bloodsucker_level_unspent = rand(3,4)
message_admins("[ADMIN_LOOKUPFLW(selected_player)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.")
log_game("DYNAMIC: [key_name(selected_player)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.")
return TRUE
diff --git a/code/game/gamemodes/dynamic/readme.md b/code/game/gamemodes/dynamic/readme.md
index f3a02856aba2..0b14eceead55 100644
--- a/code/game/gamemodes/dynamic/readme.md
+++ b/code/game/gamemodes/dynamic/readme.md
@@ -180,4 +180,4 @@ In `/datum/game_mode/dynamic/on_pre_random_event` (in `dynamic_hijacking.dm`), D

-`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance`.
\ No newline at end of file
+`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance`.
diff --git a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
index 50ed7b771136..bd857f8740e6 100644
--- a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
+++ b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
@@ -60,17 +60,6 @@
cultie.add_antag_datum(new_antag)
return ..()
-
-/datum/game_mode/proc/update_heretic_icons_added(datum/mind/heretic)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HERETIC]
- hud.join_hud(heretic.current)
- set_antag_hud(heretic.current, (IS_HERETIC(heretic.current) ? "heretic" : "heretic_beast"))
-
-/datum/game_mode/proc/update_heretic_icons_removed(datum/mind/heretic)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HERETIC]
- hud.leave_hud(heretic.current)
- set_antag_hud(heretic.current, null)
-
/datum/game_mode/heretics/generate_report()
return "Cybersun Industries has announced that they have successfully raided a high-security library. The library contained a very dangerous book that was \
shown to possess anomalous properties. We suspect that the book has been copied over, Stay vigilant!"
diff --git a/code/game/gamemodes/hivemind/hivemind.dm b/code/game/gamemodes/hivemind/hivemind.dm
index 28838d59675e..187a92a31c26 100644
--- a/code/game/gamemodes/hivemind/hivemind.dm
+++ b/code/game/gamemodes/hivemind/hivemind.dm
@@ -26,9 +26,9 @@
/mob/living/proc/is_real_hivehost() //This proc ignores mind controlled vessels
for(var/datum/antagonist/hivemind/hive in GLOB.antagonists)
- if(!hive.owner?.spell_list)
+ if(!hive.owner?.current?.actions)
continue
- var/obj/effect/proc_holder/spell/target_hive/hive_control/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_control) in hive.owner.spell_list
+ var/datum/action/cooldown/spell/aoe/target_hive/hive_control/the_spell = locate(/datum/action/cooldown/spell/aoe/target_hive/hive_control) in hive.owner.current.actions
if((!the_spell || !the_spell.active ) && mind == hive.owner)
return TRUE
if(the_spell?.active && the_spell.original_body == src)
@@ -41,7 +41,7 @@
return
if(!is_hivehost(M) || is_real_hivehost(M))
return M
- var/obj/effect/proc_holder/spell/target_hive/hive_control/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_control) in M.mind.spell_list
+ var/datum/action/cooldown/spell/aoe/target_hive/hive_control/the_spell = locate(/datum/action/cooldown/spell/aoe/target_hive/hive_control) in M.actions
if(the_spell?.active)
return the_spell.original_body
return M
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 920f7755d0da..703dd097b4ee 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -667,7 +667,7 @@ GLOBAL_LIST_EMPTY(possible_items)
if(M.current.mind.assigned_role in possible_item.excludefromjob)
continue check_items
approved_targets += possible_item
- return set_target(safepick(approved_targets))
+ return set_target(pick(approved_targets))
/datum/objective/steal/proc/set_target(datum/objective_item/item)
if(item)
@@ -716,7 +716,7 @@ GLOBAL_LIST_EMPTY(possible_items)
if(!isliving(M.current))
continue
- var/list/all_items = M.current.GetAllContents() //this should get things in cheesewheels, books, etc.
+ var/list/all_items = M.current.get_all_contents() //this should get things in cheesewheels, books, etc.
for(var/obj/I in all_items) //Check for items
if(istype(I, steal_target))
@@ -731,7 +731,7 @@ GLOBAL_LIST_EMPTY(possible_items)
if (istype(team, /datum/team/infiltrator))
for (var/area/A in world)
if (is_type_in_typecache(A, GLOB.infiltrator_objective_areas))
- for (var/obj/item/I in A.GetAllContents()) //Check for items
+ for (var/obj/item/I in A.get_all_contents()) //Check for items
if (istype(I, steal_target))
if (!targetinfo)
return TRUE
@@ -820,13 +820,13 @@ GLOBAL_LIST_EMPTY(possible_items_special)
if(H && (H.stat != DEAD) && istype(H.wear_suit, /obj/item/clothing/suit/space/space_ninja))
var/obj/item/clothing/suit/space/space_ninja/S = H.wear_suit
S.stored_research.copy_research_to(checking)
- var/list/otherwise = M.GetAllContents()
+ var/list/otherwise = M.get_all_contents()
for(var/obj/item/disk/tech_disk/TD in otherwise)
TD.stored_research.copy_research_to(checking)
if (istype(team, /datum/team/infiltrator))
for (var/area/A in world)
if (is_type_in_typecache(A, GLOB.infiltrator_objective_areas))
- for (var/obj/item/disk/tech_disk/TD in A.GetAllContents()) //Check for items
+ for (var/obj/item/disk/tech_disk/TD in A.get_all_contents()) //Check for items
TD.stored_research.copy_research_to(checking)
CHECK_TICK
CHECK_TICK
@@ -1045,10 +1045,98 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/datum/objective/destroy/internal
var/stolen = FALSE //Have we already eliminated this target?
-/datum/objective/steal_five_of_type
+/datum/objective/steal_n_of_type
name = "steal five of"
- explanation_text = "Steal at least five items!"
+ explanation_text = "Steal some items!"
+ //what types we want to steal
var/list/wanted_items = list()
+ //how many we want to steal
+ var/amount = 5
+
+/datum/objective/steal_n_of_type/New()
+ ..()
+ wanted_items = typecacheof(wanted_items)
+
+/datum/objective/steal_n_of_type/check_completion()
+ var/list/datum/mind/owners = get_owners()
+ var/stolen_count = 0
+ for(var/datum/mind/M in owners)
+ if(!isliving(M.current))
+ continue
+ var/list/all_items = M.current.get_all_contents() //this should get things in cheesewheels, books, etc.
+ for(var/obj/current_item in all_items) //Check for wanted items
+ if(is_type_in_typecache(current_item, wanted_items))
+ if(check_if_valid_item(current_item))
+ stolen_count++
+ return stolen_count >= amount
+
+/datum/objective/steal_n_of_type/proc/check_if_valid_item(obj/item/current_item)
+ return TRUE
+
+/datum/objective/steal_n_of_type/summon_guns
+ name = "steal guns"
+ explanation_text = "Steal at least five guns!"
+ wanted_items = list(/obj/item/gun)
+ amount = 5
+
+/datum/objective/steal_n_of_type/summon_guns/check_if_valid_item(obj/item/current_item)
+ var/obj/item/gun/gun = current_item
+// return !(gun.gun_flags & NOT_A_REAL_GUN)
+ return istype(gun)
+
+/datum/objective/steal_n_of_type/summon_magic
+ name = "steal magic"
+ explanation_text = "Steal at least five magical artefacts!"
+ wanted_items = list()
+ amount = 5
+
+/datum/objective/steal_n_of_type/summon_magic/New()
+ wanted_items = GLOB.summoned_magic_objectives
+ ..()
+
+/datum/objective/steal_n_of_type/summon_magic/check_completion()
+ var/list/datum/mind/owners = get_owners()
+ var/stolen_count = 0
+ for(var/datum/mind/M in owners)
+ if(!isliving(M.current))
+ continue
+ var/list/all_items = M.current.get_all_contents() //this should get things in cheesewheels, books, etc.
+ for(var/obj/thing in all_items) //Check for wanted items
+ if(istype(thing, /obj/item/book/granter/action/spell))
+ var/obj/item/book/granter/action/spell/spellbook = thing
+ if(spellbook.uses > 0) //if the book still has powers...
+ stolen_count++ //it counts. nice.
+ else if(is_type_in_typecache(thing, wanted_items))
+ stolen_count++
+ return stolen_count >= amount
+
+/datum/objective/steal_n_of_type/organs
+ name = "steal organs"
+ explanation_text = "Steal at least 5 organic organs! They must be kept healthy."
+ wanted_items = list(/obj/item/organ)
+ amount = 5 //i want this to be higher, but the organs must be fresh at roundend
+
+/datum/objective/steal_n_of_type/organs/check_completion()
+ var/list/datum/mind/owners = get_owners()
+ var/stolen_count = 0
+ for(var/datum/mind/mind in owners)
+ if(!isliving(mind.current))
+ continue
+ var/list/all_items = mind.current.get_all_contents() //this should get things in cheesewheels, books, etc.
+ for(var/obj/item/stolen in all_items) //Check for wanted items
+ var/found = FALSE
+ for(var/wanted_type in wanted_items)
+ if(istype(stolen, wanted_type))
+ found = TRUE
+ break
+ if(!found)
+ continue
+ //this is an objective item
+ var/obj/item/organ/wanted = stolen
+ if(!(wanted.organ_flags & ORGAN_FAILING) && !(wanted.organ_flags & ORGAN_SYNTHETIC))
+ stolen_count++
+ return stolen_count >= amount
+
//Created by admin tools
/datum/objective/custom
@@ -1379,7 +1467,7 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/datum/objective/minor/deadpics/check_completion()
if(..())
return TRUE
- var/list/all_items = owner.current.GetAllContents()
+ var/list/all_items = owner.current.get_all_contents()
for(var/obj/item/photo/P in all_items)
for(var/mob/M in P.picture.dead_seen)
if(M.real_name == target.name)
@@ -1431,7 +1519,7 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/datum/objective/minor/staffpics/check_completion()
if(..())
return TRUE
- var/list/all_items = owner.current.GetAllContents()
+ var/list/all_items = owner.current.get_all_contents()
for(var/obj/item/photo/P in all_items)
for(var/mob/M in P.picture.mobs_seen)
if(M.real_name == target.name)
@@ -1456,10 +1544,10 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/datum/objective/survive/bloodsucker,
/datum/objective/bloodsucker/protege,
/datum/objective/bloodsucker/heartthief,
+ /datum/objective/bloodsucker/vassalhim,
/datum/objective/bloodsucker/gourmand,
// MISC OBJECTIVES //
/datum/objective/bloodsucker/monsterhunter,
- /datum/objective/bloodsucker/vassalhim,
/datum/objective/bloodsucker/frenzy,
// Fulp edit END
/datum/objective/destroy,
diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm
index c047cc00b500..736b241564a2 100644
--- a/code/game/gamemodes/objective_items.dm
+++ b/code/game/gamemodes/objective_items.dm
@@ -129,7 +129,7 @@
if(!isliving(M.current))
continue
- var/list/all_items = M.current.GetAllContents()
+ var/list/all_items = M.current.get_all_contents()
for(var/o in all_items)
if(!istype(o, /obj/item/tank))
continue
@@ -138,7 +138,7 @@
if (istype(objective.team, /datum/team/infiltrator))
for (var/area/A in world)
if (is_type_in_typecache(A, GLOB.infiltrator_objective_areas))
- for (var/obj/item/tank/T in A.GetAllContents()) //Check for items
+ for (var/obj/item/tank/T in A.get_all_contents()) //Check for items
found_amount += T.air_contents.get_moles(/datum/gas/plasma)
CHECK_TICK
CHECK_TICK
diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm
index 7bfbcf385348..83193f5354f7 100644
--- a/code/game/gamemodes/wizard/wizard.dm
+++ b/code/game/gamemodes/wizard/wizard.dm
@@ -49,11 +49,15 @@
/datum/game_mode/wizard/are_special_antags_dead()
for(var/datum/mind/wizard in wizards)
- if(isliving(wizard.current) && wizard.current.stat!=DEAD)
+ if(isliving(wizard.current) && wizard.current.stat != DEAD)
return FALSE
- for(var/obj/item/phylactery/P in GLOB.poi_list) //TODO : IsProperlyDead()
- if(P.mind && P.mind.has_antag_datum(/datum/antagonist/wizard))
+
+ for(var/obj/item/phylactery in GLOB.poi_list) //TODO : IsProperlyDead()
+ if(!phylactery.GetComponent(/datum/component/phylactery))
+ continue
+ var/datum/component/phylactery/phylactery_component
+ if(phylactery_component?.lich_mind?.has_antag_datum(/datum/antagonist/wizard))
return FALSE
if(SSevents.wizardmode) //If summon events was active, turn it off
diff --git a/code/game/gamemodes/zombie/zombie.dm b/code/game/gamemodes/zombie/zombie.dm
index d66d4394617a..0e3ca3d87003 100644
--- a/code/game/gamemodes/zombie/zombie.dm
+++ b/code/game/gamemodes/zombie/zombie.dm
@@ -91,7 +91,7 @@ GLOBAL_LIST_EMPTY(zombies)
if(!istype(antag))
continue
actual_roundstart_zombies++
- antag.start_timer()
+// antag.start_timer()
addtimer(CALLBACK(src, PROC_REF(call_shuttle)), 60 MINUTES) //Shuttle called after 1 hour if it hasn't been
. = ..()
diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm
index 17b8c6e42ff4..bf43c4af6a18 100644
--- a/code/game/machinery/Sleeper.dm
+++ b/code/game/machinery/Sleeper.dm
@@ -107,7 +107,7 @@
if(mob_occupant && mob_occupant.stat != DEAD)
to_chat(occupant, "[enter_message]")
if(mob_occupant && stasis)
- mob_occupant.ExtinguishMob()
+ mob_occupant.extinguish_mob()
if(close_sound)
playsound(src, close_sound, 40)
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 0bdc6e2243ba..8f4f0045152f 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -510,7 +510,7 @@ Class Procs:
for(var/obj/item/B in W.contents)
if(istype(B, P) && istype(A, P))
//won't replace beakers if they have reagents in them to prevent funny explosions
- if(istype(B,/obj/item/reagent_containers) && !isemptylist(B.reagents?.reagent_list))
+ if(istype(B,/obj/item/reagent_containers) && length(B.reagents?.reagent_list))
continue
// If it's a corrupt or rigged cell, attempting to send it through Bluespace could have unforeseen consequences.
if(istype(B, /obj/item/stock_parts/cell) && W.works_from_distance)
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 98f93d519ac8..1f656f3c7bc3 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -476,7 +476,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
if(obj_flags & EMAGGED)
var/mob/living/M = user
M.adjust_fire_stacks(5)
- M.IgniteMob() //flew into a star, so you're on fire
+ M.ignite_mob() //flew into a star, so you're on fire
to_chat(user, span_userdanger("You feel an immense wave of heat emanate from the arcade machine. Your skin bursts into flames."))
if(obj_flags & EMAGGED)
@@ -549,7 +549,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
if(ORION_TRAIL_RAIDERS)
if(prob(50))
to_chat(usr, span_userdanger("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
+ M.adjust_hallucinations(30 SECONDS)
else
to_chat(usr, span_userdanger("Something strikes you from behind! It hurts like hell and feel like a blunt weapon, but nothing is there..."))
M.take_bodypart_damage(30)
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
index 2bb678fc8f96..85dd7f284c36 100644
--- a/code/game/machinery/computer/camera_advanced.dm
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -56,12 +56,10 @@
/obj/machinery/computer/camera_advanced/remove_eye_control(mob/living/user)
if(!user)
return
- for(var/V in actions)
- var/datum/action/A = V
+ for(var/datum/action/A as anything in actions)
A.Remove(user)
actions.Cut()
- for(var/V in eyeobj.visibleCameraChunks)
- var/datum/camerachunk/C = V
+ for(var/datum/camerachunk/C as anything in eyeobj.visibleCameraChunks)
C.remove(eyeobj)
if(user.client)
user.reset_perspective(null)
@@ -229,7 +227,7 @@
/datum/action/innate/camera_off
name = "End Camera View"
- icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon = 'icons/mob/actions/actions_silicon.dmi'
button_icon_state = "camera_off"
/datum/action/innate/camera_off/Activate()
@@ -242,7 +240,7 @@
/datum/action/innate/camera_jump
name = "Jump To Camera"
- icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon = 'icons/mob/actions/actions_silicon.dmi'
button_icon_state = "camera_jump"
/datum/action/innate/camera_jump/Activate()
@@ -287,10 +285,11 @@
desc = "A console used to snoop on the station's goings-on. A jet of steam occasionally whooshes out from slats on its sides."
use_power = FALSE
networks = list("ss13", "minisat") //:eye:
- var/datum/action/innate/servant_warp/warp_action = new
+ var/datum/action/innate/servant_warp/warp_action
/obj/machinery/computer/camera_advanced/ratvar/Initialize()
. = ..()
+ warp_action = new(src)
ratvar_act()
/obj/machinery/computer/camera_advanced/ratvar/process()
@@ -305,11 +304,10 @@
eyeobj.icon_state = "generic_camera"
/obj/machinery/computer/camera_advanced/ratvar/GrantActions(mob/living/carbon/user)
- ..()
- if(warp_action)
- warp_action.Grant(user)
- warp_action.target = src
- actions += warp_action
+ . = ..()
+ warp_action = new(src)
+ warp_action.Grant(user)
+ actions += warp_action
/obj/machinery/computer/camera_advanced/ratvar/can_use(mob/living/user)
if(!is_servant_of_ratvar(user))
@@ -323,7 +321,7 @@
/datum/action/innate/servant_warp
name = "Warp"
desc = "Warps to the tile you're viewing. You can use the Abscond scripture to return. Clicking this button again cancels the warp."
- icon_icon = 'icons/mob/actions/actions_clockcult.dmi'
+ button_icon = 'icons/mob/actions/actions_clockcult.dmi'
button_icon_state = "warp_down"
background_icon_state = "bg_clock"
buttontooltipstyle = "clockcult"
@@ -340,43 +338,44 @@
return
var/mob/living/carbon/human/user = owner
var/mob/camera/aiEye/remote/remote_eye = user.remote_control
- var/obj/machinery/computer/camera_advanced/ratvar/R = target
- var/turf/T = get_turf(remote_eye)
- if(!is_reebe(user.z) || !is_station_level(T.z))
+ var/obj/machinery/computer/camera_advanced/ratvar/camera_console = target
+ var/turf/teleport_turf = get_turf(remote_eye)
+ if(!is_reebe(user.z) || !is_station_level(teleport_turf.z))
return
- if(isclosedturf(T))
+ if(isclosedturf(teleport_turf))
to_chat(user, "[span_sevtug_small("You can't teleport into a wall.")]")
return
- else if(isspaceturf(T))
+ else if(isspaceturf(teleport_turf))
to_chat(user, "[span_sevtug_small("[prob(1) ? "Servant cannot into space." : "You can't teleport into space."]")]")
return
- else if(T.flags_1 & NOJAUNT_1)
+ else if(teleport_turf.flags_1 & NOJAUNT_1)
to_chat(user, "[span_sevtug_small("This tile is blessed by strange energies and deflects the warp.")]")
return
- else if(locate(/obj/effect/blessing, T))
+ else if(locate(/obj/effect/blessing, teleport_turf))
to_chat(user, "[span_sevtug_small("This tile is blessed by holy water and deflects the warp.")]")
return
- var/area/AR = get_area(T)
- if(!AR.clockwork_warp_allowed)
- to_chat(user, "[span_sevtug_small("[AR.clockwork_warp_fail]")]")
+ var/area/teleport_area = get_area(teleport_turf)
+ if(!teleport_area.clockwork_warp_allowed)
+ to_chat(user, "[span_sevtug_small("[teleport_area.clockwork_warp_fail]")]")
return
- if (user.w_uniform && user.w_uniform.name == initial(user.w_uniform.name))
- if (tgui_alert(user, "ARE YOU SURE YOU WANT TO WARP WITHOUT CAMOUFLAGING YOUR JUMPSUIT?", "Preflight Check", list("Yes", "No")) == "No" )
+ if(user.w_uniform && user.w_uniform.name == initial(user.w_uniform.name))
+ if(tgui_alert(user, "ARE YOU SURE YOU WANT TO WARP WITHOUT CAMOUFLAGING YOUR JUMPSUIT?", "Preflight Check", list("Yes", "No")) == "No" )
return
- if(tgui_alert(user, "Are you sure you want to warp to [AR]?", target.name, list("Warp", "Cancel")) == "Cancel" || QDELETED(R) || !user.canUseTopic(R))
+ if(tgui_alert(user, "Are you sure you want to warp to [teleport_area]?", camera_console.name, list("Warp", "Cancel")) == \
+ "Cancel" || QDELETED(camera_console) || !user.canUseTopic(camera_console))
return
do_sparks(5, TRUE, user)
- do_sparks(5, TRUE, T)
- user.visible_message(span_warning("[user]'s [target.name] flares!"), "You begin warping to [AR]...")
+ do_sparks(5, TRUE, teleport_turf)
+ user.visible_message(span_warning("[user]'s [camera_console.name] flares!"), "You begin warping to [teleport_area]...")
button_icon_state = "warp_cancel"
owner.update_action_buttons()
- var/warp_time = 50
- if(!istype(T, /turf/open/floor/clockwork) && GLOB.clockwork_hardmode_active)
- to_chat(user, "[span_sevtug_small("The [target.name]'s inner machinery protests vehemently as it attempts to warp you to a non-brass tile, this will take time...")]")
- warp_time = 300
- warping = new(T, user, warp_time)
+ var/warp_time = 5 SECONDS
+ if(!istype(teleport_turf, /turf/open/floor/clockwork) && GLOB.clockwork_hardmode_active)
+ to_chat(user, "[span_sevtug_small("The [camera_console.name]'s inner machinery protests vehemently as it attempts to warp you to a non-brass tile, this will take time...")]")
+ warp_time = 30 SECONDS
+ warping = new(teleport_turf, user, warp_time)
if(!do_after(user, warp_time, warping, extra_checks = CALLBACK(src, PROC_REF(is_canceled))))
to_chat(user, "Warp interrupted.")
QDEL_NULL(warping)
@@ -386,13 +385,13 @@
return
button_icon_state = "warp_down"
owner.update_action_buttons()
- T.visible_message(span_warning("[user] warps in!"))
+ teleport_turf.visible_message(span_warning("[user] warps in!"))
playsound(user, 'sound/magic/magic_missile.ogg', 50, TRUE)
- playsound(T, 'sound/magic/magic_missile.ogg', 50, TRUE)
- user.forceMove(get_turf(T))
+ playsound(teleport_turf, 'sound/magic/magic_missile.ogg', 50, TRUE)
+ user.forceMove(get_turf(teleport_turf))
user.setDir(SOUTH)
flash_color(user, flash_color = "#AF0AAF", flash_time = 5)
- R.remove_eye_control(user)
+ camera_console.remove_eye_control(user)
QDEL_NULL(warping)
/datum/action/innate/servant_warp/proc/is_canceled()
diff --git a/code/game/machinery/computer/launchpad_control.dm b/code/game/machinery/computer/launchpad_control.dm
index 4d726ddee68d..aba85f627461 100644
--- a/code/game/machinery/computer/launchpad_control.dm
+++ b/code/game/machinery/computer/launchpad_control.dm
@@ -42,7 +42,7 @@
if(QDELETED(pad))
to_chat(user, span_warning("ERROR: Launchpad not responding. Check launchpad integrity."))
return
- if(!pad.isAvailable())
+ if(!pad.IsAvailable(feedback = FALSE))
to_chat(user, span_warning("ERROR: Launchpad not operative. Make sure the launchpad is ready and powered."))
return
pad.doteleport(user, sending)
diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm
index 6b2407364152..cccd7f7dfcbd 100644
--- a/code/game/machinery/computer/security.dm
+++ b/code/game/machinery/computer/security.dm
@@ -749,7 +749,7 @@
if("criminal_status")
if(active_security_record)
- var/crime = input("Select a status", "Criminal Status Selection") as null|anything in list("None", "Arrest", "Search", "Incarcerated", "Suspected", "Paroled", "Discharged")
+ var/crime = tgui_input_list(usr, "Select a status", "Criminal Status Selection", list("None", "Arrest", "Search", "Incarcerated", "Suspected", "Paroled", "Discharged"))
if(!crime)
crime = "none"
var/old_field = active_security_record.fields["criminal"]
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index 1fba07b3f352..521b080023f4 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -335,7 +335,7 @@ GLOBAL_VAR_INIT(cryopods_enabled, FALSE)
announcer.announce("CRYOSTORAGE", mob_occupant.real_name, announce_rank, list())
visible_message(span_notice("\The [src] hums and hisses as it moves [mob_occupant.real_name] into storage."))
- for(var/obj/item/W in mob_occupant.GetAllContents())
+ for(var/obj/item/W in mob_occupant.get_all_contents())
if(QDELETED(W))
continue
if(W.loc.loc && (( W.loc.loc == loc ) || (W.loc.loc == control_computer)))
diff --git a/code/game/machinery/decontamination.dm b/code/game/machinery/decontamination.dm
index ed4431e7c808..a10c9c2aacd5 100644
--- a/code/game/machinery/decontamination.dm
+++ b/code/game/machinery/decontamination.dm
@@ -105,7 +105,7 @@
mob_occupant.adjustFireLoss(rand(15, 26))
mob_occupant.radiation += 500
mob_occupant.adjust_fire_stacks(2)
- mob_occupant.IgniteMob()
+ mob_occupant.ignite_mob()
if(iscarbon(mob_occupant) && mob_occupant.stat < UNCONSCIOUS)
//Awake, organic and screaming
mob_occupant.emote("scream")
@@ -152,10 +152,10 @@
mob_occupant.radiation = 0
else
visible_message(span_notice("[src]'s gate slides open. The glowing yellow lights dim to a gentle green."))
- var/list/things_to_clear = list() //Done this way since using GetAllContents on the SSU itself would include circuitry and such.
+ var/list/things_to_clear = list() //Done this way since using get_all_contents on the SSU itself would include circuitry and such.
if(occupant)
things_to_clear += occupant
- things_to_clear += occupant.GetAllContents()
+ things_to_clear += occupant.get_all_contents()
dump_mob()
if(contents.len)
things_to_clear += contents
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 50ec5e6f904b..02d0fefa3ecd 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -164,7 +164,7 @@
damage_deflection = AIRLOCK_DAMAGE_DEFLECTION_R
prepare_huds()
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
- diag_hud.add_to_hud(src)
+ diag_hud.add_atom_to_hud(src)
diag_hud_set_electrified()
rebuild_parts()
@@ -440,7 +440,7 @@
D.removeMe(src)
qdel(note)
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
- diag_hud.remove_from_hud(src)
+ diag_hud.remove_atom_from_hud(src)
if(brace) //yogs
brace.remove() //yogs
return ..()
@@ -1821,7 +1821,7 @@
for(var/mob/living/carbon/human/H in orange(2,src))
H.Unconscious(160)
H.adjust_fire_stacks(20)
- H.IgniteMob() //Guaranteed knockout and ignition for nearby people
+ H.ignite_mob() //Guaranteed knockout and ignition for nearby people
H.apply_damage(40, BRUTE, BODY_ZONE_CHEST)
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 89a2edc3104c..4313faf58c19 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -89,6 +89,11 @@
real_explosion_block = explosion_block
explosion_block = EXPLOSION_BLOCK_PROC
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_MAGICALLY_UNLOCKED = PROC_REF(on_magic_unlock),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/machinery/door/proc/set_init_door_layer()
if(density)
layer = closingLayer
@@ -442,5 +447,11 @@
//if it blows up a wall it should blow up a door
..(severity ? max(1, severity - 1) : 0, target)
+/// Signal proc for [COMSIG_ATOM_MAGICALLY_UNLOCKED]. Open up when someone casts knock.
+/obj/machinery/door/proc/on_magic_unlock(datum/source, datum/action/cooldown/spell/aoe/knock/spell, mob/living/caster)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(open))
+
/obj/machinery/door/GetExplosionBlock()
return density ? real_explosion_block : 0
diff --git a/code/game/machinery/gulag_processor.dm b/code/game/machinery/gulag_processor.dm
index f83ad6a070f1..b900e9bbce3e 100644
--- a/code/game/machinery/gulag_processor.dm
+++ b/code/game/machinery/gulag_processor.dm
@@ -183,7 +183,7 @@ GLOBAL_VAR_INIT(gulag_required_items, typecacheof(list(
open_machine()
prisoner.Paralyze(stun_duration)
if(!prisoner.handcuffed && (prisoner.get_num_arms(FALSE) >= 2 || prisoner.get_arm_ignore()))
- prisoner.handcuffed = new /obj/item/restraints/handcuffs/cable/zipties/used(prisoner)
+ prisoner.set_handcuffed(new /obj/item/restraints/handcuffs/cable/zipties/used(prisoner))
prisoner.update_handcuffed()
visible_message(span_warning("Prisoner Processed."))
diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm
index b6278194a329..0902a34b5419 100644
--- a/code/game/machinery/launch_pad.dm
+++ b/code/game/machinery/launch_pad.dm
@@ -30,20 +30,20 @@
. = ..()
prepare_huds()
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
- diag_hud.add_to_hud(src)
+ diag_hud.add_atom_to_hud(src)
+ update_hud()
+
+/obj/machinery/launchpad/proc/update_hud()
var/image/holder = hud_list[DIAG_LAUNCHPAD_HUD]
- var/mutable_appearance/MA = new /mutable_appearance()
- MA.icon = 'icons/effects/effects.dmi'
- MA.icon_state = "launchpad_target"
- MA.layer = ABOVE_OPEN_TURF_LAYER
- MA.plane = 0
- holder.appearance = MA
+ var/mutable_appearance/target = mutable_appearance('icons/effects/effects.dmi', "launchpad_target", ABOVE_OPEN_TURF_LAYER, src, GAME_PLANE)
+ holder.appearance = target
update_indicator()
/obj/machinery/launchpad/Destroy()
- qdel(hud_list[DIAG_LAUNCHPAD_HUD])
+ for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
+ diag_hud.remove_atom_from_hud(src)
return ..()
/obj/machinery/launchpad/examine(mob/user)
@@ -71,7 +71,7 @@
return ..()
-/obj/machinery/launchpad/proc/isAvailable()
+/obj/machinery/launchpad/proc/IsAvailable(feedback = FALSE)
if(stat & NOPOWER)
return FALSE
if(panel_open)
@@ -81,7 +81,7 @@
/obj/machinery/launchpad/proc/update_indicator()
var/image/holder = hud_list[DIAG_LAUNCHPAD_HUD]
var/turf/target_turf
- if(isAvailable())
+ if(IsAvailable(feedback = FALSE))
target_turf = locate(x + x_offset, y + y_offset, z)
if(target_turf)
holder.icon_state = indicator_icon
@@ -133,7 +133,7 @@
indicator_icon = "launchpad_target"
update_indicator()
- if(QDELETED(src) || !isAvailable())
+ if(QDELETED(src) || !IsAvailable(feedback = FALSE))
return
teleporting = FALSE
@@ -232,7 +232,7 @@
QDEL_NULL(briefcase)
return ..()
-/obj/machinery/launchpad/briefcase/isAvailable()
+/obj/machinery/launchpad/briefcase/IsAvailable(feedback = FALSE)
if(closed)
return FALSE
return ..()
@@ -352,7 +352,7 @@
if(QDELETED(pad))
to_chat(user, span_warning("ERROR: Launchpad not responding. Check launchpad integrity."))
return
- if(!pad.isAvailable())
+ if(!pad.IsAvailable(feedback = FALSE))
to_chat(user, span_warning("ERROR: Launchpad not operative. Make sure the launchpad is ready and powered."))
return
pad.doteleport(user, sending)
diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm
index bffbde8bcf68..1e49c5f35e0f 100644
--- a/code/game/machinery/newscaster.dm
+++ b/code/game/machinery/newscaster.dm
@@ -203,6 +203,8 @@ GLOBAL_LIST_EMPTY(allCasters)
var/datum/newscaster/feed_channel/viewing_channel = null
var/allow_comments = 1
+/obj/machinery/newscaster/pai
+
/obj/machinery/newscaster/security_unit
name = "security newscaster"
securityCaster = 1
@@ -279,7 +281,7 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+="
The newscaster recognises you as: [scanned_user]"
if(1)
dat+= "Station Feed Channels
"
- if( isemptylist(GLOB.news_network.network_channels) )
+ if( !length(GLOB.news_network.network_channels) )
dat+="No active channels found..."
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -359,7 +361,7 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
"
dat+="No further feed story additions are allowed while the D-Notice is in effect.
"
else
- if( isemptylist(viewing_channel.messages) )
+ if( !length(viewing_channel.messages) )
dat+="No feed messages found in channel...
"
else
var/i = 0
@@ -387,7 +389,7 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
"
dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it."
dat+="
Select Feed channel to get Stories from:
"
- if(isemptylist(GLOB.news_network.network_channels))
+ if(!length(GLOB.news_network.network_channels))
dat+="No feed channels found active...
"
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -398,7 +400,7 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's"
dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed"
dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
"
- if(isemptylist(GLOB.news_network.network_channels))
+ if(!length(GLOB.news_network.network_channels))
dat+="No feed channels found active...
"
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -407,7 +409,7 @@ GLOBAL_LIST_EMPTY(allCasters)
if(12)
dat+="[viewing_channel.channel_name]: \[ created by: [viewing_channel.returnAuthor(-1)] \]
"
dat+="[(viewing_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]
"
- if(isemptylist(viewing_channel.messages))
+ if(!length(viewing_channel.messages))
dat+="No feed messages found in channel...
"
else
for(var/datum/newscaster/feed_message/MESSAGE in viewing_channel.messages)
@@ -424,7 +426,7 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
"
dat+="No further feed story additions are allowed while the D-Notice is in effect.
"
else
- if(isemptylist(viewing_channel.messages))
+ if(!length(viewing_channel.messages))
dat+="No feed messages found in channel...
"
else
for(var/datum/newscaster/feed_message/MESSAGE in viewing_channel.messages)
@@ -881,7 +883,7 @@ GLOBAL_LIST_EMPTY(allCasters)
if(0) //Cover
dat+="The Griffon
"
dat+="Nanotrasen-standard newspaper, for use on Nanotrasen? Space Facilities
"
- if(isemptylist(news_content))
+ if(!length(news_content))
if(wantedAuthor)
dat+="Contents:
**Important Security Announcement** \[page [pages+2]\]
"
else
@@ -908,7 +910,7 @@ GLOBAL_LIST_EMPTY(allCasters)
if(notContent(C.DclassCensorTime))
dat+="This channel was deemed dangerous to the general welfare of the station and therefore marked with a D-Notice. Its contents were not transferred to the newspaper at the time of printing."
else
- if(isemptylist(C.messages))
+ if(!length(C.messages))
dat+="No Feed stories stem from this channel..."
else
var/i = 0
diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm
index 7c4b6ed2c300..e75aa43e07d0 100644
--- a/code/game/machinery/porta_turret/portable_turret.dm
+++ b/code/game/machinery/porta_turret/portable_turret.dm
@@ -558,6 +558,7 @@
A.preparePixelProjectile(target, T)
A.firer = src
A.fired_from = src
+ A.ignored_factions = faction
A.fire()
return A
@@ -573,7 +574,7 @@
/datum/action/turret_toggle
name = "Toggle Mode"
- icon_icon = 'icons/mob/actions/actions_mecha.dmi'
+ button_icon = 'icons/mob/actions/actions_mecha.dmi'
button_icon_state = "mech_cycle_equip_off"
/datum/action/turret_toggle/Trigger()
@@ -584,7 +585,7 @@
/datum/action/turret_quit
name = "Release Control"
- icon_icon = 'icons/mob/actions/actions_mecha.dmi'
+ button_icon = 'icons/mob/actions/actions_mecha.dmi'
button_icon_state = "mech_eject"
/datum/action/turret_quit/Trigger()
diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm
index 38d85f51d4bc..09a1f04f688e 100644
--- a/code/game/machinery/recycler.dm
+++ b/code/game/machinery/recycler.dm
@@ -98,7 +98,7 @@
if(!isturf(AM0.loc))
return //I don't know how you called Crossed() but stop it.
- var/list/to_eat = AM0.GetAllContents()
+ var/list/to_eat = AM0.get_all_contents()
var/living_detected = FALSE //technically includes silicons as well but eh
var/list/nom = list()
diff --git a/code/game/machinery/stasis.dm b/code/game/machinery/stasis.dm
index d2ff306d93c2..71959216cf96 100644
--- a/code/game/machinery/stasis.dm
+++ b/code/game/machinery/stasis.dm
@@ -147,7 +147,7 @@
var/freq = rand(24750, 26550)
playsound(src, 'sound/effects/spray.ogg', 5, TRUE, 2, frequency = freq)
target.apply_status_effect(STATUS_EFFECT_STASIS, null, TRUE, stasis_amount)
- target.ExtinguishMob()
+ target.extinguish_mob()
use_power = ACTIVE_POWER_USE
if(obj_flags & EMAGGED)
to_chat(target, span_warning("Your limbs start to feel numb..."))
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index 4efd2ab6d7cb..5754b48ec1f5 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -254,22 +254,22 @@
else
visible_message(span_warning("[src]'s door slides open, barraging you with the nauseating smell of charred flesh."))
playsound(src, 'sound/machines/airlockclose.ogg', 25, 1)
- var/list/things_to_clear = list() //Done this way since using GetAllContents on the SSU itself would include circuitry and such.
+ var/list/things_to_clear = list() //Done this way since using get_all_contents on the SSU itself would include circuitry and such.
if(suit)
things_to_clear += suit
- things_to_clear += suit.GetAllContents()
+ things_to_clear += suit.get_all_contents()
if(helmet)
things_to_clear += helmet
- things_to_clear += helmet.GetAllContents()
+ things_to_clear += helmet.get_all_contents()
if(mask)
things_to_clear += mask
- things_to_clear += mask.GetAllContents()
+ things_to_clear += mask.get_all_contents()
if(storage)
things_to_clear += storage
- things_to_clear += storage.GetAllContents()
+ things_to_clear += storage.get_all_contents()
if(occupant)
things_to_clear += occupant
- things_to_clear += occupant.GetAllContents()
+ things_to_clear += occupant.get_all_contents()
for(var/am in things_to_clear) //Scorches away blood and forensic evidence, although the SSU itself is unaffected
var/atom/movable/dirty_movable = am
dirty_movable.wash(CLEAN_ALL)
diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm
index 69c1b71b1d8b..9e984e38a8ae 100644
--- a/code/game/machinery/telecomms/computers/message.dm
+++ b/code/game/machinery/telecomms/computers/message.dm
@@ -395,7 +395,7 @@
//Get out list of viable PDAs
var/list/obj/item/pda/sendPDAs = get_viewable_pdas()
if(GLOB.PDAs && GLOB.PDAs.len > 0)
- customrecepient = input(usr, "Select a PDA from the list.") as null|anything in sortNames(sendPDAs)
+ customrecepient = input(usr, "Select a PDA from the list.") as null|anything in sortUsernames(sendPDAs)
else
customrecepient = null
diff --git a/code/game/mecha/equipment/tools/medical_tools.dm b/code/game/mecha/equipment/tools/medical_tools.dm
index d304396ff9a8..0ae7242751ac 100644
--- a/code/game/mecha/equipment/tools/medical_tools.dm
+++ b/code/game/mecha/equipment/tools/medical_tools.dm
@@ -323,7 +323,7 @@
var/list/mobs = new
for(var/mob/living/carbon/M in mechsyringe.loc)
mobs += M
- var/mob/living/carbon/M = safepick(mobs)
+ var/mob/living/carbon/M = pick(mobs)
if(M)
var/R
mechsyringe.visible_message(span_attack(" [M] was hit by the syringe!"))
diff --git a/code/game/mecha/equipment/weapons/melee_weapons.dm b/code/game/mecha/equipment/weapons/melee_weapons.dm
index f1b8f63192a8..7d302e50fac6 100644
--- a/code/game/mecha/equipment/weapons/melee_weapons.dm
+++ b/code/game/mecha/equipment/weapons/melee_weapons.dm
@@ -319,15 +319,15 @@
to_chat(H, span_warning("You muscles seize, making you collapse!"))
else
H.Paralyze(stunforce)
- H.Jitter(20)
- H.confused = max(8, H.confused)
+ H.adjust_jitter(20 SECONDS)
+ H.adjust_confusion(8 SECONDS)
H.apply_effect(EFFECT_STUTTER, stunforce)
else if(current_stamina_damage > 70)
- H.Jitter(10)
- H.confused = max(8, H.confused)
+ H.adjust_jitter(10 SECONDS)
+ H.adjust_confusion(8 SECONDS)
H.apply_effect(EFFECT_STUTTER, stunforce)
else if(current_stamina_damage >= 20)
- H.Jitter(5)
+ H.adjust_jitter(5 SECONDS)
H.apply_effect(EFFECT_STUTTER, stunforce)
if(isliving(target))
@@ -359,7 +359,7 @@
else
L.apply_damage(burn_damage, BURN)
L.adjust_fire_stacks(2)
- L.IgniteMob()
+ L.ignite_mob()
playsound(L, 'sound/items/welder.ogg', 50, 1)
/obj/item/mecha_parts/mecha_equipment/melee_weapon/sword/maul
diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm
index f1ce750b5650..833c0b2ca4e1 100644
--- a/code/game/mecha/equipment/weapons/weapons.dm
+++ b/code/game/mecha/equipment/weapons/weapons.dm
@@ -232,14 +232,14 @@
continue
to_chat(M, "HONK")
M.SetSleeping(0)
- M.stuttering += 20
+ M.adjust_stutter(2 SECONDS)
M.adjustEarDamage(0, 30)
- M.Knockdown(60)
+ M.Knockdown(6 SECONDS)
if(prob(30))
- M.Stun(100)
- M.Unconscious(80)
+ M.Stun(10 SECONDS)
+ M.Unconscious(8 SECONDS)
else
- M.Jitter(500)
+ M.adjust_jitter(50 SECONDS)
log_message("Honked from [src.name]. HONK!", LOG_MECHA)
var/turf/T = get_turf(src)
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index a8fc6b5719cb..eac814f4c1d7 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -164,7 +164,7 @@
GLOB.mechas_list += src //global mech list
prepare_huds()
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
- diag_hud.add_to_hud(src)
+ diag_hud.add_atom_to_hud(src)
diag_hud_set_mechhealth()
diag_hud_set_mechcell()
diag_hud_set_mechstat()
@@ -514,7 +514,7 @@
if (occupant && !enclosed && !silicon_pilot)
if (occupant.fire_stacks < 5)
occupant.adjust_fire_stacks(1)
- occupant.IgniteMob()
+ occupant.ignite_mob()
/obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits.
return
@@ -561,7 +561,7 @@
if(!omnidirectional_attacks && dir_to_target && !(dir_to_target & dir))//wrong direction
return
if(internal_damage & MECHA_INT_CONTROL_LOST)
- target = safepick(view(3,target))
+ target = pick(view(3,target))
if(!target)
return
@@ -597,7 +597,7 @@
selected.start_cooldown()
else
if(internal_damage & MECHA_INT_CONTROL_LOST)
- target = safepick(oview(1,src))
+ target = pick(oview(1,src))
if(!melee_can_hit || !istype(target, /atom))
return
if(equipment_disabled)
@@ -776,19 +776,19 @@
///////////////////////////////////
/obj/mecha/proc/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null)
- if(!islist(possible_int_damage) || isemptylist(possible_int_damage))
+ if(!islist(possible_int_damage) || !length(possible_int_damage))
return
if(prob(20))
if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold)
for(var/T in possible_int_damage)
if(internal_damage & T)
possible_int_damage -= T
- var/int_dam_flag = safepick(possible_int_damage)
+ var/int_dam_flag = pick(possible_int_damage)
if(int_dam_flag)
setInternalDamage(int_dam_flag)
if(prob(5))
if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold)
- var/obj/item/mecha_parts/mecha_equipment/ME = safepick(equipment)
+ var/obj/item/mecha_parts/mecha_equipment/ME = pick(equipment)
if(ME)
qdel(ME)
return
diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm
index 19e51f64d17b..bebbc829af15 100644
--- a/code/game/mecha/mecha_actions.dm
+++ b/code/game/mecha/mecha_actions.dm
@@ -23,8 +23,8 @@
strafing_action.Remove(user)
/datum/action/innate/mecha
- check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS
- icon_icon = 'icons/mob/actions/actions_mecha.dmi'
+ check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS
+ button_icon = 'icons/mob/actions/actions_mecha.dmi'
var/obj/mecha/chassis
/datum/action/innate/mecha/Grant(mob/living/L, obj/mecha/M)
@@ -58,7 +58,7 @@
button_icon_state = "mech_internals_[chassis.use_internal_tank ? "on" : "off"]"
chassis.occupant_message("Now taking air from [chassis.use_internal_tank?"internal airtank":"environment"].")
chassis.log_message("Now taking air from [chassis.use_internal_tank?"internal airtank":"environment"].", LOG_MECHA)
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/mecha/mech_cycle_equip
name = "Cycle Equipment. Can also be cycled with throwmode."
@@ -82,7 +82,7 @@
chassis.occupant_message("You select [chassis.selected]")
send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list())
button_icon_state = "mech_cycle_equip_on"
- UpdateButtonIcon()
+ build_all_button_icons()
return
var/number = 0
for(var/A in available_equipment)
@@ -101,7 +101,7 @@
chassis.occupant_message("You switch to [chassis.selected]")
button_icon_state = "mech_cycle_equip_on"
send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list())
- UpdateButtonIcon()
+ build_all_button_icons()
return
@@ -120,7 +120,7 @@
chassis.set_light_on(chassis.lights)
chassis.occupant_message("Toggled lights [chassis.lights?"on":"off"].")
chassis.log_message("Toggled lights [chassis.lights?"on":"off"].", LOG_MECHA)
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/mecha/mech_view_stats
name = "View Stats"
@@ -151,7 +151,7 @@
occupant_message("Toggled strafing mode [strafe?"on":"off"].")
log_message("Toggled strafing mode [strafe?"on":"off"].", LOG_MECHA)
- strafing_action.UpdateButtonIcon()
+ strafing_action.build_all_button_icons()
//////////////////////////////////////// Specific Ability Actions ///////////////////////////////////////////////
//Need to be granted by the mech type, Not default abilities.
@@ -189,7 +189,7 @@
chassis.deflect_chance -= chassis.defence_mode_deflect_chance
chassis.occupant_message(span_danger("You disable [chassis] defence mode."))
chassis.log_message("Toggled defence mode.", LOG_MECHA)
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/mecha/mech_overload_mode
name = "Toggle leg actuators overload"
@@ -218,7 +218,7 @@
chassis.step_in = initial(chassis.step_in)
chassis.step_energy_drain = chassis.normal_step_energy_drain
chassis.occupant_message(span_notice("You disable leg actuators overload."))
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/mecha/mech_smoke
name = "Smoke"
@@ -252,7 +252,7 @@
SEND_SOUND(owner, sound('sound/mecha/imag_enh.ogg',volume=50))
else
owner.client.view_size.resetToDefault() //Let's not let this stack shall we?
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/mecha/mech_switch_damtype
name = "Reconfigure arm microtool arrays"
@@ -275,7 +275,7 @@
chassis.damtype = new_damtype
button_icon_state = "mech_damtype_[new_damtype]"
playsound(src, 'sound/mecha/mechmove01.ogg', 50, 1)
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/mecha/mech_toggle_phasing
name = "Toggle Phasing"
@@ -287,4 +287,4 @@
chassis.phasing = !chassis.phasing
button_icon_state = "mech_phasing_[chassis.phasing ? "on" : "off"]"
chassis.occupant_message("En":"#f00\">Dis"]abled phasing.")
- UpdateButtonIcon()
+ build_all_button_icons()
diff --git a/code/game/mecha/medical/odysseus.dm b/code/game/mecha/medical/odysseus.dm
index 323e77d5736c..4a27ac697a90 100644
--- a/code/game/mecha/medical/odysseus.dm
+++ b/code/game/mecha/medical/odysseus.dm
@@ -14,13 +14,13 @@
. = ..()
if(.)
var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- hud.add_hud_to(H)
+ hud.show_to(H)
/obj/mecha/medical/odysseus/go_out()
if(isliving(occupant))
var/mob/living/L = occupant
var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- hud.remove_hud_from(L)
+ hud.hide_from(L)
..()
/obj/mecha/medical/odysseus/mmi_moved_inside(obj/item/mmi/mmi_as_oc, mob/user)
@@ -28,4 +28,4 @@
if(.)
var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
var/mob/living/brain/B = mmi_as_oc.brainmob
- hud.add_hud_to(B)
+ hud.show_to(B)
diff --git a/code/game/mecha/working/clarke.dm b/code/game/mecha/working/clarke.dm
index a360e565b88d..04f6fa9cbb1e 100644
--- a/code/game/mecha/working/clarke.dm
+++ b/code/game/mecha/working/clarke.dm
@@ -35,13 +35,13 @@
. = ..()
if(.)
var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED]
- hud.add_hud_to(H)
+ hud.show_to(H)
/obj/mecha/working/clarke/go_out()
if(isliving(occupant))
var/mob/living/L = occupant
var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED]
- hud.remove_hud_from(L)
+ hud.hide_from(L)
return ..()
/obj/mecha/working/clarke/mmi_moved_inside(obj/item/mmi/M, mob/user)
@@ -49,7 +49,7 @@
if(.)
var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED]
var/mob/living/brain/B = M.brainmob
- hud.add_hud_to(B)
+ hud.show_to(B)
//Ore Box Controls
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index f405ade11d6a..d2ba03578acf 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -94,7 +94,7 @@
if(.)
if(resistance_flags & ON_FIRE) //Sets the mob on fire if you buckle them to a burning atom/movableect
M.adjust_fire_stacks(1)
- M.IgniteMob()
+ M.ignite_mob()
/atom/movable/proc/unbuckle_mob(mob/living/buckled_mob, force=FALSE)
if(istype(buckled_mob) && buckled_mob.buckled == src && (buckled_mob.can_unbuckle() || force))
@@ -142,20 +142,19 @@
span_italics("You hear metal clanking."))
/atom/movable/proc/user_unbuckle_mob(mob/living/buckled_mob, mob/user)
- var/mob/living/M = unbuckle_mob(buckled_mob)
- if(M)
- if(M != user)
- M.visible_message(\
- span_notice("[user] unbuckles [M] from [src]."),\
+ if(unbuckle_mob(buckled_mob))
+ if(buckled_mob != user)
+ buckled_mob.visible_message(\
+ span_notice("[user] unbuckles [buckled_mob] from [src]."),\
span_notice("[user] unbuckles you from [src]."),\
span_italics("You hear metal clanking."))
else
- M.visible_message(\
- span_notice("[M] unbuckles [M.p_them()]self from [src]."),\
+ buckled_mob.visible_message(\
+ span_notice("[buckled_mob] unbuckles [buckled_mob.p_them()]self from [src]."),\
span_notice("You unbuckle yourself from [src]."),\
span_italics("You hear metal clanking."))
add_fingerprint(user)
- if(isliving(M.pulledby))
- var/mob/living/L = M.pulledby
- L.set_pull_offsets(M, L.grab_state)
- return M
+ if(isliving(buckled_mob.pulledby))
+ var/mob/living/L = buckled_mob.pulledby
+ L.set_pull_offsets(buckled_mob, L.grab_state)
+ return buckled_mob
diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm
index d898eb5fc99b..bbd23a4dfa4b 100644
--- a/code/game/objects/effects/anomalies.dm
+++ b/code/game/objects/effects/anomalies.dm
@@ -203,7 +203,7 @@
do_teleport(AM, locate(AM.x, AM.y, AM.z), 8, channel = TELEPORT_CHANNEL_BLUESPACE)
/obj/effect/anomaly/bluespace/detonate()
- var/turf/T = safepick(get_area_turfs(impact_area))
+ var/turf/T = pick(get_area_turfs(impact_area))
if(T)
// Calculate new position (searches through beacons in world)
var/obj/item/beacon/chosen
diff --git a/code/game/objects/effects/blessing.dm b/code/game/objects/effects/blessing.dm
index 323f81b5c031..0a3a9510b18c 100644
--- a/code/game/objects/effects/blessing.dm
+++ b/code/game/objects/effects/blessing.dm
@@ -15,7 +15,7 @@
var/image/I = image(icon = 'icons/effects/effects.dmi', icon_state = "blessed", layer = ABOVE_OPEN_TURF_LAYER, loc = src)
I.alpha = 64
I.appearance_flags = RESET_ALPHA
- add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessedAware, "blessing", I)
+ add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessed_aware, "blessing", I)
RegisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORT, PROC_REF(block_cult_teleport))
/obj/effect/blessing/Destroy()
diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm
index 05d97d25905a..0cff66319c22 100644
--- a/code/game/objects/effects/decals/cleanable.dm
+++ b/code/game/objects/effects/decals/cleanable.dm
@@ -94,11 +94,29 @@
update_icon()
H.update_inv_shoes()
+
+/**
+ * Checks if this decal is a valid decal that can be blood crawled in.
+ */
/obj/effect/decal/cleanable/proc/can_bloodcrawl_in()
if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY))
return bloodiness
- else
- return 0
+
+ return FALSE
+
+/**
+ * Gets the color associated with the any blood present on this decal. If there is no blood, returns null.
+ */
+/obj/effect/decal/cleanable/proc/get_blood_color()
+ switch(blood_state)
+ if(BLOOD_STATE_HUMAN)
+ return rgb(149, 10, 10)
+ if(BLOOD_STATE_XENO)
+ return rgb(43, 186, 0)
+ if(BLOOD_STATE_OIL)
+ return rgb(22, 22, 22)
+
+ return null
/obj/effect/decal/cleanable/wash(clean_types)
..()
diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm
index 2fcfd1188000..8c8902aa5b3b 100644
--- a/code/game/objects/effects/decals/cleanable/humans.dm
+++ b/code/game/objects/effects/decals/cleanable/humans.dm
@@ -25,6 +25,9 @@
. = ..()
icon_state = "[icon_state]-old" //change from the normal blood icon selected from random_icon_states in the parent's Initialize to the old dried up blood.
+/obj/effect/decal/cleanable/blood/old/can_bloodcrawl_in() //Yogs -- dried blood bloodcrawl
+ return TRUE
+
/obj/effect/decal/cleanable/blood/splatter
icon_state = "gibbl1"
random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5")
diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm
index 69d6792e9795..b315c71a9656 100644
--- a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm
+++ b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm
@@ -226,7 +226,7 @@
/obj/effect/particle_effect/fluid/foam/firefighting/Initialize(mapload)
. = ..()
- //RemoveElement(/datum/element/atmos_sensitive)
+ //Remove_element(/datum/element/atmos_sensitive)
/obj/effect/particle_effect/fluid/foam/firefighting/process()
..()
@@ -261,7 +261,7 @@
if(!istype(foaming))
return
foaming.adjust_fire_stacks(-2)
- foaming.ExtinguishMob()
+ foaming.extinguish_mob()
/// A factory which produces firefighting foam
/datum/effect_system/fluid_spread/foam/firefighting
@@ -399,6 +399,6 @@
comp.visible_message(span_danger("[comp] sealed shut!"))
for(var/mob/living/potential_tinder in location)
- potential_tinder.ExtinguishMob()
+ potential_tinder.extinguish_mob()
for(var/obj/item/potential_tinder in location)
potential_tinder.extinguish()
diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
index 61f7e7e62de4..28fd3ac25150 100644
--- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
+++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
@@ -311,7 +311,7 @@
// Extinguishes everything in the turf
for(var/mob/living/potential_tinder in chilly)
- potential_tinder.ExtinguishMob()
+ potential_tinder.extinguish_mob()
for(var/obj/item/potential_tinder in chilly)
potential_tinder.extinguish()
diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm
index d3fa9ce2c122..fb1275636884 100644
--- a/code/game/objects/effects/forcefields.dm
+++ b/code/game/objects/effects/forcefields.dm
@@ -1,38 +1,63 @@
/obj/effect/forcefield
- desc = "A space wizard's magic wall."
name = "FORCEWALL"
+ desc = "A space wizard's magic wall."
icon_state = "m_shield"
anchored = TRUE
opacity = 0
density = TRUE
CanAtmosPass = ATMOS_PASS_DENSITY
- var/timeleft = 300 //Set to 0 for permanent forcefields (ugh)
+ /// If set, how long the force field lasts after it's created. Set to 0 to have infinite duration forcefields.
+ var/initial_duration = 30 SECONDS
/obj/effect/forcefield/Initialize()
. = ..()
- if(timeleft)
- QDEL_IN(src, timeleft)
+ if(initial_duration)
+ QDEL_IN(src, initial_duration)
/obj/effect/forcefield/singularity_pull()
return
+// The wizard's forcefield, summoned by forcewall
+/obj/effect/forcefield/wizard
+ /// Flags for what antimagic can just ignore our forcefields
+ var/antimagic_flags = MAGIC_RESISTANCE
+ /// A weakref to whoever casted our forcefield.
+ var/datum/weakref/caster_weakref
+
+/obj/effect/forcefield/wizard/Initialize(mapload, mob/caster, flags = MAGIC_RESISTANCE)
+ . = ..()
+ if(caster)
+ caster_weakref = WEAKREF(caster)
+ antimagic_flags = flags
+
+/obj/effect/forcefield/wizard/CanAllowThrough(atom/movable/mover, border_dir)
+ if(IS_WEAKREF_OF(mover, caster_weakref))
+ return TRUE
+ if(isliving(mover))
+ var/mob/living/living_mover = mover
+ if(living_mover.can_block_magic(antimagic_flags, charge_cost = 0))
+ return TRUE
+
+ return ..()
+
+/// Cult forcefields
/obj/effect/forcefield/cult
- desc = "An unholy shield that blocks all attacks."
name = "glowing wall"
+ desc = "An unholy shield that blocks all attacks."
icon = 'icons/effects/cult_effects.dmi'
icon_state = "cultshield"
CanAtmosPass = ATMOS_PASS_NO
- timeleft = 200
+ initial_duration = 20 SECONDS
-///////////Mimewalls///////////
+/// Mime forcefields (invisible walls)
/obj/effect/forcefield/mime
- icon_state = "nothing"
name = "invisible wall"
+ icon_state = "nothing"
desc = "You have a bad feeling about this."
alpha = 0
/obj/effect/forcefield/mime/advanced
name = "invisible blockade"
desc = "You're gonna be here awhile."
- timeleft = 600
+ initial_duration = 1 MINUTES
diff --git a/code/game/objects/effects/info.dm b/code/game/objects/effects/info.dm
new file mode 100644
index 000000000000..adf609d50c23
--- /dev/null
+++ b/code/game/objects/effects/info.dm
@@ -0,0 +1,26 @@
+/// An info button that, when clicked, puts some text in the user's chat
+/obj/effect/abstract/info
+ name = "info"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "info"
+
+ /// What should the info button display when clicked?
+ var/info_text
+
+/obj/effect/abstract/info/Initialize(mapload, info_text)
+ . = ..()
+
+ if (!isnull(info_text))
+ src.info_text = info_text
+
+/obj/effect/abstract/info/Click()
+ . = ..()
+ to_chat(usr, info_text)
+
+/obj/effect/abstract/info/MouseEntered(location, control, params)
+ . = ..()
+ icon_state = "info_hovered"
+
+/obj/effect/abstract/info/MouseExited()
+ . = ..()
+ icon_state = initial(icon_state)
diff --git a/code/game/objects/effects/phased_mob.dm b/code/game/objects/effects/phased_mob.dm
index 6d723ba97b16..4cd91f2e5c7b 100644
--- a/code/game/objects/effects/phased_mob.dm
+++ b/code/game/objects/effects/phased_mob.dm
@@ -5,32 +5,60 @@
resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
invisibility = INVISIBILITY_OBSERVER
movement_type = FLOATING
+ /// The movable which's jaunting in this dummy
+ var/atom/movable/jaunter
+ /// The delay between moves while jaunted
var/movedelay = 0
+ /// The speed of movement while jaunted
var/movespeed = 0
+/obj/effect/dummy/phased_mob/Initialize(mapload, atom/movable/jaunter)
+ . = ..()
+ if(jaunter)
+ set_jaunter(jaunter)
+
+/// Sets [new_jaunter] as our jaunter, forcemoves them into our contents
+/obj/effect/dummy/phased_mob/proc/set_jaunter(atom/movable/new_jaunter)
+ jaunter = new_jaunter
+ jaunter.forceMove(src)
+ if(ismob(jaunter))
+ var/mob/mob_jaunter = jaunter
+ mob_jaunter.reset_perspective(src)
+
/obj/effect/dummy/phased_mob/Destroy()
- // Eject contents if deleted somehow
- var/atom/dest = drop_location()
- if(!dest) //You're in nullspace you clown
- return ..()
- var/area/destination_area = get_area(dest)
- var/failed_areacheck = FALSE
- if(destination_area.noteleport)
- failed_areacheck = TRUE
- for(var/_phasing_in in contents)
- var/atom/movable/phasing_in = _phasing_in
- if(!failed_areacheck)
- phasing_in.forceMove(drop_location())
- else //this ONLY happens if someone uses a phasing effect to try to land in a NOTELEPORT zone after it is created, AKA trying to exploit.
- if(isliving(phasing_in))
- var/mob/living/living_cheaterson = phasing_in
- to_chat(living_cheaterson, span_userdanger("This area has a heavy universal force occupying it, and you are scattered to the cosmos!"))
- if(ishuman(living_cheaterson))
- shake_camera(living_cheaterson, 20, 1)
- addtimer(CALLBACK(living_cheaterson, TYPE_PROC_REF(/mob/living/carbon, vomit)), 2 SECONDS)
- phasing_in.forceMove(find_safe_turf(max(z,2)))
+ jaunter = null // If a mob was left in the jaunter on qdel, they'll be dumped into nullspace
return ..()
+/// Removes [jaunter] from our phased mob
+/obj/effect/dummy/phased_mob/proc/eject_jaunter()
+ if(!jaunter)
+ return // This is weird but it can happen if the jaunt is gibbed by an arriving shuttle
+ var/turf/eject_spot = get_turf(src)
+ if(!eject_spot) //You're in nullspace you clown!
+ return
+
+ var/area/destination_area = get_area(eject_spot)
+ if(destination_area.area_flags & NOTELEPORT)
+ // this ONLY happens if someone uses a phasing effect
+ // to try to land in a NOTELEPORT zone after it is created, AKA trying to exploit.
+ if(isliving(jaunter))
+ var/mob/living/living_cheaterson = jaunter
+ to_chat(living_cheaterson, span_userdanger("This area has a heavy universal force occupying it, and you are scattered to the cosmos!"))
+ if(ishuman(living_cheaterson))
+ shake_camera(living_cheaterson, 20, 1)
+ addtimer(CALLBACK(living_cheaterson, TYPE_PROC_REF(/mob/living/carbon, vomit)), 2 SECONDS)
+ jaunter.forceMove(find_safe_turf(max(z,2)))
+
+ else
+ jaunter.forceMove(eject_spot)
+ qdel(src)
+
+/obj/effect/dummy/phased_mob/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone == jaunter)
+ SEND_SIGNAL(src, COMSIG_MOB_EJECTED_FROM_JAUNT, jaunter)
+ jaunter = null
+
/obj/effect/dummy/phased_mob/ex_act()
return FALSE
@@ -41,7 +69,9 @@
var/turf/newloc = phased_check(user, direction)
if(!newloc)
return
- setDir(direction)
+
+ if (direction in GLOB.alldirs)
+ setDir(direction)
forceMove(newloc)
/// Checks if the conditions are valid to be able to phase. Returns a turf destination if positive.
@@ -49,7 +79,7 @@
RETURN_TYPE(/turf)
if (movedelay > world.time || !direction)
return
- var/turf/newloc = get_step(src,direction)
+ var/turf/newloc = get_step_multiz(src,direction)
if(!newloc)
return
var/area/destination_area = newloc.loc
@@ -61,8 +91,3 @@
to_chat(user, span_danger("Some dull, universal force is blocking the way. It's overwhelmingly oppressive force feels dangerous."))
return
return newloc
-
-/// React to signals by deleting the effect. Used for bloodcrawl.
-/obj/effect/dummy/phased_mob/proc/deleteself(mob/living/source, obj/effect/decal/cleanable/phase_in_decal)
- SHOULD_NOT_SLEEP(TRUE)
- qdel(src)
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index 49eabafc9120..95976ac8efe9 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -110,13 +110,25 @@
icon_state = "phaseout"
/obj/effect/temp_visual/dir_setting/wraith
- name = "blood"
- icon = 'icons/mob/mob.dmi'
- icon_state = "phase_shift2"
- duration = 1.2 SECONDS
+ name = "shadow"
+ icon = 'icons/mob/nonhuman-player/cult.dmi'
+ icon_state = "phase_shift2_cult"
+ duration = 0.6 SECONDS
+
+/obj/effect/temp_visual/dir_setting/wraith/angelic
+ icon_state = "phase_shift2_holy"
+
+/obj/effect/temp_visual/dir_setting/wraith/mystic
+ icon_state = "phase_shift2_wizard"
/obj/effect/temp_visual/dir_setting/wraith/out
- icon_state = "phase_shift"
+ icon_state = "phase_shift_cult"
+
+/obj/effect/temp_visual/dir_setting/wraith/out/angelic
+ icon_state = "phase_shift_holy"
+
+/obj/effect/temp_visual/dir_setting/wraith/out/mystic
+ icon_state = "phase_shift_wizard"
/obj/effect/temp_visual/dir_setting/tailsweep
icon_state = "tailsweep"
@@ -420,7 +432,7 @@
/obj/effect/temp_visual/love_heart/invisible/Initialize(mapload, mob/seer)
. = ..()
var/image/I = image(icon = 'icons/effects/effects.dmi', icon_state = "heart", layer = ABOVE_MOB_LAYER, loc = src)
- add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/onePerson, "heart", I, seer)
+ add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/one_person, "heart", I, seer)
I.alpha = 255
I.appearance_flags = RESET_ALPHA
animate(I, alpha = 0, time = duration)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 220c1c5e106c..2bb48044069e 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -4,8 +4,6 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
// if true, everyone item when created will have its name changed to be
// more... RPG-like.
-GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons/effects/welding_effect.dmi', "welding_sparks", GASFIRE_LAYER, ABOVE_LIGHTING_PLANE))
-
/obj/item
name = "item"
icon = 'icons/obj/misc.dmi'
@@ -161,8 +159,11 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
attack_verb = typelist("attack_verb", attack_verb)
. = ..()
+
+ // Handle adding item associated actions
for(var/path in actions_types)
- new path(src)
+ add_item_action(path)
+
actions_types = null
if(GLOB.rpg_loot_items)
@@ -192,10 +193,56 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
if(ismob(loc))
var/mob/m = loc
m.temporarilyRemoveItemFromInventory(src, TRUE)
- for(var/X in actions)
- qdel(X)
+
+ // Handle cleaning up our actions list
+ for(var/datum/action/action as anything in actions)
+ remove_item_action(action)
+
return ..()
+/// Called when an action associated with our item is deleted
+/obj/item/proc/on_action_deleted(datum/source)
+ SIGNAL_HANDLER
+
+ if(!(source in actions))
+ CRASH("An action ([source.type]) was deleted that was associated with an item ([src]), but was not found in the item's actions list.")
+
+ LAZYREMOVE(actions, source)
+
+/// Adds an item action to our list of item actions.
+/// Item actions are actions linked to our item, that are granted to mobs who equip us.
+/// This also ensures that the actions are properly tracked in the actions list and removed if they're deleted.
+/// Can be be passed a typepath of an action or an instance of an action.
+/obj/item/proc/add_item_action(action_or_action_type)
+
+ var/datum/action/action
+ if(ispath(action_or_action_type, /datum/action))
+ action = new action_or_action_type(src)
+ else if(istype(action_or_action_type, /datum/action))
+ action = action_or_action_type
+ else
+ CRASH("item add_item_action got a type or instance of something that wasn't an action.")
+
+ LAZYADD(actions, action)
+ RegisterSignal(action, COMSIG_PARENT_QDELETING, PROC_REF(on_action_deleted))
+ if(ismob(loc))
+ // We're being held or are equipped by someone while adding an action?
+ // Then they should also probably be granted the action, given it's in a correct slot
+ var/mob/holder = loc
+ give_item_action(action, holder, holder.get_slot_by_item(src))
+
+ return action
+
+/// Removes an instance of an action from our list of item actions.
+/obj/item/proc/remove_item_action(datum/action/action)
+ if(!action)
+ return
+
+ UnregisterSignal(action, COMSIG_PARENT_QDELETING)
+ LAZYREMOVE(actions, action)
+ qdel(action)
+
+
/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self)
if(((src in target) && !target_self) || (!isturf(target.loc) && !isturf(target) && not_inside))
return 0
@@ -411,7 +458,7 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
R.hud_used.update_robot_modules_display()
/obj/item/proc/GetDeconstructableContents()
- return GetAllContents() - src
+ return get_all_contents() - src
// afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency
@@ -427,9 +474,11 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
/obj/item/proc/dropped(mob/user, silent = FALSE)
SHOULD_CALL_PARENT(TRUE)
- for(var/X in actions)
- var/datum/action/A = X
- A.Remove(user)
+
+ // Remove any item actions we temporary gave out.
+ for(var/datum/action/action_item_has as anything in actions)
+ action_item_has.Remove(user)
+
if(item_flags & DROPDEL)
qdel(src)
item_flags &= ~IN_INVENTORY
@@ -456,10 +505,12 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
/obj/item/proc/equipped(mob/user, slot, initial = FALSE)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot)
- for(var/X in actions)
- var/datum/action/A = X
- if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot.
- A.Grant(user)
+ SEND_SIGNAL(user, COMSIG_MOB_EQUIPPED_ITEM, src, slot)
+
+ // Give out actions our item has to people who equip it.
+ for(var/datum/action/action as anything in actions)
+ give_item_action(action, user, slot)
+
item_flags |= IN_INVENTORY
if(!initial)
if(equip_sound && !initial &&(slot_flags & slotdefine2slotbit(slot)))
@@ -467,7 +518,19 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
else if(slot == SLOT_HANDS)
playsound(src, pickup_sound, PICKUP_SOUND_VOLUME, ignore_walls = FALSE)
-//sometimes we only want to grant the item's action if it's equipped in a specific slot.
+/// Gives one of our item actions to a mob, when equipped to a certain slot
+/obj/item/proc/give_item_action(datum/action/action, mob/to_who, slot)
+ // Some items only give their actions buttons when in a specific slot.
+ if(!item_action_slot_check(slot, to_who))
+ // There is a chance we still have our item action currently,
+ // and are moving it from a "valid slot" to an "invalid slot".
+ // So call Remove() here regardless, even if excessive.
+ action.Remove(to_who)
+ return
+
+ action.Grant(to_who)
+
+/// Sometimes we only want to grant the item's action if it's equipped in a specific slot.
/obj/item/proc/item_action_slot_check(slot, mob/user)
if(slot == SLOT_IN_BACKPACK || slot == SLOT_LEGCUFFED) //these aren't true slots, so avoid granting actions there
return FALSE
@@ -598,7 +661,7 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum)
if(is_hot() && isliving(hit_atom))
var/mob/living/L = hit_atom
- L.IgniteMob()
+ L.ignite_mob()
var/itempush = 1
if(w_class < 4)
itempush = 0 //too light to push anything
diff --git a/code/game/objects/items/RCD.dm b/code/game/objects/items/RCD.dm
index 9e99d52f787d..4878db6285c5 100644
--- a/code/game/objects/items/RCD.dm
+++ b/code/game/objects/items/RCD.dm
@@ -1028,9 +1028,13 @@ RLD
/obj/item/rcd_upgrade/furnishing
desc = "It contains the design for chairs, stools, tables, and glass tables."
upgrade = RCD_UPGRADE_FURNISHING
+
/obj/item/rcd_upgrade/conveyor
desc = "The disk warns against building an endless conveyor trap, but we know what you're gonna do."
upgrade = RCD_UPGRADE_CONVEYORS
+
+/datum/action/item_action/pick_color
+ name = "Choose A Color"
#undef GLOW_MODE
#undef LIGHT_MODE
diff --git a/code/game/objects/items/RCL.dm b/code/game/objects/items/RCL.dm
index 473c3d90580a..c28b11a8ccc3 100644
--- a/code/game/objects/items/RCL.dm
+++ b/code/game/objects/items/RCL.dm
@@ -327,3 +327,13 @@
else
icon_state = "rclg-1"
item_state = "rclg-1"
+
+/datum/action/item_action/rcl_col
+ name = "Change Cable Color"
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "rcl_rainbow"
+
+/datum/action/item_action/rcl_gui
+ name = "Toggle Fast Wiring Gui"
+ button_icon = 'icons/mob/actions/actions_items.dmi'
+ button_icon_state = "rcl_gui"
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index b85a9d5a6a5b..e66386cc1672 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -99,7 +99,7 @@
if (prob(5))
var/mob/living/M = user
M.adjust_fire_stacks(1)
- M.IgniteMob()
+ M.ignite_mob()
to_chat(user, span_danger("The card shorts out and catches fire in your hands!"))
log_combat(user, target, "attempted to emag")
target.emag_act(user)
@@ -602,7 +602,7 @@ update_label("John Doe", "Clowny")
if(isliving(loc))
var/mob/living/M = loc
M.adjust_fire_stacks(1)
- M.IgniteMob()
+ M.ignite_mob()
if(istype(loc,/obj/structure/fireaxecabinet/bridge/spare)) //if somebody is being naughty and putting the temporary spare in the cabinet
var/obj/structure/fireaxecabinet/bridge/spare/holder = loc
forceMove(holder.loc)
diff --git a/code/game/objects/items/chromosome.dm b/code/game/objects/items/chromosome.dm
index 509797fffe59..eed29612a627 100644
--- a/code/game/objects/items/chromosome.dm
+++ b/code/game/objects/items/chromosome.dm
@@ -34,9 +34,13 @@
HM.power_coeff = power_coeff
if(HM.energy_coeff != -1)
HM.energy_coeff = energy_coeff
- HM.can_chromosome = 2
+ HM.can_chromosome = CHROMOSOME_USED
HM.chromosome_name = name
- HM.modify()
+
+ // Do the actual modification
+ if(HM.modify())
+ HM.modified = TRUE
+
qdel(src)
/proc/generate_chromosome()
diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm
index 6893151daae3..b52df711f875 100644
--- a/code/game/objects/items/cigs_lighters.dm
+++ b/code/game/objects/items/cigs_lighters.dm
@@ -75,7 +75,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
/obj/item/match/attack(mob/living/carbon/M, mob/living/carbon/user)
if(!isliving(M))
return
- if(lit && M.IgniteMob())
+ if(lit && M.ignite_mob())
message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]")
log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]")
var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M)
@@ -262,7 +262,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
var/turf/location = get_turf(src)
var/mob/living/M = loc
if(isliving(loc))
- M.IgniteMob()
+ M.ignite_mob()
smoketime -= delta_time
if(smoketime <= 0)
new type_butt(location)
@@ -658,7 +658,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
. = ..()
/obj/item/lighter/attack(mob/living/carbon/M, mob/living/carbon/user)
- if(lit && M.IgniteMob())
+ if(lit && M.ignite_mob())
message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]")
log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]")
var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M)
@@ -906,7 +906,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
if(reagents.get_reagent_amount(/datum/reagent/fuel))
//HOT STUFF
C.fire_stacks = 2
- C.IgniteMob()
+ C.ignite_mob()
if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire
var/datum/effect_system/reagents_explosion/e = new()
@@ -932,7 +932,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
var/mob/living/M = loc
if(isliving(loc))
- M.IgniteMob()
+ M.ignite_mob()
vapetime += delta_time
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 396cd04cf103..ec83f4c08b7d 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -668,7 +668,7 @@
C.blur_eyes(3)
C.blind_eyes(1)
if(C.get_eye_protection() <= 0) // no eye protection? ARGH IT BURNS.
- C.confused = max(C.confused, 3)
+ C.adjust_confusion(3)
C.Knockdown(20)
if(ishuman(C) && actually_paints)
var/mob/living/carbon/human/H = C
diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm
index 790bc0bfaad2..0e13076233d6 100644
--- a/code/game/objects/items/defib.dm
+++ b/code/game/objects/items/defib.dm
@@ -192,7 +192,7 @@
update_icon()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.build_all_button_icons()
/obj/item/defibrillator/proc/make_paddles()
return new /obj/item/twohanded/shockpaddles(src)
@@ -539,7 +539,7 @@
H.apply_damage(50, BURN, BODY_ZONE_CHEST)
log_combat(user, H, "overloaded the heart of", defib)
H.Paralyze(100)
- H.Jitter(100)
+ H.adjust_jitter(100 SECONDS)
if(req_defib)
defib.deductcharge(revivecost)
cooldown = TRUE
@@ -633,7 +633,7 @@
H.set_heartattack(FALSE)
H.revive()
H.emote("gasp")
- H.Jitter(100)
+ H.adjust_jitter(100 SECONDS)
SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK)
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "saved_life", /datum/mood_event/saved_life)
log_combat(user, H, "revived", defib)
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 5c24164cf374..512fced644ff 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -952,7 +952,7 @@ GLOBAL_LIST_EMPTY(PDAs)
update_icon()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.build_all_button_icons()
/obj/item/pda/proc/remove_pen()
diff --git a/code/game/objects/items/devices/busterarm/_buster.dm b/code/game/objects/items/devices/busterarm/_buster.dm
index c41ad88379bc..dd8104f5af4f 100644
--- a/code/game/objects/items/devices/busterarm/_buster.dm
+++ b/code/game/objects/items/devices/busterarm/_buster.dm
@@ -5,17 +5,17 @@
/* Formatting for these files, from top to bottom:
* Action
* Trigger()
- * IsAvailable()
+ * IsAvailable(feedback = FALSE)
* Items
In regards to actions or items with left and right subtypes, list the base, then left, then right.
*/
/// Base for all buster arm actions
/datum/action/cooldown/buster
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS
+ check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS
transparent_when_unavailable = TRUE
- icon_icon = 'icons/mob/actions/actions_arm.dmi'
+ button_icon = 'icons/mob/actions/actions_arm.dmi'
-/datum/action/cooldown/buster/IsAvailable()
+/datum/action/cooldown/buster/IsAvailable(feedback = FALSE)
. = ..()
if(!isliving(owner))
return FALSE
diff --git a/code/game/objects/items/devices/busterarm/buster_limb.dm b/code/game/objects/items/devices/busterarm/buster_limb.dm
index 29fd47671af0..bb0ebc2df3e5 100644
--- a/code/game/objects/items/devices/busterarm/buster_limb.dm
+++ b/code/game/objects/items/devices/busterarm/buster_limb.dm
@@ -1,7 +1,7 @@
/* Formatting for these files, from top to bottom:
* Action
* Trigger()
- * IsAvailable()
+ * IsAvailable(feedback = FALSE)
* Items
In regards to actions or items with left and right subtypes, list the base, then left, then right.
*/
diff --git a/code/game/objects/items/devices/busterarm/megabuster.dm b/code/game/objects/items/devices/busterarm/megabuster.dm
index 4acf2307840f..bd597a4cf6e3 100644
--- a/code/game/objects/items/devices/busterarm/megabuster.dm
+++ b/code/game/objects/items/devices/busterarm/megabuster.dm
@@ -1,7 +1,7 @@
/* Formatting for these files, from top to bottom:
* Action
* Trigger()
- * IsAvailable()
+ * IsAvailable(feedback = FALSE)
* Items
In regards to actions or items with left and right subtypes, list the base, then left, then right.
*/
@@ -45,7 +45,7 @@
owner.swap_hand(0)
StartCooldown()
-/datum/action/cooldown/buster/megabuster/l/IsAvailable()
+/datum/action/cooldown/buster/megabuster/l/IsAvailable(feedback = FALSE)
. = ..()
var/mob/living/O = owner
var/obj/item/bodypart/l_arm/L = O.get_bodypart(BODY_ZONE_L_ARM)
@@ -53,7 +53,7 @@
to_chat(owner, span_warning("The arm isn't in a functional state right now!"))
return FALSE
-/datum/action/cooldown/buster/megabuster/r/IsAvailable()
+/datum/action/cooldown/buster/megabuster/r/IsAvailable(feedback = FALSE)
. = ..()
var/mob/living/O = owner
var/obj/item/bodypart/r_arm/R = O.get_bodypart(BODY_ZONE_R_ARM)
diff --git a/code/game/objects/items/devices/busterarm/wire_snatch.dm b/code/game/objects/items/devices/busterarm/wire_snatch.dm
index 8dedc0ccd54d..0e1bb1d73c19 100644
--- a/code/game/objects/items/devices/busterarm/wire_snatch.dm
+++ b/code/game/objects/items/devices/busterarm/wire_snatch.dm
@@ -1,7 +1,7 @@
/* Formatting for these files, from top to bottom:
* Action
* Trigger()
- * IsAvailable()
+ * IsAvailable(feedback = FALSE)
* Items
In regards to actions or items with left and right subtypes, list the base, then left, then right.
*/
@@ -11,7 +11,7 @@
desc = "Extend a wire for reeling in foes from a distance. Reeled in targets will be unable to walk for 1.5 seconds. \
Anchored targets that are hit will pull you towards them instead. \
It can be used 3 times before reeling back into the arm."
- icon_icon = 'icons/obj/guns/magic.dmi'
+ button_icon = 'icons/obj/guns/magic.dmi'
button_icon_state = "hook"
cooldown_time = 5 SECONDS
@@ -47,7 +47,7 @@
if(owner.active_hand_index % 2 == 0)
owner.swap_hand(0) //making the grappling hook hand (right) the active one so using it is streamlined
-/datum/action/cooldown/buster/wire_snatch/l/IsAvailable()
+/datum/action/cooldown/buster/wire_snatch/l/IsAvailable(feedback = FALSE)
. = ..()
var/mob/living/O = owner
var/obj/item/bodypart/l_arm/L = O.get_bodypart(BODY_ZONE_L_ARM)
@@ -55,7 +55,7 @@
to_chat(owner, span_warning("The arm isn't in a functional state right now!"))
return FALSE
-/datum/action/cooldown/buster/wire_snatch/r/IsAvailable()
+/datum/action/cooldown/buster/wire_snatch/r/IsAvailable(feedback = FALSE)
. = ..()
var/mob/living/O = owner
var/obj/item/bodypart/r_arm/R = O.get_bodypart(BODY_ZONE_R_ARM)
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index ca7140b04390..aa0c9ff0329a 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -39,7 +39,7 @@
playsound(user, on ? 'sound/weapons/magin.ogg' : 'sound/weapons/magout.ogg', 40, 1)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.build_all_button_icons()
return 1
/obj/item/flashlight/suicide_act(mob/living/carbon/human/user)
diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm
index e6ef0f3c5af6..d5c56bb11dfd 100644
--- a/code/game/objects/items/devices/multitool.dm
+++ b/code/game/objects/items/devices/multitool.dm
@@ -44,25 +44,23 @@
// Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby.
/obj/item/multitool/ai_detect
+ actions_types = list(/datum/action/item_action/toggle_multitool)
var/detect_state = PROXIMITY_NONE
var/rangealert = 8 //Glows red when inside
var/rangewarning = 20 //Glows yellow when inside
var/hud_type = DATA_HUD_AI_DETECT
var/hud_on = FALSE
var/mob/camera/aiEye/remote/ai_detector/eye
- var/datum/action/item_action/toggle_multitool/toggle_action
/obj/item/multitool/ai_detect/Initialize()
. = ..()
START_PROCESSING(SSfastprocess, src)
eye = new /mob/camera/aiEye/remote/ai_detector()
- toggle_action = new /datum/action/item_action/toggle_multitool(src)
/obj/item/multitool/ai_detect/Destroy()
STOP_PROCESSING(SSfastprocess, src)
if(hud_on && ismob(loc))
remove_hud(loc)
- QDEL_NULL(toggle_action)
QDEL_NULL(eye)
return ..()
@@ -104,8 +102,8 @@
var/atom/movable/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"]
PM.alpha = 64
var/datum/atom_hud/H = GLOB.huds[hud_type]
- if(!H.hudusers[user])
- H.add_hud_to(user)
+ if(!H.hud_users[user])
+ H.show_to(user)
eye.eye_user = user
eye.setLoc(get_turf(src))
@@ -114,7 +112,7 @@
var/atom/movable/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"]
PM.alpha = 255
var/datum/atom_hud/H = GLOB.huds[hud_type]
- H.remove_hud_from(user)
+ H.hide_from(user)
if(eye)
eye.setLoc(null)
eye.eye_user = null
@@ -151,7 +149,11 @@
/datum/action/item_action/toggle_multitool
name = "Toggle AI detector HUD"
check_flags = NONE
- syndicate = TRUE
+
+/datum/action/item_action/toggle_multitool/IsAvailable(feedback = FALSE)
+ if(!is_syndicate(owner))
+ HideFrom(owner)
+ return is_syndicate(owner)
/datum/action/item_action/toggle_multitool/Trigger()
if(!..())
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index 8a20d96c6b0e..3e7136da6c92 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -16,13 +16,13 @@
return OXYLOSS
/obj/item/paicard/Initialize()
- SSpai.pai_card_list += src
+ SSpai.paicard_list += src
add_overlay("pai-off")
return ..()
/obj/item/paicard/Destroy()
//Will stop people throwing friend pAIs into the singularity so they can respawn
- SSpai.pai_card_list -= src
+ SSpai.paicard_list -= src
if (!QDELETED(pai))
QDEL_NULL(pai)
return ..()
diff --git a/code/game/objects/items/granters.dm b/code/game/objects/items/granters.dm
deleted file mode 100644
index 9ce4d6c67317..000000000000
--- a/code/game/objects/items/granters.dm
+++ /dev/null
@@ -1,637 +0,0 @@
-
-///books that teach things (intrinsic actions like bar flinging, spells like fireball or smoke, or martial arts)///
-
-/obj/item/book/granter
- due_date = 0 // Game time in deciseconds
- unique = 1 // 0 Normal book, 1 Should not be treated as normal book, unable to be copied, unable to be modified
- var/list/remarks = list() //things to read about while learning.
- var/ordered = FALSE //determines if the remarks should display in order rather than randomly
- var/pages_to_mastery = 3 //Essentially controls how long a mob must keep the book in his hand to actually successfully learn
- var/reading = FALSE //sanity
- var/oneuse = TRUE //default this is true, but admins can var this to 0 if we wanna all have a pass around of the rod form book
- var/used = FALSE //only really matters if oneuse but it might be nice to know if someone's used it for admin investigations perhaps
- var/sound = TRUE //to turn off the page turn sound if desired
-
-/obj/item/book/granter/proc/turn_page(mob/user)
- if(sound)
- playsound(user, pick('sound/effects/pageturn1.ogg','sound/effects/pageturn2.ogg','sound/effects/pageturn3.ogg'), 30, 1)
- if(do_after(user, 5 SECONDS, user))
- if(remarks.len && ordered)
- to_chat(user, span_notice("[popleft(remarks)]"))
- else if(remarks.len)
- to_chat(user, span_notice("[pick(remarks)]"))
- else
- to_chat(user, span_notice("You keep reading..."))
- return TRUE
- return FALSE
-
-/obj/item/book/granter/proc/recoil(mob/user) //nothing so some books can just return
-
-/obj/item/book/granter/proc/already_known(mob/user)
- return FALSE
-
-/obj/item/book/granter/proc/on_reading_start(mob/user)
- to_chat(user, span_notice("You start reading [name]..."))
-
-/obj/item/book/granter/proc/on_reading_stopped(mob/user)
- to_chat(user, span_notice("You stop reading..."))
-
-/obj/item/book/granter/proc/on_reading_finished(mob/user)
- to_chat(user, span_notice("You finish reading [name]!"))
-
-/obj/item/book/granter/proc/onlearned(mob/user)
- used = TRUE
-
-
-/obj/item/book/granter/attack_self(mob/user)
- if(reading)
- to_chat(user, span_warning("You're already reading this!"))
- return FALSE
- if(!user.can_read(src))
- return FALSE
- if(already_known(user))
- return FALSE
- if(used && oneuse)
- recoil(user)
- else
- on_reading_start(user)
- reading = TRUE
- for(var/i=1, i<=pages_to_mastery, i++)
- if(!turn_page(user))
- on_reading_stopped()
- reading = FALSE
- return
- if(do_after(user, 5 SECONDS, user))
- on_reading_finished(user)
- reading = FALSE
- return TRUE
-
-///ACTION BUTTONS///
-
-/obj/item/book/granter/action
- var/granted_action
- var/actionname = "catching bugs" //might not seem needed but this makes it so you can safely name action buttons toggle this or that without it fucking up the granter, also caps
-
-/obj/item/book/granter/action/already_known(mob/user)
- if(!granted_action)
- return TRUE
- for(var/datum/action/A in user.actions)
- if(A.type == granted_action)
- to_chat(user, span_notice("You already know all about [actionname]."))
- return TRUE
- return FALSE
-
-/obj/item/book/granter/action/on_reading_start(mob/user)
- to_chat(user, span_notice("You start reading about [actionname]..."))
-
-/obj/item/book/granter/action/on_reading_finished(mob/user)
- to_chat(user, span_notice("You feel like you've got a good handle on [actionname]!"))
- var/datum/action/G = new granted_action
- G.Grant(user)
- onlearned(user)
-
-/obj/item/book/granter/action/origami
- granted_action = /datum/action/innate/origami
- name = "The Art of Origami"
- desc = "A meticulously in-depth manual explaining the art of paper folding."
- icon_state = "origamibook"
- actionname = "origami"
- oneuse = TRUE
- remarks = list("Dead-stick stability...", "Symmetry seems to play a rather large factor...", "Accounting for crosswinds... really?", "Drag coefficients of various paper types...", "Thrust to weight ratios?", "Positive dihedral angle?", "Center of gravity forward of the center of lift...")
-
-/datum/action/innate/origami
- name = "Origami Folding"
- desc = "Toggles your ability to fold and catch robust paper airplanes."
- button_icon_state = "origami_off"
- check_flags = NONE
-
-/datum/action/innate/origami/Activate()
- to_chat(owner, span_notice("You will now fold origami planes."))
- button_icon_state = "origami_on"
- active = TRUE
- UpdateButtonIcon()
-
-/datum/action/innate/origami/Deactivate()
- to_chat(owner, span_notice("You will no longer fold origami planes."))
- button_icon_state = "origami_off"
- active = FALSE
- UpdateButtonIcon()
-
-///SPELLS///
-
-/obj/item/book/granter/spell
- var/spell
- var/spellname = "conjure bugs"
-
-/obj/item/book/granter/spell/already_known(mob/user)
- if(!spell)
- return TRUE
- for(var/obj/effect/proc_holder/spell/knownspell in user.mind.spell_list)
- if(knownspell.type == spell)
- if(user.mind)
- if(iswizard(user))
- to_chat(user,span_notice("You're already far more versed in this spell than this flimsy how-to book can provide."))
- else
- to_chat(user,span_notice("You've already read this one."))
- return TRUE
- return FALSE
-
-/obj/item/book/granter/spell/on_reading_start(mob/user)
- to_chat(user, span_notice("You start reading about casting [spellname]..."))
-
-/obj/item/book/granter/spell/on_reading_finished(mob/user)
- to_chat(user, span_notice("You feel like you've experienced enough to cast [spellname]!"))
- var/obj/effect/proc_holder/spell/S = new spell
- user.mind.AddSpell(S)
- user.log_message("learned the spell [spellname] ([S])", LOG_ATTACK, color="orange")
- onlearned(user)
-
-/obj/item/book/granter/spell/recoil(mob/user)
- user.visible_message(span_warning("[src] glows in a black light!"))
-
-/obj/item/book/granter/spell/onlearned(mob/user)
- ..()
- if(oneuse)
- user.visible_message(span_caution("[src] glows dark for a second!"))
-
-/obj/item/book/granter/spell/fireball
- spell = /obj/effect/proc_holder/spell/aimed/fireball
- spellname = "fireball"
- icon_state = "bookfireball"
- desc = "This book feels warm to the touch."
- remarks = list("Aim...AIM, FOOL!", "Just catching them on fire won't do...", "Accounting for crosswinds... really?", "I think I just burned my hand...", "Why the dumb stance? It's just a flick of the hand...", "OMEE... ONI... Ugh...", "What's the difference between a fireball and a pyroblast...")
-
-/obj/item/book/granter/spell/fireball/recoil(mob/user)
- ..()
- explosion(user.loc, 1, 0, 2, 3, FALSE, FALSE, 2)
- qdel(src)
-
-/obj/item/book/granter/spell/sacredflame
- spell = /obj/effect/proc_holder/spell/targeted/sacred_flame
- spellname = "sacred flame"
- icon_state = "booksacredflame"
- desc = "Become one with the flames that burn within... and invite others to do so as well."
- remarks = list("Well, it's one way to stop an attacker...", "I'm gonna need some good gear to stop myself from burning to death...", "Keep a fire extinguisher handy, got it...", "I think I just burned my hand...", "Apply flame directly to chest for proper ignition...", "No pain, no gain...", "One with the flame...")
-
-/obj/item/book/granter/spell/smoke
- spell = /obj/effect/proc_holder/spell/targeted/smoke
- spellname = "smoke"
- icon_state = "booksmoke"
- desc = "This book is overflowing with the dank arts."
- remarks = list("Smoke Bomb! Heh...", "Smoke bomb would do just fine too...", "Wait, there's a machine that does the same thing in chemistry?", "This book smells awful...", "Why all these weed jokes? Just tell me how to cast it...", "Wind will ruin the whole spell, good thing we're in space... Right?", "So this is how the spider clan does it...")
-
-/obj/item/book/granter/spell/smoke/lesser //Chaplain smoke book
- spell = /obj/effect/proc_holder/spell/targeted/smoke/lesser
-
-/obj/item/book/granter/spell/smoke/recoil(mob/user)
- ..()
- to_chat(user,span_caution("Your stomach rumbles..."))
- if(user.nutrition)
- user.set_nutrition(200)
- if(user.nutrition <= 0)
- user.set_nutrition(0)
-
-/obj/item/book/granter/spell/blind
- spell = /obj/effect/proc_holder/spell/pointed/trigger/blind
- spellname = "blind"
- icon_state = "bookblind"
- desc = "This book looks blurry, no matter how you look at it."
- remarks = list("Well I can't learn anything if I can't read the damn thing!", "Why would you use a dark font on a dark background...", "Ah, I can't see an Oh, I'm fine...", "I can't see my hand...!", "I'm manually blinking, damn you book...", "I can't read this page, but somehow I feel like I learned something from it...", "Hey, who turned off the lights?")
-
-/obj/item/book/granter/spell/blind/recoil(mob/user)
- ..()
- to_chat(user,span_warning("You go blind!"))
- user.blind_eyes(10)
-
-/obj/item/book/granter/spell/mindswap
- spell = /obj/effect/proc_holder/spell/targeted/mind_transfer
- spellname = "mindswap"
- icon_state = "bookmindswap"
- desc = "This book's cover is pristine, though its pages look ragged and torn."
- var/mob/stored_swap //Used in used book recoils to store an identity for mindswaps
- remarks = list("If you mindswap from a mouse, they will be helpless when you recover...", "Wait, where am I...?", "This book is giving me a horrible headache...", "This page is blank, but I feel words popping into my head...", "GYNU... GYRO... Ugh...", "The voices in my head need to stop, I'm trying to read here...", "I don't think anyone will be happy when I cast this spell...")
-
-/obj/item/book/granter/spell/mindswap/onlearned()
- spellname = pick("fireball", "smoke", "blind", "forcewall", "knock", "barnyard", "charge")
- icon_state = "book[spellname]"
- name = "spellbook of [spellname]" //Note, desc doesn't change by design
- ..()
-
-/obj/item/book/granter/spell/mindswap/recoil(mob/user)
- ..()
- if(stored_swap in GLOB.dead_mob_list)
- stored_swap = null
- if(!stored_swap)
- stored_swap = user
- to_chat(user,span_warning("For a moment you feel like you don't even know who you are anymore."))
- return
- if(stored_swap == user)
- to_chat(user,span_notice("You stare at the book some more, but there doesn't seem to be anything else to learn..."))
- return
- var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new
- if(swapper.cast(list(stored_swap), user, TRUE, TRUE))
- to_chat(user,span_warning("You're suddenly somewhere else... and someone else?!"))
- to_chat(stored_swap,span_warning("Suddenly you're staring at [src] again... where are you, who are you?!"))
- else
- user.visible_message(span_warning("[src] fizzles slightly as it stops glowing!")) //if the mind_transfer failed to transfer mobs, likely due to the target being catatonic.
-
- stored_swap = null
-
-/obj/item/book/granter/spell/forcewall
- spell = /obj/effect/proc_holder/spell/targeted/forcewall
- spellname = "forcewall"
- icon_state = "bookforcewall"
- desc = "This book has a dedication to mimes everywhere inside the front cover."
- remarks = list("I can go through the wall! Neat.", "Why are there so many mime references...?", "This would cause much grief in a hallway...", "This is some surprisingly strong magic to create a wall nobody can pass through...", "Why the dumb stance? It's just a flick of the hand...", "Why are the pages so hard to turn, is this even paper?", "I can't mo Oh, i'm fine...")
-
-/obj/item/book/granter/spell/forcewall/recoil(mob/living/user)
- ..()
- to_chat(user,span_warning("You suddenly feel very solid!"))
- user.Stun(40, ignore_canstun = TRUE)
- user.petrify(60)
-
-/obj/item/book/granter/spell/knock
- spell = /obj/effect/proc_holder/spell/aoe_turf/knock
- spellname = "knock"
- icon_state = "bookknock"
- desc = "This book is hard to hold closed properly."
- remarks = list("Open Sesame!", "So THAT'S the magic password!", "Slow down, book. I still haven't finished this page...", "The book won't stop moving!", "I think this is hurting the spine of the book...", "I can't get to the next page, it's stuck t- I'm good, it just turned to the next page on it's own.", "Yeah, staff of doors does the same thing. Go figure...")
-
-/obj/item/book/granter/spell/knock/recoil(mob/living/user)
- ..()
- to_chat(user,span_warning("You're knocked down!"))
- user.Paralyze(40)
-
-/obj/item/book/granter/spell/barnyard
- spell = /obj/effect/proc_holder/spell/targeted/barnyardcurse
- spellname = "barnyard"
- icon_state = "bookhorses"
- desc = "This book is more horse than your mind has room for."
- remarks = list("Moooooooo!","Moo!","Moooo!", "NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!", "Oink!", "Squeeeeeeee!", "Oink Oink!", "Ree!!", "Reee!!", "REEE!!", "REEEEE!!")
-
-/obj/item/book/granter/spell/barnyard/recoil(mob/living/carbon/user)
- if(ishuman(user))
- to_chat(user,"HORSIE HAS RISEN")
- var/obj/item/clothing/magichead = new /obj/item/clothing/mask/horsehead/cursed(user.drop_location())
- if(!user.dropItemToGround(user.wear_mask))
- qdel(user.wear_mask)
- user.equip_to_slot_if_possible(magichead, SLOT_WEAR_MASK, TRUE, TRUE)
- qdel(src)
- else
- to_chat(user,span_notice("I say thee neigh")) //It still lives here
-
-/obj/item/book/granter/spell/charge
- spell = /obj/effect/proc_holder/spell/targeted/charge
- spellname = "charge"
- icon_state = "bookcharge"
- desc = "This book is made of 100% postconsumer wizard."
- remarks = list("I feel ALIVE!", "I CAN TASTE THE MANA!", "What a RUSH!", "I'm FLYING through these pages!", "THIS GENIUS IS MAKING IT!", "This book is ACTION PAcKED!", "HE'S DONE IT", "LETS GOOOOOOOOOOOO")
-
-/obj/item/book/granter/spell/charge/recoil(mob/user)
- ..()
- to_chat(user,span_warning("[src] suddenly feels very warm!"))
- empulse(src, 1, 1)
-
-/obj/item/book/granter/spell/summonitem
- spell = /obj/effect/proc_holder/spell/targeted/summonitem
- spellname = "instant summons"
- icon_state = "booksummons"
- desc = "This book is bright and garish, very hard to miss."
- remarks = list("I can't look away from the book!", "The words seem to pop around the page...", "I just need to focus on one item...", "Make sure to have a good grip on it when casting...", "Slow down, book. I still haven't finished this page...", "Sounds pretty great with some other magical artifacts...", "Magicians must love this one.")
-
-/obj/item/book/granter/spell/summonitem/recoil(mob/user)
- ..()
- to_chat(user,span_warning("[src] suddenly vanishes!"))
- qdel(src)
-
-/obj/item/book/granter/spell/lightningbolt
- spell = /obj/effect/proc_holder/spell/aimed/lightningbolt
- spellname = "lightning bolt"
- desc = "A book that crackles with energy."
- remarks = list("ZAP!", "I feel some tingling in my fingers...", "Swirl your hands to charge...?", "Unlimited... power...? Shocking...", "FEEL THE THUNDER!")
-
-/obj/item/book/granter/spell/lightningbolt/recoil(mob/user)
- ..()
- to_chat(user, span_warning("The book twists into lightning and leaps at you!"))
- tesla_zap(user, 8, 20000, TESLA_MOB_DAMAGE) //Will chain at a range of 8, but shouldn't straight up crit
- qdel(src)
-
-/obj/item/book/granter/spell/random
- icon_state = "random_book"
-
-/obj/item/book/granter/spell/random/Initialize()
- . = ..()
- var/static/banned_spells = list(/obj/item/book/granter/spell/mimery_blockade, /obj/item/book/granter/spell/mimery_guns, /obj/item/book/granter/spell/fireball)
- var/real_type = pick(subtypesof(/obj/item/book/granter/spell) - banned_spells)
- new real_type(loc)
- return INITIALIZE_HINT_QDEL
-
-///MARTIAL ARTS///
-
-/obj/item/book/granter/martial
- var/martial
- var/martialname = "bug jitsu"
- var/greet = "You feel like you have mastered the art in breaking code. Nice work, jackass."
-
-
-/obj/item/book/granter/martial/already_known(mob/user)
- if(!martial)
- return TRUE
- var/datum/martial_art/MA = martial
- if(user.mind.has_martialart(initial(MA.id)))
- to_chat(user,span_warning("You already know [martialname]!"))
- return TRUE
- return FALSE
-
-/obj/item/book/granter/martial/on_reading_start(mob/user)
- if(sound)
- to_chat(user, span_notice("You start reading about [martialname]..."))
-
-/obj/item/book/granter/martial/on_reading_finished(mob/user)
- to_chat(user, "[greet]")
- var/datum/martial_art/MA = new martial
- MA.teach(user)
- user.log_message("learned the martial art [martialname] ([MA])", LOG_ATTACK, color="orange")
- onlearned(user)
-
-/obj/item/book/granter/martial/cqc
- martial = /datum/martial_art/cqc
- name = "old manual"
- martialname = "close quarters combat"
- desc = "A small, black manual. There are drawn instructions of tactical hand-to-hand combat."
- greet = span_boldannounce("You've mastered the basics of CQC.")
- icon_state = "cqcmanual"
- remarks = list("Kick... Slam...", "Lock... Kick...", "Strike their abdomen, neck and back for critical damage...", "Slam... Lock...", "I could probably combine this with some other martial arts!", "Words that kill...", "The last and final moment is yours...")
-
-/obj/item/book/granter/martial/cqc/onlearned(mob/living/carbon/user)
- ..()
- if(oneuse == TRUE)
- to_chat(user, span_warning("[src] beeps ominously..."))
-
-/obj/item/book/granter/martial/cqc/recoil(mob/living/carbon/user)
- to_chat(user, span_warning("[src] explodes!"))
- playsound(src,'sound/effects/explosion1.ogg',40,1)
- user.flash_act(1, 1)
- user.adjustBruteLoss(6)
- user.adjustFireLoss(6)
- qdel(src)
-
-/obj/item/book/granter/martial/carp
- martial = /datum/martial_art/the_sleeping_carp
- name = "mysterious scroll"
- martialname = "sleeping carp"
- desc = "A scroll filled with strange markings. It seems to be drawings of some sort of martial art."
- greet = "You have learned the ancient martial art of the Sleeping Carp! Your hand-to-hand combat has become much more effective, and you are now able to deflect any projectiles \
- directed toward you. However, you are also unable to use any ranged weaponry. You can learn more about your newfound art by using the Recall Teachings verb in the Sleeping Carp tab."
- icon = 'icons/obj/wizard.dmi'
- icon_state = "scroll2"
- remarks = list("I must prove myself worthy to the masters of the sleeping carp...", "Stance means everything...", "Focus... And you'll be able to incapacitate any foe in seconds...", "I must pierce armor for maximum damage...", "I don't think this would combine with other martial arts...", "Grab them first so they don't retaliate...", "I must prove myself worthy of this power...")
-
-/obj/item/book/granter/martial/carp/onlearned(mob/living/carbon/user)
- ..()
- if(oneuse == TRUE)
- desc = "It's completely blank."
- name = "empty scroll"
- icon_state = "blankscroll"
-
-/obj/item/book/granter/martial/flyingfang
- martial = /datum/martial_art/flyingfang
- name = "strange tablet"
- martialname = "Flying Fang"
- desc = "A tablet with strange pictograms that appear to detail some kind of fighting technique."
- force = 10
- greet = "You have learned the ancient martial art of Flying Fang! Your unarmed attacks have become somewhat more effective, \
- and you are more resistant to damage and stun-based weaponry. However, you are also unable to use any ranged weaponry or armor. You can learn more about your newfound art by using the Recall Teachings verb in the Flying Fang tab."
- icon = 'icons/obj/library.dmi'
- icon_state = "stone_tablet"
- remarks = list("Feasting on the insides of your enemies...", "Some of these techniques look a bit dizzying...", "Not like I need armor anyways...", "Don't get shot, whatever that means...")
-
-/obj/item/book/granter/martial/flyingfang/already_known(mob/user)
- if(!islizard(user))
- to_chat(user, span_warning("You can't tell if this is some poorly written fanfiction or an actual guide to something."))
- return TRUE
- return ..()
-
-/obj/item/book/granter/martial/flyingfang/onlearned(mob/living/carbon/user)
- ..()
- if(oneuse == TRUE)
- desc = "It's completely blank."
- name = "blank tablet"
- icon_state = "stone_tablet_blank"
-
-/obj/item/book/granter/martial/plasma_fist
- martial = /datum/martial_art/plasma_fist
- name = "frayed scroll"
- martialname = "plasma fist"
- desc = "An aged and frayed scrap of paper written in shifting runes. There are hand-drawn illustrations of pugilism."
- greet = "You have learned the ancient martial art of Plasma Fist. Your combos are extremely hard to pull off, but include some of the most deadly moves ever seen including \
- the plasma fist, which when pulled off will make someone violently explode."
- icon = 'icons/obj/wizard.dmi'
- icon_state ="scroll2"
- remarks = list("Balance...", "Power...", "Control...", "Mastery...", "Vigilance...", "Skill...")
-
-/obj/item/book/granter/martial/plasma_fist/onlearned(mob/living/carbon/user)
- ..()
- if(oneuse == TRUE)
- desc = "It's completely blank."
- name = "empty scroll"
-
-/obj/item/book/granter/martial/preternis_stealth
- martial = /datum/martial_art/stealth
- name = "strange electronic board"
- martialname = "Stealth"
- desc = "A strange electronic board, containing some sort of software."
- greet = "You have uploaded some combat modules into yourself. Your combos will now have special effects on your enemies, and mostly are not obvious to other people. \
- You can check what combos can you do, and their effect by using Refresh Data verb in Combat Modules tab."
- icon = 'icons/obj/module.dmi'
- icon_state = "cyborg_upgrade"
- remarks = list("Processing data...")
-
-/obj/item/book/granter/martial/preternis_stealth/already_known(mob/user)
- if(!ispreternis(user))
- to_chat(user, span_warning("You don't understand what to do with this strange electronic device."))
- return TRUE
- return ..()
-
-/obj/item/book/granter/martial/preternis_stealth/onlearned(mob/living/carbon/user)
- ..()
- if(oneuse == TRUE)
- desc = "It looks like it doesn't contain any data no more."
-
-/obj/item/book/granter/martial/garden_warfare
- martial = /datum/martial_art/gardern_warfare
- name = "mysterious scroll"
- martialname = "Garden Warfare"
- desc = "A scroll, filled with a tone of text. Looks like it says something about combat and... plants?"
- greet = "You know the martial art of Garden Warfare! Now you control your body better, then other phytosians do, allowing you to extend vines from your body and impale people with splinters. \
- You can check what combos can you do, and their effect by using Remember the basics verb in Garden Warfare tab."
- icon = 'icons/obj/wizard.dmi'
- icon_state = "scroll2"
- remarks = list("I didn't know that my body grows sprinklers...", "I am able to snatch people with vines? Interesting.", "Wow, strangling people is brutal.") ///Kill me please for this cringe
-
-/obj/item/book/granter/martial/garden_warfare/already_known(mob/user)
- if(!ispodperson(user))
- to_chat(user, span_warning("You see that this scroll says something about natural abilitites of podpeople, but, unfortunately, you are not one of them."))
- return TRUE
- return ..()
-
-/obj/item/book/granter/martial/garden_warfare/onlearned(mob/living/carbon/user)
- ..()
- if(oneuse == TRUE)
- desc = "It's completely blank."
-
-/obj/item/book/granter/martial/explosive_fist
- martial = /datum/martial_art/explosive_fist
- name = "burnt scroll"
- martialname = "Explosive Fist"
- desc = "A burnt scroll, that glorifies plasmamen, and also says a lot things of explosions."
- greet = "You know the martial art of Explosive Fist. Now your attacks deal brute and burn damage, while your combos are able to set people on fire, explode them, or all at once. \
- You can check what combos can you do, and their effect by using Remember the basics verb in Explosive Fist tab."
- icon = 'icons/obj/wizard.dmi'
- icon_state = "scroll2"
- remarks = list("Set them on fire...", "Show the punny humans who is here the supreme race...", "Make them burn...", "Explosion are cool!")
-
-/obj/item/book/granter/martial/explosive_fist/already_known(mob/user)
- if(!isplasmaman(user))
- to_chat(user, span_warning("It says about very dangerous things, that you would prefer not to know."))
- return TRUE
- return ..()
-
-/obj/item/book/granter/martial/explosive_fist/onlearned(mob/living/carbon/user)
- ..()
- if(oneuse == TRUE)
- desc = "It's completely blank."
-
-/obj/item/book/granter/martial/ultra_violence
- martial = /datum/martial_art/ultra_violence
- name = "Version one upgrade module"
- martialname = "Ultra Violence"
- desc = "A module full of forbidden techniques from a horrific event long since passed, or perhaps yet to come."
- greet = "You have installed how to perform Ultra Violence! You are able to redirect electromagnetic pulses, \
- blood heals you, and you CANNOT BE STOPPED. You can mentally practice by using Cyber Grind in the Ultra Violence tab."
- icon = 'icons/obj/module.dmi'
- icon_state = "cyborg_upgrade"
- remarks = list("MANKIND IS DEAD.", "BLOOD IS FUEL.", "HELL IS FULL.")
- ordered = TRUE
-
-/obj/item/book/granter/martial/ultra_violence/already_known(mob/user)
- if(!isipc(user))
- to_chat(user, span_warning("You don't understand what to do with this strange electronic device."))
- return TRUE
- return ..()
-
-/obj/item/book/granter/martial/ultra_violence/onlearned(mob/living/carbon/user)
- ..()
- if(oneuse == TRUE)
- desc = "It's a damaged upgrade module."
- name = "damaged board"
-
-/obj/item/book/granter/martial/worldshaker
- martial = /datum/martial_art/worldshaker
- name = "prototype worldshaker compound"
- martialname = "Worldshaker"
- desc = "A foul concoction made by reverse engineering chemicals compounds found in an ancient Vxtrin military outpost."
- greet = "You feel weirdly good, good enough to shake the world to it's very core. \
- Your plates feel like they are growing past their normal limits. The protection will come in handy, but it will eventually slow you down.\
- You can think about all the things you are now capable of by using the Worldshaker tab."
- icon = 'icons/obj/drinks.dmi'
- icon_state = "flaming_moe"
- remarks = list("Is... it bubbling?", "What's that gross residue on the sides of the vial?", "Am I really considering drinking this?", "I'm pretty sure I just saw a dead fly dissolve in it.", "This is temporary, right?", "I sure hope someone's tested this.")
- sound = FALSE //not a book, it's a vial
-
-/obj/item/book/granter/martial/worldshaker/already_known(mob/user)
- if(!ispreternis(user))
- to_chat(user, span_warning("There is no way in hell i'm drinking this."))
- return TRUE
- return ..()
-
-/obj/item/book/granter/martial/worldshaker/onlearned(mob/living/carbon/user)
- ..()
- if(oneuse == TRUE)
- var/obj/item/reagent_containers/glass/bottle/vial/empty = new()
- user.put_in_active_hand(empty)
- qdel(src)
-
-// I did not include mushpunch's grant, it is not a book and the item does it just fine.
-
-
-//Crafting Recipe books
-
-/obj/item/book/granter/crafting_recipe
- var/list/crafting_recipe_types = list()
-
-/obj/item/book/granter/crafting_recipe/on_reading_finished(mob/user)
- . = ..()
- if(!user.mind)
- return
- for(var/crafting_recipe_type in crafting_recipe_types)
- var/datum/crafting_recipe/R = crafting_recipe_type
- user.mind.teach_crafting_recipe(crafting_recipe_type)
- to_chat(user,span_notice("You learned how to make [initial(R.name)]."))
-
-/obj/item/book/granter/crafting_recipe/weapons
- name = "makeshift weapons 101"
- desc = "A book filled with directions on how to make various weaponry."
- crafting_recipe_types = list(/datum/crafting_recipe/metal_baseball_bat, /datum/crafting_recipe/lance, /datum/crafting_recipe/knifeboxing, /datum/crafting_recipe/pipebomb, /datum/crafting_recipe/makeshiftpistol, /datum/crafting_recipe/makeshiftmagazine, /datum/crafting_recipe/makeshiftsuppressor, /datum/crafting_recipe/makeshiftcrowbar, /datum/crafting_recipe/makeshiftwrench, /datum/crafting_recipe/makeshiftwirecutters, /datum/crafting_recipe/makeshiftweldingtool, /datum/crafting_recipe/makeshiftmultitool, /datum/crafting_recipe/makeshiftscrewdriver, /datum/crafting_recipe/makeshiftknife, /datum/crafting_recipe/makeshiftpickaxe, /datum/crafting_recipe/makeshiftradio, /datum/crafting_recipe/bola_arrow, /datum/crafting_recipe/explosive_arrow, /datum/crafting_recipe/syringe_arrow, /datum/crafting_recipe/flaming_arrow, /datum/crafting_recipe/makeshiftemag)
- icon_state = "bookCrafting"
- oneuse = TRUE
-
-/obj/item/book/granter/crafting_recipe/roburgers
- name = "roburger crafting recipe"
- desc = "A book containing knowledge how to make roburgers."
- crafting_recipe_types = list(/datum/crafting_recipe/food/roburger)
- icon_state = "bookCrafting"
- oneuse = FALSE
-
-// For testing
-/obj/item/book/granter/crafting_recipe/ashwalker
- name = "sandstone slab"
- desc = "A book filled with directions on how to make various tribal clothes and weapons."
- icon_state = "stone_tablet"
- crafting_recipe_types = list(/datum/crafting_recipe/bola_arrow,
- /datum/crafting_recipe/explosive_arrow,
- /datum/crafting_recipe/flaming_arrow,
- /datum/crafting_recipe/syringe_arrow,
- /datum/crafting_recipe/raider_leather,
- /datum/crafting_recipe/tribal_wraps,
- /datum/crafting_recipe/ash_robe,
- /datum/crafting_recipe/ash_robe/young,
- /datum/crafting_recipe/ash_robe/hunter,
- /datum/crafting_recipe/ash_robe/chief,
- /datum/crafting_recipe/ash_robe/shaman,
- /datum/crafting_recipe/ash_robe/tunic,
- /datum/crafting_recipe/ash_robe/dress,
- /datum/crafting_recipe/shamanash,
- /datum/crafting_recipe/tribalmantle,
- /datum/crafting_recipe/leathercape,
- /datum/crafting_recipe/hidemantle)
-
-//for upstart mech pilots to move faster
-/obj/item/book/granter/mechpiloting
- name = "Mech Piloting for Dummies"
- desc = "A step-by-step guide on how to effectively pilot a mech, written in such a way that even a clown could understand."
- remarks = list("Hmm, press forward to go forwards...", "Avoid getting hit to reduce damage...", "Welding to repair..?", "Make sure to turn it on...", "EMP bad...", "I need to turn internals on?", "What's a gun ham?")
-
-/obj/item/book/granter/mechpiloting/on_reading_finished(mob/user)
- . = ..()
- user.AddComponent(/datum/component/mech_pilot, 0.8)
- onlearned(user)
-
-/obj/item/book/granter/martial/psychotic_brawling
- martial = /datum/martial_art/psychotic_brawling
- name = "blood-stained paper"
- martialname = "psychotic brawling"
- desc = "A piece of blood-stained paper that emanates pure rage. Just holding it makes you want to punch someone."
- greet = "You have learned the tried and true art of Psychotic Brawling. \
- You will be unable to disarm or grab, but your punches have a chance to do serious damage."
- icon = 'yogstation/icons/obj/bureaucracy.dmi'
- icon_state = "paper_talisman"
- remarks = list("Just keep punching...", "Let go of your inhibitions...", "Methamphetamine...", "Embrace Space Florida...", "Become too angry to die...")
-
-/obj/item/book/granter/martial/psychotic_brawling/onlearned(mob/living/carbon/user)
- ..()
- if(oneuse == TRUE)
- to_chat(user, span_notice("All of the blood on the paper seems to have vanished."))
- user.dropItemToGround(src)
- qdel(src)
- user.put_in_hands(new /obj/item/paper)
diff --git a/code/game/objects/items/granters/_granters.dm b/code/game/objects/items/granters/_granters.dm
new file mode 100644
index 000000000000..1b7ea6d69276
--- /dev/null
+++ b/code/game/objects/items/granters/_granters.dm
@@ -0,0 +1,106 @@
+/**
+ * Books that teach things.
+ *
+ * (Intrinsic actions like bar flinging, spells like fireball or smoke, or martial arts)
+ */
+/obj/item/book/granter
+ due_date = 0
+ unique = 1
+ /// Flavor messages displayed to mobs reading the granter
+ var/list/remarks = list()
+ /// Controls how long a mob must keep the book in his hand to actually successfully learn
+ var/pages_to_mastery = 3
+ /// Sanity, whether it's currently being read
+ var/reading = FALSE
+ /// The amount of uses on the granter.
+ var/uses = 1
+ /// The sounds played as the user's reading the book.
+ var/list/book_sounds = list(
+ 'sound/effects/pageturn1.ogg',
+ 'sound/effects/pageturn2.ogg',
+ 'sound/effects/pageturn3.ogg',
+ )
+
+/obj/item/book/granter/attack_self(mob/living/user)
+ if(reading)
+ to_chat(user, span_warning("You're already reading this!"))
+ return FALSE
+ if(is_blind(user))
+ to_chat(user, span_warning("You are blind and can't read anything!"))
+ return FALSE
+ if(!isliving(user) || !user.can_read(src))
+ return FALSE
+ if(!can_learn(user))
+ return FALSE
+
+ if(uses <= 0)
+ recoil(user)
+ return FALSE
+
+ on_reading_start(user)
+ reading = TRUE
+ for(var/i in 1 to pages_to_mastery)
+ if(!turn_page(user))
+ on_reading_stopped()
+ reading = FALSE
+ return
+ if(do_after(user, 5 SECONDS, src))
+ uses--
+ on_reading_finished(user)
+ reading = FALSE
+
+ return TRUE
+
+/// Called when the user starts to read the granter.
+/obj/item/book/granter/proc/on_reading_start(mob/living/user)
+ to_chat(user, span_notice("You start reading [name]..."))
+
+/// Called when the reading is interrupted without finishing.
+/obj/item/book/granter/proc/on_reading_stopped(mob/living/user)
+ to_chat(user, span_notice("You stop reading..."))
+
+/// Called when the reading is completely finished. This is where the actual granting should happen.
+/obj/item/book/granter/proc/on_reading_finished(mob/living/user)
+ to_chat(user, span_notice("You finish reading [name]!"))
+
+/// The actual "turning over of the page" flavor bit that happens while someone is reading the granter.
+/obj/item/book/granter/proc/turn_page(mob/living/user)
+ playsound(user, pick(book_sounds), 30, TRUE)
+
+ if(!do_after(user, 5 SECONDS, src))
+ return FALSE
+
+ to_chat(user, span_notice("[length(remarks) ? pick(remarks) : "You keep reading..."]"))
+ return TRUE
+
+/// Effects that occur whenever the book is read when it has no uses left.
+/obj/item/book/granter/proc/recoil(mob/living/user)
+
+/// Checks if the user can learn whatever this granter... grants
+/obj/item/book/granter/proc/can_learn(mob/living/user)
+ return TRUE
+
+// Generic action giver
+/obj/item/book/granter/action
+ /// The typepath of action that is given
+ var/datum/action/granted_action
+ /// The name of the action, formatted in a more text-friendly way.
+ var/action_name = ""
+
+/obj/item/book/granter/action/can_learn(mob/living/user)
+ if(!granted_action)
+ CRASH("Someone attempted to learn [type], which did not have an action set.")
+ if(locate(granted_action) in user.actions)
+ to_chat(user, span_warning("You already know all about [action_name]!"))
+ return FALSE
+ return TRUE
+
+/obj/item/book/granter/action/on_reading_start(mob/living/user)
+ to_chat(user, span_notice("You start reading about [action_name]..."))
+
+/obj/item/book/granter/action/on_reading_finished(mob/living/user)
+ to_chat(user, span_notice("You feel like you've got a good handle on [action_name]!"))
+ // Action goes on the mind as the user actually learns the thing in your brain
+ var/datum/action/new_action = new granted_action(user.mind || user)
+ new_action.Grant(user)
+ new_action.build_all_button_icons()
diff --git a/code/game/objects/items/granters/crafting/_crafting_granter.dm b/code/game/objects/items/granters/crafting/_crafting_granter.dm
new file mode 100644
index 000000000000..c7bcf0e0fb6e
--- /dev/null
+++ b/code/game/objects/items/granters/crafting/_crafting_granter.dm
@@ -0,0 +1,11 @@
+/obj/item/book/granter/crafting_recipe
+ /// A list of all recipe types we grant on learn
+ var/list/crafting_recipe_types = list()
+
+/obj/item/book/granter/crafting_recipe/on_reading_finished(mob/user)
+ . = ..()
+ if(!user.mind)
+ return
+ for(var/datum/crafting_recipe/crafting_recipe_type as anything in crafting_recipe_types)
+ user.mind.teach_crafting_recipe(crafting_recipe_type)
+ to_chat(user, span_notice("You learned how to make [initial(crafting_recipe_type.name)]."))
\ No newline at end of file
diff --git a/code/game/objects/items/granters/crafting/ashwalkers.dm b/code/game/objects/items/granters/crafting/ashwalkers.dm
new file mode 100644
index 000000000000..3a2f7782c057
--- /dev/null
+++ b/code/game/objects/items/granters/crafting/ashwalkers.dm
@@ -0,0 +1,23 @@
+// For testing
+/obj/item/book/granter/crafting_recipe/ashwalker
+ name = "sandstone slab"
+ desc = "A book filled with directions on how to make various tribal clothes and weapons."
+ icon_state = "stone_tablet"
+ crafting_recipe_types = list(
+ /datum/crafting_recipe/bola_arrow,
+ /datum/crafting_recipe/flaming_arrow,
+ /datum/crafting_recipe/raider_leather,
+ /datum/crafting_recipe/tribal_wraps,
+ /datum/crafting_recipe/ash_robe,
+ /datum/crafting_recipe/ash_robe/young,
+ /datum/crafting_recipe/ash_robe/hunter,
+ /datum/crafting_recipe/ash_robe/chief,
+ /datum/crafting_recipe/ash_robe/shaman,
+ /datum/crafting_recipe/ash_robe/tunic,
+ /datum/crafting_recipe/ash_robe/dress,
+ /datum/crafting_recipe/shamanash,
+ /datum/crafting_recipe/tribalmantle,
+ /datum/crafting_recipe/leathercape,
+ /datum/crafting_recipe/hidemantle
+ )
+ uses = 15
diff --git a/code/game/objects/items/granters/crafting/bone_notes.dm b/code/game/objects/items/granters/crafting/bone_notes.dm
new file mode 100644
index 000000000000..c3d7597cab7c
--- /dev/null
+++ b/code/game/objects/items/granters/crafting/bone_notes.dm
@@ -0,0 +1,20 @@
+/*/obj/item/book/granter/crafting_recipe/boneyard_notes
+ name = "The Complete Works of Lavaland Bone Architecture"
+ desc = "Pried from the lead Archaeologist's cold, dead hands, this seems to explain how ancient bone architecture was erected long ago."
+ crafting_recipe_types = list(
+ /datum/crafting_recipe/rib,
+ /datum/crafting_recipe/boneshovel,
+ /datum/crafting_recipe/halfskull,
+ /datum/crafting_recipe/skull,
+ )
+ icon = 'icons/obj/library.dmi'
+ icon_state = "boneworking_learing"
+ uses = INFINITY
+ remarks = list(
+ "Who knew you could bend bones that far back?",
+ "I guess that was much easier before the planet heated up...",
+ "So that's how they made those ruins survive the ashstorms. Neat!",
+ "The page is just filled with insane ramblings about some 'legion' thing.",
+ "But why would they need vinegar to polish the bones? And rags too?",
+ "You spend a few moments cleaning dirt and blood off of the page, yeesh.",
+ )*/
diff --git a/code/game/objects/items/granters/crafting/cannon.dm b/code/game/objects/items/granters/crafting/cannon.dm
new file mode 100644
index 000000000000..98dbb2f77650
--- /dev/null
+++ b/code/game/objects/items/granters/crafting/cannon.dm
@@ -0,0 +1,20 @@
+/*/obj/item/book/granter/crafting_recipe/trash_cannon
+ name = "diary of a demoted engineer"
+ desc = "A lost journal. The engineer seems very deranged about their demotion."
+ crafting_recipe_types = list(
+ /datum/crafting_recipe/trash_cannon,
+ /datum/crafting_recipe/trashball,
+ )
+ icon_state = "book1"
+ remarks = list(
+ "\"I'll show them! I'll build a CANNON!\"",
+ "\"Gunpowder is ideal, but i'll have to improvise...\"",
+ "\"I savor the look on the CE's face when I BLOW down the walls to engineering!\"",
+ "\"If the supermatter gets loose from my rampage, so be it!\"",
+ "\"I'VE GONE COMPLETELY MENTAL!\"",
+ )
+
+/obj/item/book/granter/crafting_recipe/trash_cannon/recoil(mob/living/user)
+ to_chat(user, span_warning("The book turns to dust in your hands."))
+ qdel(src)
+*/
diff --git a/code/game/objects/items/granters/crafting/desserts.dm b/code/game/objects/items/granters/crafting/desserts.dm
new file mode 100644
index 000000000000..4cb3069d04ba
--- /dev/null
+++ b/code/game/objects/items/granters/crafting/desserts.dm
@@ -0,0 +1,21 @@
+/*/obj/item/book/granter/crafting_recipe/cooking_sweets_101
+ name = "Cooking Desserts 101"
+ desc = "A cook book that teaches you some more of the newest desserts. AI approved, and a best seller on Honkplanet."
+ crafting_recipe_types = list(
+ /datum/crafting_recipe/food/mimetart,
+ /datum/crafting_recipe/food/berrytart,
+ /datum/crafting_recipe/food/cocolavatart,
+ /datum/crafting_recipe/food/clowncake,
+ /datum/crafting_recipe/food/vanillacake
+ )
+ icon_state = "cooking_learing_sweets"
+ uses = INFINITY
+ remarks = list(
+ "So that is how icing is made!",
+ "Placing fruit on top? How simple...",
+ "Huh layering cake seems harder then this...",
+ "This book smells like candy",
+ "A clown must have made this page, or they forgot to spell check it before printing...",
+ "Wait, a way to cook slime to be safe?",
+ )
+*/
diff --git a/code/game/objects/items/granters/crafting/makeshift_weapons.dm b/code/game/objects/items/granters/crafting/makeshift_weapons.dm
new file mode 100644
index 000000000000..1d0dcb57a410
--- /dev/null
+++ b/code/game/objects/items/granters/crafting/makeshift_weapons.dm
@@ -0,0 +1,25 @@
+/obj/item/book/granter/crafting_recipe/weapons
+ name = "makeshift weapons 101"
+ desc = "A book filled with directions on how to make various weaponry."
+ crafting_recipe_types = list(
+ /datum/crafting_recipe/metal_baseball_bat,
+ /datum/crafting_recipe/lance,
+ /datum/crafting_recipe/knifeboxing,
+ /datum/crafting_recipe/pipebomb,
+ /datum/crafting_recipe/makeshiftpistol,
+ /datum/crafting_recipe/makeshiftmagazine,
+ /datum/crafting_recipe/makeshiftsuppressor,
+ /datum/crafting_recipe/makeshiftcrowbar,
+ /datum/crafting_recipe/makeshiftwrench,
+ /datum/crafting_recipe/makeshiftwirecutters,
+ /datum/crafting_recipe/makeshiftweldingtool,
+ /datum/crafting_recipe/makeshiftmultitool,
+ /datum/crafting_recipe/makeshiftscrewdriver,
+ /datum/crafting_recipe/makeshiftknife,
+ /datum/crafting_recipe/makeshiftpickaxe,
+ /datum/crafting_recipe/makeshiftradio,
+ /datum/crafting_recipe/bola_arrow,
+ /datum/crafting_recipe/flaming_arrow,
+ /datum/crafting_recipe/makeshiftemag
+ )
+ icon_state = "bookCrafting"
diff --git a/code/game/objects/items/granters/crafting/pipegun.dm b/code/game/objects/items/granters/crafting/pipegun.dm
new file mode 100644
index 000000000000..4028066c4836
--- /dev/null
+++ b/code/game/objects/items/granters/crafting/pipegun.dm
@@ -0,0 +1,20 @@
+/*/obj/item/book/granter/crafting_recipe/pipegun_prime
+ name = "diary of a dead assistant"
+ desc = "A battered journal. Looks like he had a pretty rough life."
+ crafting_recipe_types = list(
+ /datum/crafting_recipe/pipegun_prime
+ )
+ icon_state = "book1"
+ remarks = list(
+ "He apparently mastered some lost guncrafting technique.",
+ "Why do I have to go through so many hoops to get this shitty gun?",
+ "That much Grey Bull cannot be healthy...",
+ "Did he drop this into a moisture trap? Yuck.",
+ "Toolboxing techniques, huh? I kinda just want to know how to make the gun.",
+ "What the hell does he mean by 'ancient warrior tradition'?",
+ )
+
+/obj/item/book/granter/crafting_recipe/pipegun_prime/recoil(mob/living/user)
+ to_chat(user, span_warning("The book turns to dust in your hands."))
+ qdel(src)
+*/
diff --git a/code/game/objects/items/granters/crafting/roburgers.dm b/code/game/objects/items/granters/crafting/roburgers.dm
new file mode 100644
index 000000000000..cd062988ed6f
--- /dev/null
+++ b/code/game/objects/items/granters/crafting/roburgers.dm
@@ -0,0 +1,6 @@
+/obj/item/book/granter/crafting_recipe/roburgers
+ name = "roburger crafting recipe"
+ desc = "A book containing knowledge how to make roburgers."
+ crafting_recipe_types = list(/datum/crafting_recipe/food/roburger)
+ icon_state = "bookCrafting"
+ uses = INFINITY
diff --git a/code/game/objects/items/granters/magic/_spell_granter.dm b/code/game/objects/items/granters/magic/_spell_granter.dm
new file mode 100644
index 000000000000..6ca1b877ef73
--- /dev/null
+++ b/code/game/objects/items/granters/magic/_spell_granter.dm
@@ -0,0 +1,93 @@
+/obj/item/book/granter/action/spell
+
+/obj/item/book/granter/action/spell/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_ITEM_MAGICALLY_CHARGED, PROC_REF(on_magic_charge))
+
+/**
+ * Signal proc for [COMSIG_ITEM_MAGICALLY_CHARGED]
+ *
+ * Refreshes uses on our spell granter, or make it quicker to read if it's already infinite use
+ */
+/obj/item/book/granter/action/spell/proc/on_magic_charge(datum/source, datum/action/cooldown/spell/spell, mob/living/caster)
+ SIGNAL_HANDLER
+
+ // What're the odds someone uses 2000 uses of an infinite use book?
+ if(uses >= INFINITY - 2000)
+ to_chat(caster, span_notice("This book is infinite use and can't be recharged, \
+ yet the magic has improved it somehow..."))
+ pages_to_mastery = max(pages_to_mastery - 1, 1)
+ return COMPONENT_ITEM_CHARGED|COMPONENT_ITEM_BURNT_OUT
+
+ if(prob(80))
+ INVOKE_ASYNC(caster, TYPE_PROC_REF(/mob/, dropItemToGround), src, TRUE)
+ INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/, visible_message), span_warning("[src] catches fire and burns to ash!"))
+ new /obj/effect/decal/cleanable/ash(drop_location())
+ qdel(src)
+ return COMPONENT_ITEM_BURNT_OUT
+
+ uses++
+ return COMPONENT_ITEM_CHARGED
+
+/obj/item/book/granter/action/spell/can_learn(mob/living/user)
+ if(!granted_action)
+ CRASH("Someone attempted to learn [type], which did not have an spell set.")
+ if(locate(granted_action) in user.actions)
+ if(is_wizard(user))
+ to_chat(user, span_warning("You're already far more versed in the spell [action_name] \
+ than this flimsy how-to book can provide!"))
+ else
+ to_chat(user, span_warning("You've already know the spell [action_name]!"))
+ return FALSE
+ return TRUE
+
+/obj/item/book/granter/action/spell/on_reading_start(mob/living/user)
+ to_chat(user, span_notice("You start reading about casting [action_name]..."))
+
+/obj/item/book/granter/action/spell/on_reading_finished(mob/living/user)
+ to_chat(user, span_notice("You feel like you've experienced enough to cast [action_name]!"))
+ var/datum/action/cooldown/spell/new_spell = new granted_action(user.mind || user)
+ new_spell.Grant(user)
+ user.log_message("learned the spell [action_name] ([new_spell])", LOG_ATTACK, color = "orange")
+ if(uses <= 0)
+ user.visible_message(span_warning("[src] glows dark for a second!"))
+
+/obj/item/book/granter/action/spell/recoil(mob/living/user)
+ user.visible_message(span_warning("[src] glows in a black light!"))
+
+/// Simple granter that's replaced with a random spell granter on Initialize.
+/obj/item/book/granter/action/spell/random
+ icon_state = "random_book"
+
+/obj/item/book/granter/action/spell/random/Initialize(mapload)
+ . = ..()
+ var/static/list/banned_spells = list(
+ /obj/item/book/granter/action/spell/true_random,
+ ) + typesof(/obj/item/book/granter/action/spell/mime)
+
+ var/real_type = pick(subtypesof(/obj/item/book/granter/action/spell) - banned_spells)
+ new real_type(loc)
+
+ return INITIALIZE_HINT_QDEL
+
+/// A more volatile granter that can potentially have any spell within. Use wisely.
+/obj/item/book/granter/action/spell/true_random
+ icon_state = "random_book"
+ desc = "You feel as if anything could be gained from this book."
+ /// A list of schools we probably shouldn't grab, for various reasons
+ var/static/list/blacklisted_schools = list(SCHOOL_UNSET, SCHOOL_HOLY, SCHOOL_MIME)
+
+/obj/item/book/granter/action/spell/true_random/Initialize(mapload)
+ . = ..()
+
+ var/static/list/spell_options
+ if(!spell_options)
+ spell_options = subtypesof(/datum/action/cooldown/spell)
+ for(var/datum/action/cooldown/spell/spell as anything in spell_options)
+ if(initial(spell.school) in blacklisted_schools)
+ spell_options -= spell
+ if(initial(spell.name) == "Spell") // Abstract types
+ spell_options -= spell
+
+ granted_action = pick(spell_options)
+ action_name = lowertext(initial(granted_action.name))
diff --git a/code/game/objects/items/granters/magic/barnyard.dm b/code/game/objects/items/granters/magic/barnyard.dm
new file mode 100644
index 000000000000..0e94d1313ee7
--- /dev/null
+++ b/code/game/objects/items/granters/magic/barnyard.dm
@@ -0,0 +1,34 @@
+/obj/item/book/granter/action/spell/barnyard
+ granted_action = /datum/action/cooldown/spell/pointed/barnyardcurse
+ action_name = "barnyard"
+ icon_state ="bookhorses"
+ desc = "This book is more horse than your mind has room for."
+ remarks = list(
+ "Moooooooo!",
+ "Moo!",
+ "Moooo!",
+ "NEEIIGGGHHHH!",
+ "NEEEIIIIGHH!",
+ "NEIIIGGHH!",
+ "HAAWWWWW!",
+ "HAAAWWW!",
+ "Oink!",
+ "Squeeeeeeee!",
+ "Oink Oink!",
+ "Ree!!",
+ "Reee!!",
+ "REEE!!",
+ "REEEEE!!",
+ )
+
+/obj/item/book/granter/action/spell/barnyard/recoil(mob/living/user)
+ if(ishuman(user))
+ to_chat(user, "HORSIE HAS RISEN")
+ var/obj/item/clothing/magic_mask = new /obj/item/clothing/mask/horsehead/cursed(user.drop_location())
+ var/mob/living/carbon/human/human_user = user
+ if(!user.dropItemToGround(human_user.wear_mask))
+ qdel(human_user.wear_mask)
+ user.equip_to_slot_if_possible(magic_mask, ITEM_SLOT_MASK, TRUE, TRUE)
+ qdel(src)
+ else
+ to_chat(user,span_notice("I say thee neigh")) //It still lives here
diff --git a/code/game/objects/items/granters/magic/blind.dm b/code/game/objects/items/granters/magic/blind.dm
new file mode 100644
index 000000000000..23b2e9629fcd
--- /dev/null
+++ b/code/game/objects/items/granters/magic/blind.dm
@@ -0,0 +1,19 @@
+/obj/item/book/granter/action/spell/blind
+ granted_action = /datum/action/cooldown/spell/pointed/blind
+ action_name = "blind"
+ icon_state = "bookblind"
+ desc = "This book looks blurry, no matter how you look at it."
+ remarks = list(
+ "Well I can't learn anything if I can't read the damn thing!",
+ "Why would you use a dark font on a dark background...",
+ "Ah, I can't see an Oh, I'm fine...",
+ "I can't see my hand...!",
+ "I'm manually blinking, damn you book...",
+ "I can't read this page, but somehow I feel like I learned something from it...",
+ "Hey, who turned off the lights?",
+ )
+
+/obj/item/book/granter/action/spell/blind/recoil(mob/living/user)
+ . = ..()
+ to_chat(user, span_warning("You go blind!"))
+ user.blind_eyes(10)
\ No newline at end of file
diff --git a/code/game/objects/items/granters/magic/charge.dm b/code/game/objects/items/granters/magic/charge.dm
new file mode 100644
index 000000000000..72ab119aa152
--- /dev/null
+++ b/code/game/objects/items/granters/magic/charge.dm
@@ -0,0 +1,20 @@
+/obj/item/book/granter/action/spell/charge
+ granted_action = /datum/action/cooldown/spell/charge
+ action_name = "charge"
+ icon_state ="bookcharge"
+ desc = "This book is made of 100% postconsumer wizard."
+ remarks = list(
+ "I feel ALIVE!",
+ "I CAN TASTE THE MANA!",
+ "What a RUSH!",
+ "I'm FLYING through these pages!",
+ "THIS GENIUS IS MAKING IT!",
+ "This book is ACTION PAcKED!",
+ "HE'S DONE IT",
+ "LETS GOOOOOOOOOOOO",
+ )
+
+/obj/item/book/granter/action/spell/charge/recoil(mob/living/user)
+ . = ..()
+ to_chat(user,span_warning("[src] suddenly feels very warm!"))
+ empulse(src, 1, 1)
\ No newline at end of file
diff --git a/code/game/objects/items/granters/magic/fireball.dm b/code/game/objects/items/granters/magic/fireball.dm
new file mode 100644
index 000000000000..52674dcd9760
--- /dev/null
+++ b/code/game/objects/items/granters/magic/fireball.dm
@@ -0,0 +1,26 @@
+/obj/item/book/granter/action/spell/fireball
+ granted_action = /datum/action/cooldown/spell/pointed/projectile/fireball
+ action_name = "fireball"
+ icon_state ="bookfireball"
+ desc = "This book feels warm to the touch."
+ remarks = list(
+ "Aim...AIM, FOOL!",
+ "Just catching them on fire won't do...",
+ "Accounting for crosswinds... really?",
+ "I think I just burned my hand...",
+ "Why the dumb stance? It's just a flick of the hand...",
+ "OMEE... ONI... Ugh...",
+ "What's the difference between a fireball and a pyroblast...",
+ )
+
+/obj/item/book/granter/action/spell/fireball/recoil(mob/living/user)
+ . = ..()
+ explosion(
+ user,
+ devastation_range = 1,
+ light_impact_range = 2,
+ flame_range = 2,
+ flash_range = 3,
+ adminlog = FALSE,
+ )
+ qdel(src)
diff --git a/code/game/objects/items/granters/magic/forcewall.dm b/code/game/objects/items/granters/magic/forcewall.dm
new file mode 100644
index 000000000000..bf1228d72618
--- /dev/null
+++ b/code/game/objects/items/granters/magic/forcewall.dm
@@ -0,0 +1,20 @@
+/obj/item/book/granter/action/spell/forcewall
+ granted_action = /datum/action/cooldown/spell/forcewall
+ action_name = "forcewall"
+ icon_state ="bookforcewall"
+ desc = "This book has a dedication to mimes everywhere inside the front cover."
+ remarks = list(
+ "I can go through the wall! Neat.",
+ "Why are there so many mime references...?",
+ "This would cause much grief in a hallway...",
+ "This is some surprisingly strong magic to create a wall nobody can pass through...",
+ "Why the dumb stance? It's just a flick of the hand...",
+ "Why are the pages so hard to turn, is this even paper?",
+ "I can't mo Oh, i'm fine...",
+ )
+
+/obj/item/book/granter/action/spell/forcewall/recoil(mob/living/user)
+ . = ..()
+ to_chat(user, span_warning("You suddenly feel very solid!"))
+ user.Stun(4 SECONDS, ignore_canstun = TRUE)
+ user.petrify(6 SECONDS)
\ No newline at end of file
diff --git a/code/game/objects/items/granters/magic/knock.dm b/code/game/objects/items/granters/magic/knock.dm
new file mode 100644
index 000000000000..115883b4a387
--- /dev/null
+++ b/code/game/objects/items/granters/magic/knock.dm
@@ -0,0 +1,19 @@
+/obj/item/book/granter/action/spell/knock
+ granted_action = /datum/action/cooldown/spell/aoe/knock
+ action_name = "knock"
+ icon_state ="bookknock"
+ desc = "This book is hard to hold closed properly."
+ remarks = list(
+ "Open Sesame!",
+ "So THAT'S the magic password!",
+ "Slow down, book. I still haven't finished this page...",
+ "The book won't stop moving!",
+ "I think this is hurting the spine of the book...",
+ "I can't get to the next page, it's stuck t- I'm good, it just turned to the next page on it's own.",
+ "Yeah, staff of doors does the same thing. Go figure...",
+ )
+
+/obj/item/book/granter/action/spell/knock/recoil(mob/living/user)
+ . = ..()
+ to_chat(user, span_warning("You're knocked down!"))
+ user.Paralyze(4 SECONDS)
\ No newline at end of file
diff --git a/code/game/objects/items/granters/magic/lightning_bolt.dm b/code/game/objects/items/granters/magic/lightning_bolt.dm
new file mode 100644
index 000000000000..3e3cbade46fa
--- /dev/null
+++ b/code/game/objects/items/granters/magic/lightning_bolt.dm
@@ -0,0 +1,16 @@
+/obj/item/book/granter/action/spell/lightningbolt
+ granted_action = /datum/action/cooldown/spell/pointed/projectile/lightningbolt
+ action_name = "lightning bolt"
+ icon_state ="booklightning"
+ desc = "Become like lightning, the rain transformed."
+ remarks = list(
+ "I never quite liked insulated gloves...",
+ "Will this effect my haircut?",
+ "I wonder if I can use this to charge a MODsuit?",
+ "Ouch! That page had static...",
+ "Is cackling optional?",
+ "Just think like a tesla ball...",
+ "UNLIMITED power? Well... maybe if I practice...",
+ "Wait until they're grouped up...",
+ )
+
diff --git a/code/game/objects/items/granters/magic/mime.dm b/code/game/objects/items/granters/magic/mime.dm
new file mode 100644
index 000000000000..b1b66b485f53
--- /dev/null
+++ b/code/game/objects/items/granters/magic/mime.dm
@@ -0,0 +1,28 @@
+/obj/item/book/granter/action/spell/mime
+ name = "Guide to Mimery Vol 0"
+ desc = "The missing entry into the legendary saga. Unfortunately it doesn't teach you anything."
+ icon_state ="bookmime"
+ remarks = list("...")
+
+/obj/item/book/granter/action/spell/mime/attack_self(mob/user)
+ . = ..()
+ if(!.)
+ return
+
+ // Gives the user a vow ability if they don't have one
+ var/datum/action/cooldown/spell/vow_of_silence/vow = locate() in user.actions
+ if(!vow && user.mind)
+ vow = new(user.mind)
+ vow.Grant(user)
+
+/obj/item/book/granter/action/spell/mime/mimery_blockade
+ granted_action = /datum/action/cooldown/spell/forcewall/mime
+ action_name = "Invisible Blockade"
+ name = "Guide to Advanced Mimery Vol 1"
+ desc = "The pages don't make any sound when turned."
+
+/obj/item/book/granter/action/spell/mime/mimery_guns
+ granted_action = /datum/action/cooldown/spell/pointed/projectile/finger_guns
+ action_name = "Finger Guns"
+ name = "Guide to Advanced Mimery Vol 2"
+ desc = "There aren't any words written..."
\ No newline at end of file
diff --git a/code/game/objects/items/granters/magic/mindswap.dm b/code/game/objects/items/granters/magic/mindswap.dm
new file mode 100644
index 000000000000..39f85bc67335
--- /dev/null
+++ b/code/game/objects/items/granters/magic/mindswap.dm
@@ -0,0 +1,57 @@
+/obj/item/book/granter/action/spell/mindswap
+ granted_action = /datum/action/cooldown/spell/pointed/mind_transfer
+ action_name = "mindswap"
+ icon_state = "bookmindswap"
+ desc = "This book's cover is pristine, though its pages look ragged and torn."
+ remarks = list(
+ "If you mindswap from a mouse, they will be helpless when you recover...",
+ "Wait, where am I...?",
+ "This book is giving me a horrible headache...",
+ "This page is blank, but I feel words popping into my head...",
+ "GYNU... GYRO... Ugh...",
+ "The voices in my head need to stop, I'm trying to read here...",
+ "I don't think anyone will be happy when I cast this spell...",
+ )
+ /// Mob used in book recoils to store an identity for mindswaps
+ var/datum/weakref/stored_swap_ref
+
+/obj/item/book/granter/action/spell/mindswap/on_reading_finished()
+ . = ..()
+ visible_message(span_notice("[src] begins to shake and shift."))
+ action_name = pick(
+ "fireball",
+ "smoke",
+ "blind",
+ "forcewall",
+ "knock",
+ "barnyard",
+ "charge",
+ )
+ icon_state = "book[action_name]"
+ name = "spellbook of [action_name]"
+
+/obj/item/book/granter/action/spell/mindswap/recoil(mob/living/user)
+ . = ..()
+ var/mob/living/real_stored_swap = stored_swap_ref?.resolve()
+ if(QDELETED(real_stored_swap))
+ stored_swap_ref = WEAKREF(user)
+ to_chat(user, span_warning("For a moment you feel like you don't even know who you are anymore."))
+ return
+ if(real_stored_swap.stat == DEAD)
+ stored_swap_ref = null
+ return
+ if(real_stored_swap == user)
+ to_chat(user, span_notice("You stare at the book some more, but there doesn't seem to be anything else to learn..."))
+ return
+
+ var/datum/action/cooldown/spell/pointed/mind_transfer/swapper = new(src)
+
+ if(swapper.swap_minds(user, real_stored_swap))
+ to_chat(user, span_warning("You're suddenly somewhere else... and someone else?!"))
+ to_chat(real_stored_swap, span_warning("Suddenly you're staring at [src] again... where are you, who are you?!"))
+
+ else
+ // if the mind_transfer failed to transfer mobs (likely due to the target being catatonic).
+ user.visible_message(span_warning("[src] fizzles slightly as it stops glowing!"))
+
+ stored_swap_ref = null
\ No newline at end of file
diff --git a/code/game/objects/items/granters/magic/sacred_flame.dm b/code/game/objects/items/granters/magic/sacred_flame.dm
new file mode 100644
index 000000000000..e416c6e6c990
--- /dev/null
+++ b/code/game/objects/items/granters/magic/sacred_flame.dm
@@ -0,0 +1,14 @@
+/obj/item/book/granter/action/spell/sacredflame
+ granted_action = /datum/action/cooldown/spell/aoe/sacred_flame
+ action_name = "sacred flame"
+ icon_state = "booksacredflame"
+ desc = "Become one with the flames that burn within... and invite others to do so as well."
+ remarks = list(
+ "Well, it's one way to stop an attacker...",
+ "I'm gonna need some good gear to stop myself from burning to death...",
+ "Keep a fire extinguisher handy, got it...",
+ "I think I just burned my hand...",
+ "Apply flame directly to chest for proper ignition...",
+ "No pain, no gain...",
+ "One with the flame...",
+ )
\ No newline at end of file
diff --git a/code/game/objects/items/granters/magic/smoke.dm b/code/game/objects/items/granters/magic/smoke.dm
new file mode 100644
index 000000000000..e94d0ec1e2ba
--- /dev/null
+++ b/code/game/objects/items/granters/magic/smoke.dm
@@ -0,0 +1,26 @@
+/obj/item/book/granter/action/spell/smoke
+ granted_action = /datum/action/cooldown/spell/smoke
+ action_name = "smoke"
+ icon_state ="booksmoke"
+ desc = "This book is overflowing with the dank arts."
+ remarks = list(
+ "Smoke Bomb! Heh...",
+ "Smoke bomb would do just fine too...",
+ "Wait, there's a machine that does the same thing in chemistry?",
+ "This book smells awful...",
+ "Why all these weed jokes? Just tell me how to cast it...",
+ "Wind will ruin the whole spell, good thing we're in space... Right?",
+ "So this is how the spider clan does it...",
+ )
+
+/obj/item/book/granter/action/spell/smoke/recoil(mob/living/user)
+ . = ..()
+ to_chat(user,span_warning("Your stomach rumbles..."))
+ if(user.nutrition)
+ user.set_nutrition(200)
+ if(user.nutrition <= 0)
+ user.set_nutrition(0)
+
+// Chaplain's smoke book
+/obj/item/book/granter/action/spell/smoke/lesser
+ granted_action = /datum/action/cooldown/spell/smoke/lesser
\ No newline at end of file
diff --git a/code/game/objects/items/granters/magic/summon_item.dm b/code/game/objects/items/granters/magic/summon_item.dm
new file mode 100644
index 000000000000..ad0104498702
--- /dev/null
+++ b/code/game/objects/items/granters/magic/summon_item.dm
@@ -0,0 +1,19 @@
+/obj/item/book/granter/action/spell/summonitem
+ granted_action = /datum/action/cooldown/spell/summonitem
+ action_name = "instant summons"
+ icon_state ="booksummons"
+ desc = "This book is bright and garish, very hard to miss."
+ remarks = list(
+ "I can't look away from the book!",
+ "The words seem to pop around the page...",
+ "I just need to focus on one item...",
+ "Make sure to have a good grip on it when casting...",
+ "Slow down, book. I still haven't finished this page...",
+ "Sounds pretty great with some other magical artifacts...",
+ "Magicians must love this one.",
+ )
+
+/obj/item/book/granter/action/spell/summonitem/recoil(mob/living/user)
+ . = ..()
+ to_chat(user,span_warning("[src] suddenly vanishes!"))
+ qdel(src)
\ No newline at end of file
diff --git a/code/game/objects/items/granters/martial_arts/_martial_arts.dm b/code/game/objects/items/granters/martial_arts/_martial_arts.dm
new file mode 100644
index 000000000000..a73c168794f5
--- /dev/null
+++ b/code/game/objects/items/granters/martial_arts/_martial_arts.dm
@@ -0,0 +1,24 @@
+/obj/item/book/granter/martial
+ /// The martial arts type we give
+ var/datum/martial_art/martial
+ /// The name of the martial arts, formatted in a more text-friendly way.
+ var/martial_name = ""
+ /// The text given to the user when they learn the martial arts
+ var/greet = ""
+
+/obj/item/book/granter/martial/can_learn(mob/user)
+ if(!martial)
+ CRASH("Someone attempted to learn [type], which did not have a martial arts set.")
+ if(user.mind.has_martialart(initial(martial.id)))
+ to_chat(user, span_warning("You already know [martial_name]!"))
+ return FALSE
+ return TRUE
+
+/obj/item/book/granter/martial/on_reading_start(mob/user)
+ to_chat(user, span_notice("You start reading about [martial_name]..."))
+
+/obj/item/book/granter/martial/on_reading_finished(mob/user)
+ to_chat(user, "[greet]")
+ var/datum/martial_art/martial_to_learn = new martial()
+ martial_to_learn.teach(user)
+ user.log_message("learned the martial art [martial_name] ([martial_to_learn])", LOG_ATTACK, color = "orange")
\ No newline at end of file
diff --git a/code/game/objects/items/granters/martial_arts/cqc.dm b/code/game/objects/items/granters/martial_arts/cqc.dm
new file mode 100644
index 000000000000..74479cde4fe6
--- /dev/null
+++ b/code/game/objects/items/granters/martial_arts/cqc.dm
@@ -0,0 +1,29 @@
+/obj/item/book/granter/martial/cqc
+ martial = /datum/martial_art/cqc
+ name = "old manual"
+ martial_name = "close quarters combat"
+ desc = "A small, black manual. There are drawn instructions of tactical hand-to-hand combat."
+ greet = "You've mastered the basics of CQC."
+ icon_state = "cqcmanual"
+ remarks = list(
+ "Kick... Slam...",
+ "Lock... Kick...",
+ "Strike their abdomen, neck and back for critical damage...",
+ "Slam... Lock...",
+ "I could probably combine this with some other martial arts!",
+ "Words that kill...",
+ "The last and final moment is yours...",
+ )
+
+/obj/item/book/granter/martial/cqc/on_reading_finished(mob/living/carbon/user)
+ . = ..()
+ if(!uses)
+ to_chat(user, span_warning("[src] beeps ominously..."))
+
+/obj/item/book/granter/martial/cqc/recoil(mob/living/user)
+ to_chat(user, span_warning("[src] explodes!"))
+ playsound(src,'sound/effects/explosion1.ogg',40,TRUE)
+ user.flash_act(1, 1)
+ user.adjustBruteLoss(6)
+ user.adjustFireLoss(6)
+ qdel(src)
diff --git a/code/game/objects/items/granters/martial_arts/plasma_fist.dm b/code/game/objects/items/granters/martial_arts/plasma_fist.dm
new file mode 100644
index 000000000000..8b9b1adb4b82
--- /dev/null
+++ b/code/game/objects/items/granters/martial_arts/plasma_fist.dm
@@ -0,0 +1,36 @@
+/obj/item/book/granter/martial/plasma_fist
+ martial = /datum/martial_art/plasma_fist
+ name = "frayed scroll"
+ martial_name = "plasma fist"
+ desc = "An aged and frayed scrap of paper written in shifting runes. There are hand-drawn illustrations of pugilism."
+ greet = "You have learned the ancient martial art of Plasma Fist. Your combos are extremely hard to pull off, but include some of the most deadly moves ever seen including \
+ the plasma fist, which when pulled off will make someone violently explode."
+ icon = 'icons/obj/wizard.dmi'
+ icon_state ="scroll2"
+ remarks = list(
+ "Balance...",
+ "Power...",
+ "Control...",
+ "Mastery...",
+ "Vigilance...",
+ "Skill...",
+ )
+
+/obj/item/book/granter/martial/plasma_fist/on_reading_finished(mob/living/carbon/user)
+ . = ..()
+ update_icon()
+
+/obj/item/book/granter/martial/plasma_fist/update_icon()
+ . = ..()
+ if(uses <= 0)
+ name = "empty scroll"
+ desc = "It's completely blank."
+ icon_state = "blankscroll"
+ else
+ name = initial(name)
+ desc = initial(desc)
+ icon_state = initial(icon_state)
+
+/*/obj/item/book/granter/martial/plasma_fist/nobomb
+ martial = /datum/martial_art/plasma_fist/nobomb
+*/
diff --git a/code/game/objects/items/granters/martial_arts/psychotic_brawl.dm b/code/game/objects/items/granters/martial_arts/psychotic_brawl.dm
new file mode 100644
index 000000000000..793d01d0497b
--- /dev/null
+++ b/code/game/objects/items/granters/martial_arts/psychotic_brawl.dm
@@ -0,0 +1,24 @@
+/obj/item/book/granter/martial/psychotic_brawling
+ martial = /datum/martial_art/psychotic_brawling
+ name = "blood-stained paper"
+ martial_name = "psychotic brawling"
+ desc = "A piece of blood-stained paper that emanates pure rage. Just holding it makes you want to punch someone."
+ greet = "You have learned the tried and true art of Psychotic Brawling. \
+ You will be unable to disarm or grab, but your punches have a chance to do serious damage."
+ icon = 'yogstation/icons/obj/bureaucracy.dmi'
+ icon_state = "paper_talisman"
+ remarks = list(
+ "Just keep punching...",
+ "Let go of your inhibitions...",
+ "Methamphetamine...",
+ "Embrace Space Florida...",
+ "Become too angry to die..."
+ )
+
+/obj/item/book/granter/martial/psychotic_brawling/on_reading_finished(mob/living/carbon/user)
+ . = ..()
+ if(!uses)
+ to_chat(user, span_notice("All of the blood on the paper seems to have vanished."))
+ user.dropItemToGround(src)
+ qdel(src)
+ user.put_in_hands(new /obj/item/paper)
diff --git a/code/game/objects/items/granters/martial_arts/racial.dm b/code/game/objects/items/granters/martial_arts/racial.dm
new file mode 100644
index 000000000000..cb4ab0fca847
--- /dev/null
+++ b/code/game/objects/items/granters/martial_arts/racial.dm
@@ -0,0 +1,148 @@
+/obj/item/book/granter/martial/flyingfang
+ martial = /datum/martial_art/flyingfang
+ name = "strange tablet"
+ martial_name = "Flying Fang"
+ desc = "A tablet with strange pictograms that appear to detail some kind of fighting technique."
+ force = 10
+ greet = "You have learned the ancient martial art of Flying Fang! Your unarmed attacks have become somewhat more effective, \
+ and you are more resistant to damage and stun-based weaponry. However, you are also unable to use any ranged weaponry or armor. You can learn more about your newfound art by using the Recall Teachings verb in the Flying Fang tab."
+ icon = 'icons/obj/library.dmi'
+ icon_state = "stone_tablet"
+ remarks = list("Feasting on the insides of your enemies...", "Some of these techniques look a bit dizzying...", "Not like I need armor anyways...", "Don't get shot, whatever that means...")
+
+/obj/item/book/granter/martial/flyingfang/can_learn(mob/user)
+ if(!islizard(user))
+ to_chat(user, span_warning("You can't tell if this is some poorly written fanfiction or an actual guide to something."))
+ return FALSE
+ return ..()
+
+/obj/item/book/granter/martial/flyingfang/on_reading_finished(mob/living/carbon/user)
+ ..()
+ if(!uses)
+ desc = "It's completely blank."
+ name = "blank tablet"
+ icon_state = "stone_tablet_blank"
+
+/obj/item/book/granter/martial/preternis_stealth
+ martial = /datum/martial_art/stealth
+ name = "strange electronic board"
+ martial_name = "Stealth"
+ desc = "A strange electronic board, containing some sort of software."
+ greet = "You have uploaded some combat modules into yourself. Your combos will now have special effects on your enemies, and mostly are not obvious to other people. \
+ You can check what combos can you do, and their effect by using Refresh Data verb in Combat Modules tab."
+ icon = 'icons/obj/module.dmi'
+ icon_state = "cyborg_upgrade"
+ remarks = list("Processing data...")
+
+/obj/item/book/granter/martial/preternis_stealth/can_learn(mob/user)
+ if(!ispreternis(user))
+ to_chat(user, span_warning("You don't understand what to do with this strange electronic device."))
+ return FALSE
+ return ..()
+
+/obj/item/book/granter/martial/preternis_stealth/on_reading_finished(mob/living/carbon/user)
+ ..()
+ if(!uses)
+ desc = "It looks like it doesn't contain any data no more."
+
+/obj/item/book/granter/martial/garden_warfare
+ martial = /datum/martial_art/gardern_warfare
+ name = "mysterious scroll"
+ martial_name = "Garden Warfare"
+ desc = "A scroll, filled with a tone of text. Looks like it says something about combat and... plants?"
+ greet = "You know the martial art of Garden Warfare! Now you control your body better, then other phytosians do, allowing you to extend vines from your body and impale people with splinters. \
+ You can check what combos can you do, and their effect by using Remember the basics verb in Garden Warfare tab."
+ icon = 'icons/obj/wizard.dmi'
+ icon_state = "scroll2"
+ remarks = list("I didn't know that my body grows sprinklers...", "I am able to snatch people with vines? Interesting.", "Wow, strangling people is brutal.") ///Kill me please for this cringe
+
+/obj/item/book/granter/martial/garden_warfare/can_learn(mob/user)
+ if(!ispodperson(user))
+ to_chat(user, span_warning("You see that this scroll says something about natural abilitites of podpeople, but, unfortunately, you are not one of them."))
+ return FALSE
+ return ..()
+
+/obj/item/book/granter/martial/garden_warfare/on_reading_finished(mob/living/carbon/user)
+ ..()
+ if(!uses)
+ desc = "It's completely blank."
+
+/obj/item/book/granter/martial/explosive_fist
+ martial = /datum/martial_art/explosive_fist
+ name = "burnt scroll"
+ martial_name = "Explosive Fist"
+ desc = "A burnt scroll, that glorifies plasmamen, and also says a lot things of explosions."
+ greet = "You know the martial art of Explosive Fist. Now your attacks deal brute and burn damage, while your combos are able to set people on fire, explode them, or all at once. \
+ You can check what combos can you do, and their effect by using Remember the basics verb in Explosive Fist tab."
+ icon = 'icons/obj/wizard.dmi'
+ icon_state = "scroll2"
+ remarks = list("Set them on fire...", "Show the punny humans who is here the supreme race...", "Make them burn...", "Explosion are cool!")
+
+/obj/item/book/granter/martial/explosive_fist/can_learn(mob/living/user)
+ if(!isplasmaman(user))
+ to_chat(user, span_warning("You burn your hand slightly on the scroll, better not to mess with it."))
+ user.apply_damage(5, BURN, rand(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
+ return FALSE
+ return ..()
+
+/obj/item/book/granter/martial/explosive_fist/on_reading_finished(mob/living/carbon/user)
+ ..()
+ if(!uses)
+ desc = "It's completely blank."
+
+/obj/item/book/granter/martial/ultra_violence
+ martial = /datum/martial_art/ultra_violence
+ name = "Version one upgrade module"
+ martial_name = "Ultra Violence"
+ desc = "A module full of forbidden techniques from a horrific event long since passed, or perhaps yet to come."
+ greet = span_sciradio("You have installed how to perform Ultra Violence! You are able to redirect electromagnetic pulses, \
+ blood heals you, and you CANNOT BE STOPPED. You can mentally practice by using Cyber Grind in the Ultra Violence tab.")
+ icon = 'icons/obj/module.dmi'
+ icon_state = "cyborg_upgrade"
+ remarks = list("MANKIND IS DEAD.", "BLOOD IS FUEL.", "HELL IS FULL.")
+
+/obj/item/book/granter/martial/ultra_violence/can_learn(mob/user)
+ if(!isipc(user))
+ to_chat(user, span_warning("A nice looking piece of scrap, would make a fine trade offer."))
+ return FALSE
+ return ..()
+
+/obj/item/book/granter/martial/ultra_violence/on_reading_finished(mob/living/carbon/user)
+ ..()
+ if(!uses)
+ desc = "It's a damaged upgrade module."
+ name = "damaged board"
+
+// I did not include mushpunch's grant, it is not a book and the item does it just fine.
+
+/obj/item/book/granter/martial/worldshaker
+ martial = /datum/martial_art/worldshaker
+ name = "prototype worldshaker compound"
+ martial_name = "Worldshaker"
+ desc = "A foul concoction made by reverse engineering chemicals compounds found in an ancient Vxtrin military outpost."
+ greet = span_sciradio("You feel weirdly good, good enough to shake the world to it's very core. \
+ Your plates feel like they are growing past their normal limits. The protection will come in handy, but it will eventually slow you down.\
+ You can think about all the things you are now capable of by using the Worldshaker tab.")
+ icon = 'icons/obj/drinks.dmi'
+ icon_state = "flaming_moe"
+ remarks = list(
+ "Is... it bubbling?",
+ "What's that gross residue on the sides of the vial?",
+ "Am I really considering drinking this?",
+ "I'm pretty sure I just saw a dead fly dissolve in it.",
+ "This is temporary, right?",
+ "I sure hope someone's tested this.")
+ book_sounds = list('sound/items/drink.ogg') //it's a drink, not a book
+
+/obj/item/book/granter/martial/ultra_violence/can_learn(mob/user)
+ if(!ispreternis(user))
+ to_chat(user, span_warning("There is no way in hell i'm drinking this."))
+ return FALSE
+ return ..()
+
+/obj/item/book/granter/martial/ultra_violence/on_reading_finished(mob/living/carbon/user)
+ ..()
+ if(!uses)
+ var/obj/item/reagent_containers/glass/bottle/vial/empty = new(get_turf(user))
+ user.put_in_active_hand(empty)
+ qdel(src)
diff --git a/code/game/objects/items/granters/martial_arts/sleeping_carp.dm b/code/game/objects/items/granters/martial_arts/sleeping_carp.dm
new file mode 100644
index 000000000000..a6e2eed79f23
--- /dev/null
+++ b/code/game/objects/items/granters/martial_arts/sleeping_carp.dm
@@ -0,0 +1,35 @@
+/obj/item/book/granter/martial/carp
+ martial = /datum/martial_art/the_sleeping_carp
+ name = "mysterious scroll"
+ martial_name = "sleeping carp"
+ desc = "A scroll filled with strange markings. It seems to be drawings of some sort of martial art."
+ greet = "You have learned the ancient martial art of the Sleeping Carp! Your hand-to-hand combat has become much more effective, and you are now able to deflect any projectiles \
+ directed toward you while in Throw Mode. Your body has also hardened itself, granting extra protection against lasting wounds that would otherwise mount during extended combat. \
+ However, you are also unable to use any ranged weaponry. You can learn more about your newfound art by using the Recall Teachings verb in the Sleeping Carp tab."
+ icon = 'icons/obj/wizard.dmi'
+ icon_state = "scroll2"
+ item_state = "scroll"
+ remarks = list(
+ "Wait, a high protein diet is really all it takes to become stabproof...?",
+ "Overwhelming force, immovable object...",
+ "Focus... And you'll be able to incapacitate any foe in seconds...",
+ "I must pierce armor for maximum damage...",
+ "I don't think this would combine with other martial arts...",
+ "Become one with the carp...",
+ "Glub...",
+ )
+
+/obj/item/book/granter/martial/carp/on_reading_finished(mob/living/carbon/user)
+ . = ..()
+ update_icon()
+
+/obj/item/book/granter/martial/carp/update_icon(updates)
+ . = ..()
+ if(!uses)
+ name = "empty scroll"
+ desc = "It's completely blank."
+ icon_state = "blankscroll"
+ else
+ name = initial(name)
+ desc = initial(desc)
+ icon_state = initial(icon_state)
diff --git a/code/game/objects/items/granters/mech_piloting.dm b/code/game/objects/items/granters/mech_piloting.dm
new file mode 100644
index 000000000000..53ea00f8badd
--- /dev/null
+++ b/code/game/objects/items/granters/mech_piloting.dm
@@ -0,0 +1,18 @@
+//for upstart mech pilots to move faster
+/obj/item/book/granter/mechpiloting
+ name = "Mech Piloting for Dummies"
+ desc = "A step-by-step guide on how to effectively pilot a mech, written in such a way that even a clown could understand."
+ remarks = list(
+ "Hmm, press forward to go forwards...",
+ "Avoid getting hit to reduce damage...",
+ "Welding to repair..?",
+ "Make sure to turn it on...",
+ "EMP bad...",
+ "I need to turn internals on?",
+ "What's a gun ham?"
+ )
+
+/obj/item/book/granter/mechpiloting/on_reading_finished(mob/user)
+ . = ..()
+ user.AddComponent(/datum/component/mech_pilot, 0.8)
+ on_reading_finished(user)
diff --git a/code/game/objects/items/granters/origami.dm b/code/game/objects/items/granters/origami.dm
new file mode 100644
index 000000000000..0b7d6d926157
--- /dev/null
+++ b/code/game/objects/items/granters/origami.dm
@@ -0,0 +1,35 @@
+/obj/item/book/granter/action/origami
+ granted_action = /datum/action/innate/origami
+ name = "The Art of Origami"
+ desc = "A meticulously in-depth manual explaining the art of paper folding."
+ icon_state = "origamibook"
+ action_name = "origami"
+ remarks = list(
+ "Dead-stick stability...",
+ "Symmetry seems to play a rather large factor...",
+ "Accounting for crosswinds... really?",
+ "Drag coefficients of various paper types...",
+ "Thrust to weight ratios?",
+ "Positive dihedral angle?",
+ "Center of gravity forward of the center of lift...",
+ )
+
+/datum/action/innate/origami
+ name = "Origami Folding"
+ desc = "Toggles your ability to fold and catch robust paper airplanes."
+ button_icon_state = "origami_off"
+ check_flags = NONE
+
+/datum/action/innate/origami/Activate()
+ to_chat(owner, span_notice("You will now fold origami planes."))
+ active = TRUE
+ build_all_button_icons(UPDATE_BUTTON_ICON)
+
+/datum/action/innate/origami/Deactivate()
+ to_chat(owner, span_notice("You will no longer fold origami planes."))
+ active = FALSE
+ build_all_button_icons(UPDATE_BUTTON_ICON)
+
+/datum/action/innate/origami/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force)
+ button_icon_state = "origami_[active ? "on":"off"]"
+ return ..()
diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm
index abae3bc0c53e..c5084d0cf29e 100644
--- a/code/game/objects/items/handcuffs.dm
+++ b/code/game/objects/items/handcuffs.dm
@@ -11,7 +11,7 @@
if(iscarbon(loc))
var/mob/living/carbon/M = loc
if(M.handcuffed == src)
- M.handcuffed = null
+ M.set_handcuffed(null)
M.update_handcuffed()
if(M.buckled && M.buckled.buckle_requires_restraints)
M.buckled.unbuckle_mob(M)
@@ -104,7 +104,7 @@
cuffs = new type()
cuffs.forceMove(target)
- target.handcuffed = cuffs
+ target.set_handcuffed(cuffs)
target.update_handcuffed()
if(trashtype && !dispense)
diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm
index 8ca7e059941a..1481808a3ddc 100644
--- a/code/game/objects/items/holy_weapons.dm
+++ b/code/game/objects/items/holy_weapons.dm
@@ -673,6 +673,54 @@
menutab = MENU_CLOTHING
additional_desc = "This gaudy hat has surprisingly good weight distribution, you could probably throw it very effectively."
+/obj/item/nullrod/tribal_knife/Initialize(mapload)
+ . = ..()
+ START_PROCESSING(SSobj, src)
+ AddComponent(/datum/component/butchering, 50, 100)
+
+/obj/item/nullrod/tribal_knife/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ . = ..()
+
+/obj/item/nullrod/tribal_knife/process()
+ slowdown = rand(-2, 2)
+
+
+/obj/item/nullrod/pitchfork
+ icon = 'icons/obj/weapons/spears.dmi'
+ icon_state = "pitchfork0"
+ lefthand_file = 'icons/mob/inhands/weapons/polearms_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
+ name = "unholy pitchfork"
+ w_class = WEIGHT_CLASS_NORMAL
+ desc = "Holding this makes you look absolutely devilish."
+ attack_verb = list("poked", "impaled", "pierced", "jabbed")
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ sharpness = SHARP_POINTY
+
+/obj/item/nullrod/egyptian
+ name = "egyptian staff"
+ desc = "A tutorial in mummification is carved into the staff. You could probably craft the wraps if you had some cloth."
+ icon = 'icons/obj/guns/magic.dmi'
+ icon_state = "pharoah_sceptre"
+ item_state = "pharoah_sceptre"
+ lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
+ w_class = WEIGHT_CLASS_NORMAL
+ attack_verb = list("bashes", "smacks", "whacks")
+
+/obj/item/nullrod/servoskull/equipped(mob/living/carbon/human/user, slot)
+ ..()
+ if(hud_type && slot == SLOT_GLASSES)
+ var/datum/atom_hud/H = GLOB.huds[hud_type]
+ H.show_to(user)
+
+/obj/item/nullrod/servoskull/dropped(mob/living/carbon/human/user)
+ ..()
+ if(hud_type && istype(user) && user.glasses == src)
+ var/datum/atom_hud/H = GLOB.huds[hud_type]
+ H.hide_from(user)
+
/obj/item/nullrod/servoskull
name = "servitor skull"
desc = "Even in death, I still serve"
@@ -694,17 +742,17 @@
if(hud_type && slot == SLOT_NECK)
to_chat(user, "Sensory augmentation initiated")
var/datum/atom_hud/H = GLOB.huds[hud_type]
- H.add_hud_to(user)
+ H.show_to(user)
var/datum/atom_hud/H2 = GLOB.huds[hud_type2]
- H2.add_hud_to(user)
+ H2.show_to(user)
/obj/item/nullrod/servoskull/dropped(mob/living/carbon/human/user)
..()
if(hud_type && istype(user) && user.wear_neck == src)
var/datum/atom_hud/H = GLOB.huds[hud_type]
- H.remove_hud_from(user)
+ H.hide_from(user)
var/datum/atom_hud/H2 = GLOB.huds[hud_type2]
- H2.remove_hud_from(user)
+ H2.hide_from(user)
/obj/item/nullrod/hermes
name = "fairy boots"
@@ -998,7 +1046,7 @@ it also swaps back if it gets thrown into the chaplain, but the chaplain catches
var/mob/living/simple_animal/shade/soul //when they're just a blade (stored inside the blade at all times)
var/mob/living/simple_animal/nullrod/blade //when they're flying around (blade stored inside them (soul is inside that blade))
var/mob/living/owner //the person with the recall spell
- var/obj/effect/proc_holder/spell/targeted/recallnullrod/summon //the recall spell in question
+ var/datum/action/cooldown/spell/recall_nullrod/summon //the recall spell in question
menutab = MENU_MISC
additional_desc = "You feel an unwoken presence in this one."
@@ -1032,9 +1080,9 @@ it also swaps back if it gets thrown into the chaplain, but the chaplain catches
soul.fully_replace_character_name(null, "The spirit of [input]")
to_chat(owner, "You feel the spirit within the blade stir and waken.")
- summon = new /obj/effect/proc_holder/spell/targeted/recallnullrod
+ summon = new(owner)
summon.sword = src
- owner.AddSpell(summon)
+ summon.Grant(owner)
else
to_chat(user, "The blade is dormant. Maybe you can try again later.")
possessed = FALSE
@@ -1044,7 +1092,7 @@ it also swaps back if it gets thrown into the chaplain, but the chaplain catches
if(soul)
if(owner && summon)
to_chat(owner, "You feel weakened as your blade fades from this world.")
- owner.RemoveSpell(summon)
+ summon.Remove(owner)
to_chat(soul, "You were destroyed!")
qdel(soul)
return ..()
@@ -1071,33 +1119,28 @@ it also swaps back if it gets thrown into the chaplain, but the chaplain catches
else
. = ..()
-/obj/effect/proc_holder/spell/targeted/recallnullrod
+/datum/action/cooldown/spell/recall_nullrod
name = "Sword Recall"
desc = "Pulls your possessed sword back to you."
- school = "transmutation"
panel = "Chaplain"
- charge_max = 10 SECONDS
- clothes_req = FALSE
- antimagic_allowed = TRUE
+ button_icon = 'icons/mob/actions/actions_spells.dmi'
+ button_icon_state = "swordrecall"
+
+ school = SCHOOL_CONJURATION
invocation = "COME"
- invocation_type = "shout"
- range = -1
- level_max = 0 //cannot be improved
- cooldown_min = 10 SECONDS
- include_user = TRUE
+ invocation_type = INVOCATION_SHOUT
+ cooldown_time = 10 SECONDS
+ spell_requirements = NONE
var/obj/item/nullrod/talking/sword
- action_icon = 'icons/mob/actions/actions_spells.dmi'
- action_icon_state = "swordrecall"
-/obj/effect/proc_holder/spell/targeted/recallnullrod/cast_check(skipcharge = 0, mob/user = usr)
- if(sword.loc == user)
- to_chat(user, span_notice("[sword] is already in your hand"))
- revert_cast()
+/datum/action/cooldown/spell/recall_nullrod/before_cast(atom/cast_on)
+ if(sword.loc == cast_on)
+ to_chat(cast_on, span_notice("[sword] is already in your hand"))
return FALSE
return ..()
-
-/obj/effect/proc_holder/spell/targeted/recallnullrod/cast(list/targets, mob/user)
+
+/datum/action/cooldown/spell/recall_nullrod/cast(mob/living/carbon/user)
if(sword)
if(sword.walking)
sword.blade.throw_at(user, 20, 3) //remember, sword is the item, blade is the mob
@@ -1115,7 +1158,7 @@ it also swaps back if it gets thrown into the chaplain, but the chaplain catches
real_name = "Shade"
desc = "A bound spirit."
gender = PLURAL
- icon = 'icons/mob/mob.dmi'
+ icon = 'icons/mob/nonhuman-player/holy.dmi'
icon_state = "talking_sword"
icon_living = "talking_sword"
mob_biotypes = list(MOB_INORGANIC, MOB_SPIRIT)
@@ -1142,13 +1185,13 @@ it also swaps back if it gets thrown into the chaplain, but the chaplain catches
movement_type = FLYING
initial_language_holder = /datum/language_holder/universal
var/obj/item/nullrod/talking/sword //the sword they're part of
- var/obj/effect/proc_holder/spell/targeted/nullroddrop/button //suicide button so they can return to being an item if need be
+ var/datum/action/cooldown/spell/nullrod_drop/button //suicide button so they can return to being an item if need be
/mob/living/simple_animal/nullrod/Initialize()
. = ..()
AddComponent(/datum/component/anti_magic, TRUE, TRUE, FALSE, null, null, FALSE)
- button = new /obj/effect/proc_holder/spell/targeted/nullroddrop
- AddSpell(button)
+ button = new(src)
+ button.Grant(src)
/mob/living/simple_animal/nullrod/death()
if(sword)
@@ -1159,52 +1202,48 @@ it also swaps back if it gets thrown into the chaplain, but the chaplain catches
qdel(src)
/mob/living/simple_animal/nullrod/canSuicide()
- return 0 //you're a sword, you can't suicide
+ return FALSE //you're a sword, you can't suicide
/mob/living/simple_animal/nullrod/attack_hand(mob/living/carbon/human/M)
- if(sword?.owner && M == sword.owner)//let the chaplain pick it up in one hit
- sword.owner.put_in_active_hand(sword)
+ if(!sword.owner || M != sword.owner)//let the chaplain pick it up in one hit
+ return ..()
+ sword.owner.put_in_active_hand(sword)
+ mind.transfer_to(sword.soul)
+ sword.walking = FALSE
+ visible_message("[sword.owner] grabs [src] by the hilt.")
+ qdel(src)
+
+/mob/living/simple_animal/nullrod/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+ if(!isliving(hit_atom))
+ return ..()
+ var/mob/living/target = hit_atom
+ if(sword?.owner && target == sword.owner)
+ var/caught = sword.owner.put_in_hands(sword)
mind.transfer_to(sword.soul)
sword.walking = FALSE
- visible_message("[sword.owner] grabs [src] by the hilt.")
qdel(src)
- else
- ..()
-
-/mob/living/simple_animal/nullrod/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
- if(isliving(hit_atom))
- var/mob/living/target = hit_atom
- if(sword?.owner && target == sword.owner)
- var/caught = sword.owner.put_in_hands(sword)
- mind.transfer_to(sword.soul)
- sword.walking = FALSE
- qdel(src)
- if(caught)
- visible_message("[sword.owner] catches the flying blade out of the air!")
- else
- playsound(target, 'sound/weapons/rapierhit.ogg', 30, 1, -1)
- sword.owner.take_overall_damage(5)
- visible_message("The flying blade smacks [sword.owner] in the face as [sword.owner.p_they()] try to catch it with [sword.owner.p_their()] hands full!")
- else
- ..()
+ if(caught)
+ visible_message("[sword.owner] catches the flying blade out of the air!")
+ else
+ playsound(target, 'sound/weapons/rapierhit.ogg', 30, 1, -1)
+ sword.owner.take_overall_damage(5)
+ visible_message("The flying blade smacks [sword.owner] in the face as [sword.owner.p_they()] try to catch it with [sword.owner.p_their()] hands full!")
-/obj/effect/proc_holder/spell/targeted/nullroddrop
+/datum/action/cooldown/spell/nullrod_drop
name = "land"
desc = "Return to the ground for people to wield you."
- school = "transmutation"
panel = "Chaplain"
- charge_max = 10 SECONDS
- clothes_req = FALSE
- antimagic_allowed = TRUE
- range = -1
- level_max = 0 //cannot be improved
- cooldown_min = 10 SECONDS
- include_user = TRUE
-
- action_icon = 'icons/mob/actions/actions_spells.dmi'
- action_icon_state = "sworddrop"
-
-/obj/effect/proc_holder/spell/targeted/nullroddrop/cast(list/targets, mob/user)
+ button_icon = 'icons/mob/actions/actions_spells.dmi'
+ button_icon_state = "sworddrop"
+
+ school = SCHOOL_TRANSMUTATION
+ invocation = "COME"
+ invocation_type = INVOCATION_SHOUT
+
+ cooldown_time = 10 SECONDS
+ spell_requirements = NONE
+
+/datum/action/cooldown/spell/nullrod_drop/cast(mob/living/user)
user.death()//basically a glorified suicide button PLEASE don't give it to any actual player
. = ..()
diff --git a/code/game/objects/items/implants/implant.dm b/code/game/objects/items/implants/implant.dm
index fd79c112c9b1..c648c52abb39 100644
--- a/code/game/objects/items/implants/implant.dm
+++ b/code/game/objects/items/implants/implant.dm
@@ -2,8 +2,11 @@
name = "implant"
icon = 'icons/obj/implants.dmi'
icon_state = "generic" //Shows up as the action button icon
+ item_flags = DROPDEL
+ // This gives the user an action button that allows them to activate the implant.
+ // If the implant needs no action button, then null this out.
+ // Or, if you want to add a unique action button, then replace this.
actions_types = list(/datum/action/item_action/hands_free/activate)
- var/activated = TRUE //1 for implant types that can be activated, 0 for ones that are "always on" like mindshield implants
var/mob/living/imp_in = null
var/implant_color = "b"
var/allow_multiple = FALSE
@@ -23,6 +26,9 @@
/obj/item/implant/ui_action_click()
activate("action_button")
+/obj/item/implant/item_action_slot_check(slot, mob/user)
+ return user == imp_in
+
/obj/item/implant/proc/can_be_implanted_in(mob/living/target) // for human-only and other special requirements
return TRUE
@@ -75,10 +81,8 @@
forceMove(target)
imp_in = target
target.implants += src
- if(activated)
- for(var/X in actions)
- var/datum/action/A = X
- A.Grant(target)
+ for(var/datum/action/implant_action as anything in actions)
+ implant_action.Grant(target)
if(ishuman(target))
var/mob/living/carbon/human/H = target
H.sec_hud_set_implants()
@@ -92,9 +96,8 @@
moveToNullspace()
imp_in = null
source.implants -= src
- for(var/X in actions)
- var/datum/action/A = X
- A.Grant(source)
+ for(var/datum/action/implant_action as anything in actions)
+ implant_action.Remove(source) //bruh who did this (the change that was here befo' this one)
if(ishuman(source))
var/mob/living/carbon/human/H = source
H.sec_hud_set_implants()
@@ -102,7 +105,7 @@
return 1
/obj/item/implant/Destroy()
- if(imp_in)
+ if(!QDELETED(imp_in) && !QDESTROYING(imp_in))
removed(imp_in)
return ..()
diff --git a/code/game/objects/items/implants/implant_abductor.dm b/code/game/objects/items/implants/implant_abductor.dm
index 07ae3e10b20e..25a8dc00259b 100644
--- a/code/game/objects/items/implants/implant_abductor.dm
+++ b/code/game/objects/items/implants/implant_abductor.dm
@@ -3,7 +3,6 @@
desc = "Returns you to the mothership."
icon = 'icons/obj/abductor.dmi'
icon_state = "implant"
- activated = 1
var/obj/machinery/abductor/pad/home
var/cooldown = 30
diff --git a/code/game/objects/items/implants/implant_chem.dm b/code/game/objects/items/implants/implant_chem.dm
index 23fe57355661..dac6fed8e8a2 100644
--- a/code/game/objects/items/implants/implant_chem.dm
+++ b/code/game/objects/items/implants/implant_chem.dm
@@ -2,7 +2,6 @@
name = "chem implant"
desc = "Injects things."
icon_state = "reagents"
- activated = FALSE
/obj/item/implant/chem/get_data()
var/dat = {"Implant Specifications:
diff --git a/code/game/objects/items/implants/implant_clown.dm b/code/game/objects/items/implants/implant_clown.dm
index ae1fef109dd9..c91014f6824f 100644
--- a/code/game/objects/items/implants/implant_clown.dm
+++ b/code/game/objects/items/implants/implant_clown.dm
@@ -1,6 +1,6 @@
/obj/item/implant/sad_trombone
name = "sad trombone implant"
- activated = 0
+ actions_types = null
/obj/item/implant/sad_trombone/get_data()
var/dat = {"Implant Specifications:
diff --git a/code/game/objects/items/implants/implant_exile.dm b/code/game/objects/items/implants/implant_exile.dm
index 9b6820633896..29f0fc9a977c 100644
--- a/code/game/objects/items/implants/implant_exile.dm
+++ b/code/game/objects/items/implants/implant_exile.dm
@@ -4,7 +4,7 @@
/obj/item/implant/exile
name = "exile implant"
desc = "Prevents you from returning from away missions."
- activated = 0
+ actions_types = null
/obj/item/implant/exile/get_data()
var/dat = {"Implant Specifications:
diff --git a/code/game/objects/items/implants/implant_explosive.dm b/code/game/objects/items/implants/implant_explosive.dm
index db3994cb037c..697813d1cccf 100644
--- a/code/game/objects/items/implants/implant_explosive.dm
+++ b/code/game/objects/items/implants/implant_explosive.dm
@@ -122,3 +122,7 @@
/obj/item/implanter/explosive_macro
name = "implanter (macrobomb)"
imp_type = /obj/item/implant/explosive/macro
+
+/datum/action/item_action/explosive_implant
+ check_flags = NONE
+ name = "Activate Explosive Implant"
diff --git a/code/game/objects/items/implants/implant_greytide.dm b/code/game/objects/items/implants/implant_greytide.dm
index e8d1281b7d31..d38adef6a29e 100644
--- a/code/game/objects/items/implants/implant_greytide.dm
+++ b/code/game/objects/items/implants/implant_greytide.dm
@@ -1,7 +1,6 @@
/obj/item/implant/greytide
name = "Greytide implant"
desc = "Turn a crewmate into greytider"
- activated = FALSE
/obj/item/implant/greytide/get_data()
var/dat = {"
@@ -51,7 +50,7 @@
return ..()
-/obj/item/implant/greytide/removed(mob/source)
+/obj/item/implant/greytide/removed(mob/living/source, silent = FALSE, special = 0)
. = ..()
if(!.)
return
diff --git a/code/game/objects/items/implants/implant_honor.dm b/code/game/objects/items/implants/implant_honor.dm
index 32aa757218a8..0154307972de 100644
--- a/code/game/objects/items/implants/implant_honor.dm
+++ b/code/game/objects/items/implants/implant_honor.dm
@@ -1,7 +1,6 @@
/obj/item/implant/honor
name = "honor implant"
desc = "For the honorable."
- activated = 0
/obj/item/implant/honor/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE) //Copied and adjusted from mindshields
if(..())
diff --git a/code/game/objects/items/implants/implant_krav_maga.dm b/code/game/objects/items/implants/implant_krav_maga.dm
index 373658b38646..ec4b185ee133 100644
--- a/code/game/objects/items/implants/implant_krav_maga.dm
+++ b/code/game/objects/items/implants/implant_krav_maga.dm
@@ -3,7 +3,6 @@
desc = "Teaches you the arts of Krav Maga in 5 short instructional videos beamed directly into your eyeballs."
icon = 'icons/obj/wizard.dmi'
icon_state ="scroll2"
- activated = 1
var/datum/martial_art/krav_maga/style = new
/obj/item/implant/krav_maga/get_data()
diff --git a/code/game/objects/items/implants/implant_mindshield.dm b/code/game/objects/items/implants/implant_mindshield.dm
index 5dea8d41aada..153448b4ba7d 100644
--- a/code/game/objects/items/implants/implant_mindshield.dm
+++ b/code/game/objects/items/implants/implant_mindshield.dm
@@ -1,7 +1,7 @@
/obj/item/implant/mindshield
name = "mindshield implant"
desc = "Protects against brainwashing."
- activated = 0
+ actions_types = null
/obj/item/implant/mindshield/get_data()
var/dat = {"Implant Specifications:
@@ -30,7 +30,7 @@
if(host)
var/datum/mind/M = host.owner
if(M)
- var/obj/effect/proc_holder/spell/target_hive/hive_control/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_control) in M.spell_list
+ var/datum/action/cooldown/spell/aoe/target_hive/hive_control/the_spell = locate(/datum/action/cooldown/spell/aoe/target_hive/hive_control) in M.current.actions
if(the_spell && the_spell.active)
the_spell.release_control()
@@ -45,9 +45,8 @@
removed(target, TRUE)
return FALSE
- var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(target)
- if(target.mind.has_antag_datum(/datum/antagonist/vassal || !(vassaldatum.favorite_vassal)))
- if(vassaldatum.favorite_vassal)
+ if(IS_VASSAL(target))
+ if(IS_FAVORITE_VASSAL(target))
if(!silent)
target.visible_message(span_warning("[target] seems to resist the implant!"), span_warning("You feel something interfering with your mental conditioning, but you resist it!"))
removed(target, TRUE)
diff --git a/code/game/objects/items/implants/implant_mindshieldtot.dm b/code/game/objects/items/implants/implant_mindshieldtot.dm
index 29773a0c4edb..e593dc5dac3a 100644
--- a/code/game/objects/items/implants/implant_mindshieldtot.dm
+++ b/code/game/objects/items/implants/implant_mindshieldtot.dm
@@ -1,7 +1,6 @@
/obj/item/implant/mindshield/tot
name = "mindshield implant"
desc = "Protects against brainwashing."
- activated = 0
/obj/item/implant/mindshield/tot/get_data()
var/dat = {"Implant Specifications:
@@ -30,7 +29,7 @@
if(host)
var/datum/mind/M = host.owner
if(M)
- var/obj/effect/proc_holder/spell/target_hive/hive_control/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_control) in M.spell_list
+ var/datum/action/cooldown/spell/aoe/target_hive/hive_control/the_spell = locate(/datum/action/cooldown/spell/aoe/target_hive/hive_control) in M.current.actions
if(the_spell && the_spell.active)
the_spell.release_control()
diff --git a/code/game/objects/items/implants/implant_misc.dm b/code/game/objects/items/implants/implant_misc.dm
index 836d2acefd4f..b353070df7b8 100644
--- a/code/game/objects/items/implants/implant_misc.dm
+++ b/code/game/objects/items/implants/implant_misc.dm
@@ -2,7 +2,7 @@
name = "firearms authentication implant"
desc = "Lets you shoot your guns."
icon_state = "auth"
- activated = 0
+ actions_types = null
/obj/item/implant/weapons_auth/get_data()
var/dat = {"Implant Specifications:
@@ -76,7 +76,6 @@
/obj/item/implant/health
name = "health implant"
- activated = 0
var/healthstring = ""
/obj/item/implant/health/proc/sensehealth()
@@ -92,7 +91,6 @@
/obj/item/implant/radio
name = "internal radio implant"
- activated = TRUE
var/obj/item/radio/radio
var/radio_key
var/subspace_transmission = FALSE
@@ -156,7 +154,6 @@
/obj/item/implant/empshield
name = "EMP shield implant"
desc = "An implant that completely protects from electro-magnetic pulses. It will shut down briefly if triggered too often."
- activated = 0
var/lastemp = 0
var/numrecent = 0
var/warning = TRUE
diff --git a/code/game/objects/items/implants/implant_spell.dm b/code/game/objects/items/implants/implant_spell.dm
index 916eaa3cedec..ecfbfcf0ef22 100644
--- a/code/game/objects/items/implants/implant_spell.dm
+++ b/code/game/objects/items/implants/implant_spell.dm
@@ -1,36 +1,56 @@
/obj/item/implant/spell
name = "spell implant"
desc = "Allows you to cast a spell as if you were a wizard."
- activated = FALSE
+ actions_types = null
- var/autorobeless = TRUE // Whether to automagically make the spell robeless on implant
- var/obj/effect/proc_holder/spell/spell
+ /// Whether to make the spell robeless
+ var/make_robeless = TRUE
+ /// The typepath of the spell we give to people. Instantiated in Initialize
+ var/datum/action/cooldown/spell/spell_type
+ /// The actual spell we give to the person on implant
+ var/datum/action/cooldown/spell/spell_to_give
+/obj/item/implant/spell/Initialize(mapload)
+ . = ..()
+ if(!spell_type)
+ return
+
+ spell_to_give = new spell_type(src)
+
+ if(make_robeless && (spell_to_give.spell_requirements & SPELL_REQUIRES_WIZARD_GARB))
+ spell_to_give.spell_requirements &= ~SPELL_REQUIRES_WIZARD_GARB
+
+/obj/item/implant/spell/Destroy()
+ QDEL_NULL(spell_to_give)
+ return ..()
/obj/item/implant/spell/get_data()
var/dat = {"Implant Specifications:
Name: Spell Implant
Life: 4 hours after death of host
Implant Details:
- Function: [spell ? "Allows a non-wizard to cast [spell] as if they were a wizard." : "None"]"}
+ Function: [spell_to_give ? "Allows a non-wizard to cast [spell_to_give] as if they were a wizard." : "None."]"}
return dat
/obj/item/implant/spell/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE)
- . = ..()
- if (.)
- if (!spell)
- return FALSE
- if (autorobeless && spell.clothes_req)
- spell.clothes_req = FALSE
- target.AddSpell(spell)
- return TRUE
-
-/obj/item/implant/spell/removed(mob/target, silent = FALSE, special = 0)
- . = ..()
- if (.)
- target.RemoveSpell(spell)
- if(target.stat != DEAD && !silent)
- to_chat(target, span_boldnotice("The knowledge of how to cast [spell] slips out from your mind."))
+ if (!.)
+ return
+
+ if (!spell_to_give)
+ return FALSE
+
+ spell_to_give.Grant(target)
+ return TRUE
+
+/obj/item/implant/spell/removed(mob/living/source, silent = FALSE, special = 0)
+ if (!.)
+ return FALSE
+
+ if(spell_to_give)
+ spell_to_give.Remove(source)
+ if(source.stat != DEAD && !silent)
+ to_chat(source, span_boldnotice("The knowledge of how to cast [spell_to_give] slips out from your mind."))
+ return TRUE
/obj/item/implanter/spell
name = "implanter (spell)"
diff --git a/code/game/objects/items/implants/implant_storage.dm b/code/game/objects/items/implants/implant_storage.dm
index c0f1a7af7540..01f2bc5fce03 100644
--- a/code/game/objects/items/implants/implant_storage.dm
+++ b/code/game/objects/items/implants/implant_storage.dm
@@ -9,7 +9,7 @@
. = ..()
SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SHOW, imp_in, TRUE)
-/obj/item/implant/storage/removed(source, silent = FALSE, special = 0)
+/obj/item/implant/storage/removed(mob/living/source, silent = FALSE, special = 0)
if(!special)
var/datum/component/storage/lostimplant = GetComponent(/datum/component/storage/concrete/implant)
var/mob/living/implantee = source
diff --git a/code/game/objects/items/implants/implant_track.dm b/code/game/objects/items/implants/implant_track.dm
index 9485cd08638a..cadc7a09f0fd 100644
--- a/code/game/objects/items/implants/implant_track.dm
+++ b/code/game/objects/items/implants/implant_track.dm
@@ -1,7 +1,7 @@
/obj/item/implant/tracking
name = "tracking implant"
desc = "Track with this."
- activated = FALSE
+ actions_types = null
var/lifespan_postmortem = 10 MINUTES //for how long after user death will the implant work?
var/allow_teleport = TRUE //will people implanted with this act as teleporter beacons?
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index 38b0f2d454aa..f4cda7235bed 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -454,8 +454,7 @@
w_class = WEIGHT_CLASS_SMALL
item_flags = NONE
force = 5
-
- cooldown = 25
+ cooldown = 2.5 SECONDS
stamina_damage = 85
affect_silicon = TRUE
on_sound = 'sound/weapons/contractorbatonextend.ogg'
@@ -505,8 +504,8 @@
return span_danger("The baton is still charging!")
/obj/item/melee/classic_baton/telescopic/contractor_baton/additional_effects_carbon(mob/living/target, mob/living/user)
- target.Jitter(20)
- target.stuttering += 20
+ target.set_jitter_if_lower(40 SECONDS)
+ target.set_stutter_if_lower(40 SECONDS)
/obj/item/melee/supermatter_sword
name = "supermatter sword"
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index 7460c99111f6..827acd1b39c7 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -36,15 +36,15 @@
to_chat(M, span_warning("You muscles seize, making you collapse!"))
else
M.Paralyze(stunforce)
- M.Jitter(20)
- M.confused = max(8, M.confused)
+ M.adjust_jitter(20 SECONDS)
+ M.adjust_confusion_up_to(8 SECONDS, 40 SECONDS)
M.apply_effect(EFFECT_STUTTER, stunforce)
else if(current_stamina_damage > 70)
- M.Jitter(10)
- M.confused = max(8, M.confused)
+ M.adjust_jitter(10 SECONDS)
+ M.adjust_confusion_up_to(8 SECONDS, 40 SECONDS)
M.apply_effect(EFFECT_STUTTER, stunforce)
else if(current_stamina_damage >= 20)
- M.Jitter(5)
+ M.adjust_jitter(5 SECONDS)
M.apply_effect(EFFECT_STUTTER, stunforce)
M.visible_message(span_danger("[user] has prodded [M] with [src]!"), \
@@ -329,7 +329,7 @@
span_danger("The siren pierces your hearing!"))
for(var/mob/living/carbon/M in get_hearers_in_view(9, user))
if(M.get_ear_protection() == FALSE)
- M.confused += 6
+ M.adjust_confusion(6 SECONDS)
audible_message("HUMAN HARM")
playsound(get_turf(src), 'sound/ai/default/harmalarm.ogg', 70, 3)
cooldown = world.time + 200
@@ -346,16 +346,16 @@
var/bang_effect = C.soundbang_act(2, 0, 0, 5)
switch(bang_effect)
if(1)
- C.confused += 5
- C.stuttering += 10
- C.Jitter(10)
+ C.adjust_confusion(5 SECONDS)
+ C.adjust_stutter(10 SECONDS)
+ C.adjust_jitter(10 SECONDS)
if(2)
- C.Paralyze(40)
- C.confused += 10
- C.stuttering += 15
- C.Jitter(25)
+ C.Paralyze(4 SECONDS)
+ C.adjust_confusion(10 SECONDS)
+ C.adjust_stutter(15 SECONDS)
+ C.adjust_jitter(25 SECONDS)
playsound(get_turf(src), 'sound/machines/warning-buzzer.ogg', 130, 3)
- cooldown = world.time + 600
+ cooldown = world.time + 1 MINUTES
log_game("[key_name(user)] used an emagged Cyborg Harm Alarm in [AREACOORD(user)]")
#define DISPENSE_LOLLIPOP_MODE 1
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index caef660325ce..7f9e157c8cb1 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -459,7 +459,7 @@
icon_state = "selfrepair_[on ? "on" : "off"]"
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.build_all_button_icons()
else
icon_state = "cyborg_upgrade5"
diff --git a/code/game/objects/items/scrolls.dm b/code/game/objects/items/scrolls.dm
index d25009a16241..ed53e1916f34 100644
--- a/code/game/objects/items/scrolls.dm
+++ b/code/game/objects/items/scrolls.dm
@@ -3,84 +3,61 @@
desc = "A scroll for moving around."
icon = 'icons/obj/wizard.dmi'
icon_state = "scroll"
- var/uses = 4
w_class = WEIGHT_CLASS_SMALL
item_state = "paper"
throw_speed = 3
throw_range = 7
- resistance_flags = FLAMMABLE
+ actions_types = list(/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll)
+ /// Number of uses the scroll gets.
+ var/uses = 4
+
+/obj/item/teleportation_scroll/Initialize(mapload)
+ . = ..()
+ // In the future, this can be generalized into just "magic scrolls that give you a specific spell".
+ var/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll/teleport = locate() in actions
+ if(!teleport)
+ return
+ teleport.name = name
+ teleport.button_icon = icon
+ teleport.button_icon_state = icon_state
+ RegisterSignal(teleport, COMSIG_SPELL_AFTER_CAST, PROC_REF(on_spell_cast))
+
+/// Deplete charges if spell is cast successfully
+/obj/item/teleportation_scroll/proc/on_spell_cast(datum/action/cooldown/spell/cast_spell, mob/living/cast_on)
+ SIGNAL_HANDLER
+ uses--
+ if(uses)
+ return
+ cast_on.balloon_alert(cast_on, "the scroll runs out of uses!")
+
+/obj/item/teleportation_scroll/item_action_slot_check(slot, mob/user)
+ return (slot == SLOT_HANDS)
/obj/item/teleportation_scroll/apprentice
name = "lesser scroll of teleportation"
uses = 1
-
+/obj/item/teleportation_scroll/examine(mob/user)
+ . = ..()
+ if(uses)
+ . += "It has [uses] use\s remaining."
/obj/item/teleportation_scroll/attack_self(mob/user)
- user.set_machine(src)
- var/dat = ""
-
- dat += ""
-
- dat +="Teleportation Scroll:
"
- dat += "Number of uses: [src.uses]
"
- dat += "
"
- dat += "Four uses, use them wisely:
"
- dat += "Teleport
"
- dat += "Kind regards,
Wizards Federation
P.S. Don't forget to bring your gear, you'll need it to cast most spells.
"
-
- dat += ""
-
- user << browse(dat, "window=scroll")
- onclose(user, "scroll")
- return
-
-/obj/item/teleportation_scroll/Topic(href, href_list)
- ..()
- if (usr.stat || usr.restrained() || src.loc != usr)
+ . = ..()
+ if(.)
return
- if (!ishuman(usr))
- return 1
- var/mob/living/carbon/human/H = usr
- if(H.is_holding(src))
- H.set_machine(src)
- if (href_list["spell_teleport"])
- if(uses)
- teleportscroll(H)
- if(H)
- attack_self(H)
- return
-
-/obj/item/teleportation_scroll/proc/teleportscroll(mob/user)
- var/A
-
- A = input(user, "Area to jump to", "BOOYEA", A) as null|anything in GLOB.teleportlocs
- if(!src || QDELETED(src) || !user || !user.is_holding(src) || user.incapacitated() || !A || !uses)
+ if(!uses)
return
- var/area/thearea = GLOB.teleportlocs[A]
-
- var/datum/effect_system/fluid_spread/smoke/smoke = new
- smoke.set_up(2, location = user.loc)
- smoke.attach(user)
- smoke.start()
- var/list/L = list()
- var/list/achors = thearea.teleport_anchors
- if(achors.len) //check the areas prefered teleportation list if it is empty just use a random enpty tile like before
- for(var/turf/T in achors)
- if(!is_blocked_turf(T))
- L += T
- if(!L.len)
- for(var/turf/T in get_area_turfs(thearea.type))
- if(!is_blocked_turf(T))
- L += T
-
- if(!L.len)
- to_chat(user, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.")
+ if(!ishuman(user))
return
-
- if(do_teleport(user, pick(L), forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = TRUE))
- smoke.start()
- uses--
- else
- to_chat(user, "The spell matrix was disrupted by something near the destination.")
+ var/mob/living/carbon/human/human_user = user
+ if(human_user.incapacitated() || !human_user.is_holding(src))
+ return
+ var/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll/teleport = locate() in actions
+ if(!teleport)
+ to_chat(user, span_warning("[src] seems to be a faulty teleportation scroll, and has no magic associated."))
+ return
+ if(!teleport.Activate(user))
+ return
+ return TRUE
diff --git a/code/game/objects/items/signs.dm b/code/game/objects/items/signs.dm
index fa2802870e34..7d450b045b41 100644
--- a/code/game/objects/items/signs.dm
+++ b/code/game/objects/items/signs.dm
@@ -57,6 +57,15 @@
animate(pixel_y = user.pixel_y + (1 * direction), time = 0.1 SECONDS, easing = SINE_EASING)
user.changeNext_move(CLICK_CD_MELEE)
+/datum/action/item_action/nano_picket_sign
+ name = "Retext Nano Picket Sign"
+
+/datum/action/item_action/nano_picket_sign/Trigger(trigger_flags)
+ if(!istype(target, /obj/item/picket_sign))
+ return
+ var/obj/item/picket_sign/sign = target
+ sign.retext(owner)
+
/datum/crafting_recipe/picket_sign
name = "Picket Sign"
result = /obj/item/picket_sign
diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm
index 264b82e18de0..477739bb1d1e 100644
--- a/code/game/objects/items/stacks/bscrystal.dm
+++ b/code/game/objects/items/stacks/bscrystal.dm
@@ -73,7 +73,7 @@
if(iscarbon(user))
var/mob/living/carbon/C = user
C.adjust_disgust(30) //Won't immediately make you vomit, just dont use more than one or two at a time
- C.confused += 7
+ C.adjust_confusion(7 SECONDS)
use(1)
/obj/item/stack/ore/bluespace_crystal/proc/blink_mob(mob/living/L)
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 4b073270bde5..346dec3a8970 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -276,35 +276,30 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \
return ..()
/obj/item/stack/sheet/mineral/wood/attackby(obj/item/item, mob/user, params)
- if(item.get_sharpness())
- user.visible_message(
- span_notice("[user] begins whittling [src] into a pointy object."),
- span_notice("You begin whittling [src] into a sharp point at one end."),
- span_hear("You hear wood carving."),
- )
- // 5 Second Timer
- if(!do_after(user, 5 SECONDS, src))
- return
- // Make Stake
- var/obj/item/stake/new_item = new(user.loc)
- user.visible_message(
- span_notice("[user] finishes carving a stake out of [src]."),
- span_notice("You finish carving a stake out of [src]."),
- )
- // Prepare to Put in Hands (if holding wood)
- var/obj/item/stack/sheet/mineral/wood/wood_stack = src
- var/replace = (user.get_inactive_held_item() == wood_stack)
- // Use Wood
- wood_stack.use(1)
- // If stack depleted, put item in that hand (if it had one)
- if(!wood_stack && replace)
- user.put_in_hands(new_item)
- if(istype(item, merge_type))
- var/obj/item/stack/merged_stack = item
- if(merge(merged_stack))
- to_chat(user, span_notice("Your [merged_stack.name] stack now contains [merged_stack.get_amount()] [merged_stack.singular_name]\s."))
+ if(!item.get_sharpness())
+ return ..()
+ user.visible_message(
+ span_notice("[user] begins whittling [src] into a pointy object."),
+ span_notice("You begin whittling [src] into a sharp point at one end."),
+ span_hear("You hear wood carving."),
+ )
+ // 5 Second Timer
+ if(!do_after(user, 5 SECONDS, src, NONE, TRUE))
return
- return ..()
+ // Make Stake
+ var/obj/item/stake/new_item = new(user.loc)
+ user.visible_message(
+ span_notice("[user] finishes carving a stake out of [src]."),
+ span_notice("You finish carving a stake out of [src]."),
+ )
+ // Prepare to Put in Hands (if holding wood)
+ var/obj/item/stack/sheet/mineral/wood/wood_stack = src
+ var/replace = (user.get_inactive_held_item() == wood_stack)
+ // Use Wood
+ wood_stack.use(1)
+ // If stack depleted, put item in that hand (if it had one)
+ if(!wood_stack && replace)
+ user.put_in_hands(new_item)
/obj/item/stack/sheet/mineral/wood/fifty
amount = 50
diff --git a/code/game/objects/items/stacks/tape.dm b/code/game/objects/items/stacks/tape.dm
index e4cc6808d00c..c3a087189648 100644
--- a/code/game/objects/items/stacks/tape.dm
+++ b/code/game/objects/items/stacks/tape.dm
@@ -45,7 +45,7 @@
if(I.w_class > maximum_weight_class)
to_chat(user, span_warning("[I] is too big!"))
return
- var/list/item_contents = I.GetAllContents()
+ var/list/item_contents = I.get_all_contents()
for(var/obj/item/C in item_contents)
if(is_type_in_typecache(C,tape_blacklist))
to_chat(user, span_warning("The [src] doesn't seem to stick to [I]!"))
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index ca738bd76fc1..8a16dc156bd5 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -367,6 +367,20 @@
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
STR.max_combined_w_class = 30
+/obj/item/storage/backpack/duffelbag/cursed
+ name = "living duffel bag"
+ desc = "A cursed clown duffel bag that hungers for food of any kind. A warning label suggests that it eats food inside. \
+ If that food happens to be a horribly ruined mess or the chef scrapped out of the microwave, or poisoned in some way, \
+ then it might have negative effects on the bag..."
+ icon_state = "duffel-curse"
+ item_state = "duffel-curse"
+ slowdown = 1.5
+ max_integrity = 100
+
+/obj/item/storage/backpack/duffelbag/cursed/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/curse_of_hunger, add_dropdel = TRUE)
+
/obj/item/storage/backpack/duffelbag/captain
name = "captain's duffel bag"
desc = "A large duffel bag for holding extra captainly goods."
diff --git a/code/game/objects/items/storage/book.dm b/code/game/objects/items/storage/book.dm
index c299179a3564..31744eb5b65d 100644
--- a/code/game/objects/items/storage/book.dm
+++ b/code/game/objects/items/storage/book.dm
@@ -202,7 +202,8 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning",
SS.usability = TRUE
for(var/mob/living/simple_animal/shade/EX in SS)
SSticker.mode.remove_cultist(EX.mind, 1, 0)
- EX.icon_state = "ghost1"
+
+ EX.icon_state = "shade_holy"
EX.name = "Purified [EX.name]"
SS.release_shades(user)
qdel(SS)
@@ -241,7 +242,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning",
to_chat(S, span_userdanger("You were destroyed by the exorcism!"))
qdel(S)
if(sword.owner)
- sword.owner.RemoveSpell(sword.summon)
+ sword.summon.Remove(sword.owner)
sword.owner = null
sword.possessed = FALSE //allows the chaplain (or someone else) to reroll a new spirit for their sword
sword.name = initial(sword.name)
diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm
index 572ea9cb60e0..b76374179d67 100644
--- a/code/game/objects/items/storage/toolbox.dm
+++ b/code/game/objects/items/storage/toolbox.dm
@@ -110,7 +110,7 @@
/obj/item/storage/toolbox/mechanical/old/clean/proc/calc_damage()
var/power = 0
- for (var/obj/item/stack/telecrystal/TC in GetAllContents())
+ for (var/obj/item/stack/telecrystal/TC in get_all_contents())
power += TC.amount
force = 19 + power
throwforce = 22 + power
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index 4d0e75c105fd..e77362be6af0 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -181,9 +181,9 @@
new /obj/item/clothing/suit/yogs/armor/sith_suit(src) //See above
new /obj/item/clothing/shoes/combat(src) //See above
new /obj/item/clothing/gloves/combat(src) //Maybe 1 TC, so you don't shock yourself
- new /obj/item/book/granter/spell/lightningbolt(src) //Lightning bolt, LIGHTNING BOLT. A 2 SP cost spell that doesn't require robes and provides ranged potential
- new /obj/item/book/granter/spell/forcewall(src) //It has the word force in it? But more importantly, it doesn't require robes and it's 1 SP and it's VERY good defense
- new /obj/item/book/granter/spell/summonitem(src) //So you can throw your lightsaber and call it back. A 1 SP cost spell that doesn't require robes
+ new /obj/item/book/granter/action/spell/lightningbolt(src) //Lightning bolt, LIGHTNING BOLT. A 2 SP cost spell that doesn't require robes and provides ranged potential
+ new /obj/item/book/granter/action/spell/forcewall(src) //It has the word force in it? But more importantly, it doesn't require robes and it's 1 SP and it's VERY good defense
+ new /obj/item/book/granter/action/spell/summonitem(src) //So you can throw your lightsaber and call it back. A 1 SP cost spell that doesn't require robes
if("white_whale_holy_grail") //Unique items that don't appear anywhere else, more than 100 carps or your TC back
new /obj/item/pneumatic_cannon/speargun(src)
@@ -692,8 +692,8 @@
new /obj/item/gun/ballistic/revolver/reverse(src)
/obj/item/storage/box/syndie_kit/mimery/PopulateContents()
- new /obj/item/book/granter/spell/mimery_blockade(src)
- new /obj/item/book/granter/spell/mimery_guns(src)
+ new /obj/item/book/granter/action/spell/mime/mimery_blockade(src)
+ new /obj/item/book/granter/action/spell/mime/mimery_guns(src)
/obj/item/storage/box/syndie_kit/centcom_costume/PopulateContents()
new /obj/item/clothing/under/rank/centcom_officer(src)
diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm
index 926a377b90ea..f1c2b598bf93 100644
--- a/code/game/objects/items/stunbaton.dm
+++ b/code/game/objects/items/stunbaton.dm
@@ -246,15 +246,15 @@
to_chat(L, span_warning("You muscles seize, making you collapse!"))
else
L.Paralyze(stunforce)
- L.Jitter(20)
- L.confused = max(8, L.confused)
+ L.adjust_jitter(20 SECONDS)
+ L.adjust_confusion(8 SECONDS)
L.apply_effect(EFFECT_STUTTER, stunforce)
else if(current_stamina_damage > 70)
- L.Jitter(10)
- L.confused = max(8, L.confused)
+ L.adjust_jitter(10 SECONDS)
+ L.adjust_confusion(8 SECONDS)
L.apply_effect(EFFECT_STUTTER, stunforce)
else if(current_stamina_damage >= 20)
- L.Jitter(5)
+ L.adjust_jitter(5 SECONDS)
L.apply_effect(EFFECT_STUTTER, stunforce)
if(user)
diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm
index 3c1f9cac5101..537af0d89931 100644
--- a/code/game/objects/items/tanks/jetpack.dm
+++ b/code/game/objects/items/tanks/jetpack.dm
@@ -49,7 +49,7 @@
to_chat(user, span_notice("You turn the jetpack off."))
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.build_all_button_icons()
/obj/item/tank/jetpack/proc/turn_on(mob/user)
diff --git a/code/game/objects/items/tanks/watertank.dm b/code/game/objects/items/tanks/watertank.dm
index cb15dd5c22e9..d58ee3719ec8 100644
--- a/code/game/objects/items/tanks/watertank.dm
+++ b/code/game/objects/items/tanks/watertank.dm
@@ -329,6 +329,9 @@
#undef RESIN_LAUNCHER
#undef RESIN_FOAM
+/datum/action/item_action/activate_injector
+ name = "Activate Injector"
+
/obj/item/reagent_containers/chemtank
name = "backpack chemical injector"
desc = "A chemical autoinjector that can be carried on your back."
diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm
index 0c935a5ca85e..2f1dfe52a8b7 100644
--- a/code/game/objects/items/tools/weldingtool.dm
+++ b/code/game/objects/items/tools/weldingtool.dm
@@ -151,7 +151,7 @@
if(isliving(O))
var/mob/living/L = O
- if(L.IgniteMob())
+ if(L.ignite_mob())
message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(L)] on fire with [src] at [AREACOORD(user)]")
log_game("[key_name(user)] set [key_name(L)] on fire with [src] at [AREACOORD(user)]")
@@ -165,9 +165,10 @@
update_icon()
/obj/item/weldingtool/use_tool(atom/target, mob/living/user, delay, amount, volume, datum/callback/extra_checks, robo_check)
- target.add_overlay(GLOB.welding_sparks)
+ var/mutable_appearance/sparks = mutable_appearance('icons/effects/welding_effect.dmi', "welding_sparks", GASFIRE_LAYER, src, ABOVE_LIGHTING_PLANE)
+ target.add_overlay(sparks)
. = ..()
- target.cut_overlay(GLOB.welding_sparks)
+ target.cut_overlay(sparks)
// Returns the amount of fuel in the welder
/obj/item/weldingtool/proc/get_fuel()
diff --git a/code/game/objects/items/twohanded.dm b/code/game/objects/items/twohanded.dm
index 2cf9edad68ef..67e8f3878cf5 100644
--- a/code/game/objects/items/twohanded.dm
+++ b/code/game/objects/items/twohanded.dm
@@ -338,7 +338,7 @@
/obj/item/twohanded/fireaxe/energy/attack(mob/living/M, mob/living/user)
..()
- M.IgniteMob() // Ignites you if you're flammable
+ M.ignite_mob() // Ignites you if you're flammable
/obj/item/twohanded/fireaxe/energy/afterattack(atom/A, mob/user, proximity)
. = ..()
@@ -699,6 +699,10 @@
qdel(src)
// CHAINSAW
+
+/datum/action/item_action/startchainsaw
+ name = "Pull The Starting Cord"
+
/obj/item/twohanded/required/chainsaw
name = "chainsaw"
desc = "A versatile power tool. Useful for limbing trees and delimbing humans."
@@ -755,7 +759,7 @@
user.update_inv_hands()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.build_all_button_icons()
/obj/item/twohanded/required/chainsaw/doomslayer
name = "THE GREAT COMMUNICATOR"
@@ -936,6 +940,19 @@
/obj/item/twohanded/vibro_weapon/update_icon()
icon_state = "hfrequency[wielded]"
+/obj/item/twohanded/vibro_weapon/wizard
+ desc = "A blade that was mastercrafted by a legendary blacksmith. Its' enchantments let it slash through anything."
+ force = 8
+ throwforce = 20
+ wound_bonus = 20
+ bare_wound_bonus = 25
+
+/obj/item/twohanded/vibro_weapon/wizard/wizard/attack_self(mob/user, modifiers)
+ if(!iswizard(user))
+ balloon_alert(user, "you're too weak!")
+ return
+ return ..()
+
/*
* Bone Axe
*/
@@ -1060,6 +1077,14 @@
* Vxtvul Hammer
*/
+/datum/action/item_action/charge_hammer
+ name = "Charge the Blast Pads"
+
+/datum/action/item_action/charge_hammer/Trigger()
+ var/obj/item/twohanded/vxtvulhammer/vxtvulhammer = target
+ if(istype(vxtvulhammer))
+ vxtvulhammer.charge_hammer(owner)
+
/obj/item/twohanded/vxtvulhammer
icon = 'icons/obj/weapons/misc.dmi'
icon_state = "vxtvul_hammer0-0"
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 7693ff73df1d..96beec4a7f1f 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -75,7 +75,7 @@
user.visible_message(span_warning("[user] starts climbing onto [src]."), \
span_notice("You start climbing onto [src]..."))
var/adjusted_climb_time = climb_time
- if(user.restrained()) //climbing takes twice as long when restrained.
+ if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //climbing takes twice as long without help from the hands.
adjusted_climb_time *= 2
if(isalien(user))
adjusted_climb_time *= 0.25 //aliens are terrifyingly fast
diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm
index 34380daa8f2e..c60c8c725230 100644
--- a/code/game/objects/structures/beds_chairs/chair.dm
+++ b/code/game/objects/structures/beds_chairs/chair.dm
@@ -164,7 +164,7 @@
return ..()
/obj/structure/chair/comfy/proc/GetArmrest()
- return mutable_appearance('icons/obj/chairs.dmi', "comfychair_armrest")
+ return mutable_appearance(icon, "[icon_state]_armrest")
/obj/structure/chair/comfy/Destroy()
QDEL_NULL(armrest)
diff --git a/code/game/objects/structures/beds_chairs/pew.dm b/code/game/objects/structures/beds_chairs/pew.dm
index 1f88489928ee..b7aa1f65d2bd 100644
--- a/code/game/objects/structures/beds_chairs/pew.dm
+++ b/code/game/objects/structures/beds_chairs/pew.dm
@@ -69,4 +69,4 @@
/obj/structure/chair/pew/right/post_unbuckle_mob()
. = ..()
- update_rightpewarmrest()
\ No newline at end of file
+ update_rightpewarmrest()
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 0d11e1f64f19..648533139a68 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -51,13 +51,24 @@ GLOBAL_LIST_EMPTY(lockers)
var/door_anim_time = 2.5 // set to 0 to make the door not animate at all
/obj/structure/closet/Initialize(mapload)
- if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents
- addtimer(CALLBACK(src, PROC_REF(take_contents)), 0)
. = ..()
+
+ if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents
+ . = INITIALIZE_HINT_LATELOAD
+
update_icon()
PopulateContents()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_MAGICALLY_UNLOCKED = PROC_REF(on_magic_unlock),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
GLOB.lockers += src
+/obj/structure/closet/LateInitialize()
+ . = ..()
+
+ take_contents()
+
//USE THIS TO FILL IT, NOT INITIALIZE OR NEW
/obj/structure/closet/proc/PopulateContents()
return
@@ -634,4 +645,10 @@ GLOBAL_LIST_EMPTY(lockers)
if(can_open(caller) || allowed(caller))
return TRUE
. = ..()
-
\ No newline at end of file
+
+ /// Signal proc for [COMSIG_ATOM_MAGICALLY_UNLOCKED]. Unlock and open up when we get knock casted.
+/obj/structure/closet/proc/on_magic_unlock(datum/source, datum/action/cooldown/spell/aoe/knock/spell, mob/living/caster)
+ SIGNAL_HANDLER
+
+ locked = FALSE
+ INVOKE_ASYNC(src, PROC_REF(open))
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index dc46e1bedda5..c95786346146 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -13,6 +13,11 @@
delivery_icon = "deliverycrate"
door_anim_time = 0 // no animation
var/obj/item/paper/fluff/jobs/cargo/manifest/manifest
+ breakout_time = 20 SECONDS
+ ///The resident (owner) of this crate/coffin.
+ var/mob/living/resident
+ ///The time it takes to pry this open with a crowbar.
+ var/pry_lid_timer = 25 SECONDS
/obj/structure/closet/crate/Initialize()
. = ..()
diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm
index 1e96cd91e5ae..8a268250fa70 100644
--- a/code/game/objects/structures/displaycase.dm
+++ b/code/game/objects/structures/displaycase.dm
@@ -292,7 +292,7 @@
to_chat(user, span_warning("The case rejects the [W]!"))
return
- for(var/a in W.GetAllContents())
+ for(var/a in W.get_all_contents())
if(is_type_in_typecache(a, GLOB.blacklisted_cargo_types))
to_chat(user, span_warning("The case rejects the [W]!"))
return
diff --git a/code/game/objects/structures/ghost_role_spawners.dm b/code/game/objects/structures/ghost_role_spawners.dm
index 6a37d715e02c..e6923a09f890 100644
--- a/code/game/objects/structures/ghost_role_spawners.dm
+++ b/code/game/objects/structures/ghost_role_spawners.dm
@@ -419,11 +419,11 @@
random = TRUE
id_job = "SuperFriend"
id_access = "assistant"
- var/obj/effect/proc_holder/spell/targeted/summon_friend/spell
+ var/datum/action/cooldown/spell/summon_friend/spell
var/datum/mind/owner
assignedrole = "SuperFriend"
-/obj/effect/mob_spawn/human/demonic_friend/Initialize(mapload, datum/mind/owner_mind, obj/effect/proc_holder/spell/targeted/summon_friend/summoning_spell)
+/obj/effect/mob_spawn/human/demonic_friend/Initialize(mapload, datum/mind/owner_mind, datum/action/cooldown/spell/summon_friend/summoning_spell)
. = ..()
owner = owner_mind
flavour_text = "You have been given a reprieve from your eternity of torment, to be [owner.name]'s friend for [owner.p_their()] short mortal coil."
@@ -434,13 +434,11 @@
objectives = "Be [owner.name]'s friend, and keep [owner.name] alive, so you don't get sent back to hell."
spell = summoning_spell
-
/obj/effect/mob_spawn/human/demonic_friend/special(mob/living/L)
if(!QDELETED(owner.current) && owner.current.stat != DEAD)
L.fully_replace_character_name(null,"[owner.name]'s best friend")
soullink(/datum/soullink/oneway, owner.current, L)
spell.friend = L
- spell.charge_counter = spell.charge_max
L.mind.hasSoul = FALSE
var/mob/living/carbon/human/H = L
var/obj/item/worn = H.wear_id
diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm
index 442474588239..52f42d37e2cf 100644
--- a/code/game/objects/structures/manned_turret.dm
+++ b/code/game/objects/structures/manned_turret.dm
@@ -78,13 +78,14 @@
var/mob/living/controller = buckled_mobs[1]
if(!istype(controller))
return FALSE
- var/client/C = controller.client
- if(C)
- var/atom/A = C.mouseObject
- var/turf/T = get_turf(A)
- if(istype(T)) //They're hovering over something in the map.
- direction_track(controller, T)
- calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(controller, C.mouseParams)
+ var/client/controlling_client = controller.client
+ if(controlling_client)
+ var/modifiers = params2list(controlling_client.mouseParams)
+ var/atom/target_atom = controlling_client.mouse_object_ref?.resolve()
+ var/turf/target_turf = get_turf(target_atom)
+ if(istype(target_turf)) //They're hovering over something in the map.
+ direction_track(controller, target_turf)
+ calculated_projectile_vars = calculate_projectile_angle_and_pixel_offsets(controller, target_turf, modifiers)
/obj/machinery/manned_turret/proc/direction_track(mob/user, atom/targeted)
if(user.incapacitated())
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 06aa1fcffed9..a164eca7a0dd 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -251,7 +251,7 @@ GLOBAL_LIST_EMPTY(crematoriums)
if(locked)
return //don't let you cremate something twice or w/e
// Make sure we don't delete the actual morgue and its tray
- var/list/conts = GetAllContents() - src - connected
+ var/list/conts = get_all_contents() - src - connected
if(!conts.len)
audible_message(span_italics("You hear a hollow crackle."))
@@ -274,13 +274,13 @@ GLOBAL_LIST_EMPTY(crematoriums)
update_icon()
/obj/structure/bodycontainer/crematorium/proc/finish_cremate(mob/user)
- var/list/conts = GetAllContents() - src - connected
+ var/list/conts = get_all_contents() - src - connected
audible_message(span_italics("You hear a roar as the crematorium reaches its maximum temperature."))
for(var/mob/living/M in conts)
if(M.status_flags & GODMODE)
to_chat(M, span_userdanger("A strange force protects you!"))
M.adjust_fire_stacks(40)
- M.IgniteMob()
+ M.ignite_mob()
continue
if(M.stat != DEAD)
M.emote("scream")
@@ -336,7 +336,7 @@ GLOBAL_LIST_EMPTY(crematoriums)
/obj/structure/bodycontainer/crematorium/creamatorium/cremate(mob/user)
var/list/icecreams = new()
- for(var/i_scream in GetAllContents(/mob/living))
+ for(var/i_scream in get_all_contents(/mob/living))
var/obj/item/reagent_containers/food/snacks/icecream/IC = new()
IC.set_cone_type("waffle")
IC.add_mob_flavor(i_scream)
diff --git a/code/game/objects/structures/spirit_board.dm b/code/game/objects/structures/spirit_board.dm
index 5d2cf83f7530..c079c99616fe 100644
--- a/code/game/objects/structures/spirit_board.dm
+++ b/code/game/objects/structures/spirit_board.dm
@@ -59,7 +59,7 @@
light_amount = T.get_lumcount()
- if(light_amount > 0.2)
+ if(light_amount > LIGHTING_TILE_IS_DARK)
to_chat(M, span_warning("It's too bright here to use [src.name]!"))
return 0
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 9416c4d638dc..2b574b751802 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -256,7 +256,7 @@
if(washing_face)
SEND_SIGNAL(user, COMSIG_COMPONENT_CLEAN_FACE_ACT, CLEAN_WASH)
- user.drowsyness = max(user.drowsyness - rand(2,3), 0) //Washing your face wakes you up if you're falling asleep
+ user.adjust_drowsiness_up_to(-rand(2,3), 0) //Washing your face wakes you up if you're falling asleep
user.wash_cream()
else if(ishuman(user))
var/mob/living/carbon/human/human_user = user
@@ -291,7 +291,7 @@
flick("baton_active", src)
var/stunforce = B.stunforce
user.Paralyze(stunforce)
- user.stuttering = stunforce/20
+ user.adjust_stutter(stunforce/(2 SECONDS))
B.deductcharge(B.hitcost)
user.visible_message(span_warning("[user] shocks [user.p_them()]self while attempting to wash the active [B.name]!"), \
span_userdanger("You unwisely attempt to wash [B] while it's still on."))
diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm
index f03fb6ae0ebf..99d385947cdd 100644
--- a/code/game/turfs/change_turf.dm
+++ b/code/game/turfs/change_turf.dm
@@ -7,7 +7,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
/turf/proc/empty(turf_type=/turf/open/space, baseturf_type, list/ignore_typecache, flags)
// Remove all atoms except observers, landmarks, docking ports
var/static/list/ignored_atoms = typecacheof(list(/mob/dead, /obj/effect/landmark, /obj/docking_port))
- var/list/allowed_contents = typecache_filter_list_reverse(GetAllContentsIgnoring(ignore_typecache), ignored_atoms)
+ var/list/allowed_contents = typecache_filter_list_reverse(get_all_contentsIgnoring(ignore_typecache), ignored_atoms)
allowed_contents -= src
for(var/i in 1 to allowed_contents.len)
var/thing = allowed_contents[i]
diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm
index 1a7f90c450b4..de63a88639dd 100644
--- a/code/game/turfs/open.dm
+++ b/code/game/turfs/open.dm
@@ -537,47 +537,46 @@
movable_content.wash(CLEAN_WASH)
return TRUE
-/turf/open/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube, stun_amount, force_drop)
- if(C.movement_type & FLYING)
- return 0
+/turf/open/handle_slip(mob/living/carbon/slipper, knockdown_amount, obj/slippable, lube, paralyze_amount, force_drop)
+ if(slipper.movement_type & (FLYING | FLOATING))
+ return FALSE
if(has_gravity(src))
var/obj/buckled_obj
- if(C.buckled)
- buckled_obj = C.buckled
+ if(slipper.buckled)
+ buckled_obj = slipper.buckled
if(!(lube&GALOSHES_DONT_HELP)) //can't slip while buckled unless it's lube.
return 0
else
- if(!(lube&SLIP_WHEN_CRAWLING) && (!(C.mobility_flags & MOBILITY_STAND) || !(C.status_flags & CANKNOCKDOWN))) // can't slip unbuckled mob if they're lying or can't fall.
+ if(!(lube & SLIP_WHEN_CRAWLING) && (!(slipper.mobility_flags & MOBILITY_STAND)) || !(slipper.status_flags & CANKNOCKDOWN)) // can't slip unbuckled mob if they're lying or can't fall.
return 0
- if(C.m_intent == MOVE_INTENT_WALK && (lube&NO_SLIP_WHEN_WALKING))
+ if(slipper.m_intent == MOVE_INTENT_WALK && (lube&NO_SLIP_WHEN_WALKING))
return 0
if(!(lube&SLIDE_ICE))
- to_chat(C, span_notice("You slipped[ O ? " on the [O.name]" : ""]!"))
- playsound(C.loc, 'sound/misc/slip.ogg', 50, 1, -3)
+ to_chat(slipper, span_notice("You slipped[ slippable ? " on the [slippable.name]" : ""]!"))
+ playsound(slipper.loc, 'sound/misc/slip.ogg', 50, TRUE, -3)
- SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "slipped", /datum/mood_event/slipped)
+ SEND_SIGNAL(slipper, COMSIG_ON_CARBON_SLIP)
if(force_drop)
- for(var/obj/item/I in C.held_items)
- C.accident(I)
+ for(var/obj/item/I in slipper.held_items)
+ slipper.accident(I)
- var/olddir = C.dir
- C.moving_diagonally = 0 //If this was part of diagonal move slipping will stop it.
+ var/olddir = slipper.dir
+ slipper.moving_diagonally = 0 //If this was part of diagonal move slipping will stop it.
if(!(lube & SLIDE_ICE))
- C.Knockdown(knockdown_amount)
- C.Stun(stun_amount)
- C.stop_pulling()
+ slipper.Knockdown(knockdown_amount)
+ slipper.Paralyze(paralyze_amount)
+ slipper.stop_pulling()
else
- C.Knockdown(20)
+ slipper.Knockdown(20)
if(buckled_obj)
- buckled_obj.unbuckle_mob(C)
+ buckled_obj.unbuckle_mob(slipper)
lube |= SLIDE_ICE
- if(lube&SLIDE)
- new /datum/forced_movement(C, get_ranged_target_turf(C, olddir, 4), 1, FALSE, CALLBACK(C, TYPE_PROC_REF(/mob/living/carbon, spin), 1, 1))
+ var/turf/target = get_ranged_target_turf(slipper, olddir, 4)
+ if(lube & SLIDE)
+ new /datum/forced_movement(slipper, target, 1, FALSE, CALLBACK(slipper, TYPE_PROC_REF(/mob/living/carbon, spin), 1, 1))
else if(lube&SLIDE_ICE)
- if(C.force_moving) //If we're already slipping extend it
- qdel(C.force_moving)
- new /datum/forced_movement(C, get_ranged_target_turf(C, olddir, 1), 1, FALSE) //spinning would be bad for ice, fucks up the next dir
+ new /datum/forced_movement(slipper, get_ranged_target_turf(slipper, olddir, 1), 1, FALSE) //spinning would be bad for ice, fucks up the next dir
return 1
/turf/open/proc/MakeSlippery(wet_setting = TURF_WET_WATER, min_wet_time = 0, wet_time_to_add = 0, max_wet_time = MAXIMUM_WET_TIME, permanent)
diff --git a/code/game/turfs/simulated/chasm.dm b/code/game/turfs/simulated/chasm.dm
index 812260f5bb35..936050ada576 100644
--- a/code/game/turfs/simulated/chasm.dm
+++ b/code/game/turfs/simulated/chasm.dm
@@ -123,6 +123,6 @@
/turf/open/chasm/magic/Initialize()
. = ..()
- var/turf/T = safepick(get_area_turfs(/area/fabric_of_reality))
+ var/turf/T = pick(get_area_turfs(/area/fabric_of_reality))
if(T)
set_target(T)
diff --git a/code/game/turfs/simulated/lava.dm b/code/game/turfs/simulated/lava.dm
index 14260bfbd53e..71b14a93724b 100644
--- a/code/game/turfs/simulated/lava.dm
+++ b/code/game/turfs/simulated/lava.dm
@@ -154,7 +154,7 @@
L.adjustFireLoss(20 * delta_time)
if(L) //mobs turning into object corpses could get deleted here.
L.adjust_fire_stacks(20 * delta_time)
- L.IgniteMob()
+ L.ignite_mob()
/turf/open/lava/smooth
name = "lava"
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 67c323702ebe..416fbfaf2a15 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -19,6 +19,9 @@ GLOBAL_LIST_EMPTY(station_turfs)
var/blocks_air = FALSE
+ ///Which directions does this turf block the vision of, taking into account both the turf's opacity and the movable opacity_sources.
+ var/directional_opacity = NONE
+
flags_1 = CAN_BE_DIRTY_1
var/list/image/blueprint_data //for the station blueprints, images of objects eg: pipes
diff --git a/code/game/world.dm b/code/game/world.dm
index ce26e5eef1c9..4973eb6f2c86 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -120,6 +120,7 @@ GLOBAL_VAR(restart_counter)
GLOB.world_attack_log = "[GLOB.log_directory]/attack.log"
GLOB.world_pda_log = "[GLOB.log_directory]/pda.log"
GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log"
+ GLOB.world_uplink_log = "[GLOB.log_directory]/uplink.log"
GLOB.world_ntsl_log = "[GLOB.log_directory]/ntsl.log"
GLOB.world_manifest_log = "[GLOB.log_directory]/manifest.log"
GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log"
diff --git a/code/modules/VR/vr_human.dm b/code/modules/VR/vr_human.dm
index 2985a73de245..b3c522836768 100644
--- a/code/modules/VR/vr_human.dm
+++ b/code/modules/VR/vr_human.dm
@@ -74,7 +74,7 @@
/datum/action/quit_vr
name = "Quit Virtual Reality"
- icon_icon = 'icons/mob/actions/actions_vr.dmi'
+ button_icon = 'icons/mob/actions/actions_vr.dmi'
button_icon_state = "logout"
/datum/action/quit_vr/Trigger()
@@ -83,4 +83,4 @@
var/mob/living/carbon/human/virtual_reality/VR = owner
VR.revert_to_reality(FALSE)
else
- Remove(owner)
\ No newline at end of file
+ Remove(owner)
diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm
index 0d82c3bf6214..64f814a6ffd0 100644
--- a/code/modules/VR/vr_sleeper.dm
+++ b/code/modules/VR/vr_sleeper.dm
@@ -188,8 +188,7 @@
QDEL_NULL(vr_human)
/obj/machinery/vr_sleeper/proc/emagNotify()
- if(vr_human)
- vr_human.Dizzy(10)
+ vr_human?.adjust_dizzy(10 SECONDS)
/obj/effect/landmark/vr_spawn //places you can spawn in VR, auto selected by the vr_sleeper during get_vr_spawnpoint()
var/vr_category = "default" //So we can have specific sleepers, eg: "Basketball VR Sleeper", etc.
@@ -236,4 +235,4 @@
for (var/mob/living/carbon/human/virtual_reality/H in vr_area)
if (H.stat == DEAD && !H.vr_sleeper && !H.real_mind)
qdel(H)
- addtimer(CALLBACK(src, PROC_REF(clean_up)), 3 MINUTES)
\ No newline at end of file
+ addtimer(CALLBACK(src, PROC_REF(clean_up)), 3 MINUTES)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 6bb6861d57e2..94a5b4ed4d58 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -252,7 +252,7 @@
dat+="
The newscaster recognises you as:
[src.admin_signature]"
if(1)
dat+= "Station Feed Channels
"
- if( isemptylist(GLOB.news_network.network_channels) )
+ if( !length(GLOB.news_network.network_channels) )
dat+="No active channels found..."
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -305,7 +305,7 @@
dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
"
dat+="No further feed story additions are allowed while the D-Notice is in effect.
"
else
- if( isemptylist(src.admincaster_feed_channel.messages) )
+ if( !length(src.admincaster_feed_channel.messages) )
dat+="No feed messages found in channel...
"
else
var/i = 0
@@ -327,7 +327,7 @@
dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
"
dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it."
dat+="
Select Feed channel to get Stories from:
"
- if(isemptylist(GLOB.news_network.network_channels))
+ if(!length(GLOB.news_network.network_channels))
dat+="No feed channels found active...
"
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -338,7 +338,7 @@
dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's"
dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed"
dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
"
- if(isemptylist(GLOB.news_network.network_channels))
+ if(!length(GLOB.news_network.network_channels))
dat+="No feed channels found active...
"
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -349,7 +349,7 @@
dat+="[src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.returnAuthor(-1)] \]
"
dat+="[(src.admincaster_feed_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]
"
- if( isemptylist(src.admincaster_feed_channel.messages) )
+ if( !length(src.admincaster_feed_channel.messages) )
dat+="No feed messages found in channel...
"
else
for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
@@ -366,7 +366,7 @@
dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
"
dat+="No further feed story additions are allowed while the D-Notice is in effect.
"
else
- if( isemptylist(src.admincaster_feed_channel.messages) )
+ if( !length(src.admincaster_feed_channel.messages) )
dat+="No feed messages found in channel...
"
else
for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index accce5184121..ea5303bf77f8 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -173,6 +173,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/export_dynamic_json,
/client/proc/run_dynamic_simulations,
#endif
+ /client/proc/debug_spell_requirements,
)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
GLOBAL_PROTECT(admin_verbs_possess)
@@ -578,42 +579,80 @@ 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/give_spell(mob/T in GLOB.mob_list)
+/client/proc/give_spell(mob/spell_recipient in GLOB.mob_list)
set category = "Admin.Player Interaction"
set name = "Give Spell"
set desc = "Gives a spell to a mob."
+ var/which = tgui_alert(usr, "Chose by name or by type path?", "Chose option", list("Name", "Typepath"))
+ if(!which)
+ return
+ if(QDELETED(spell_recipient))
+ to_chat(usr, span_warning("The intended spell recipient no longer exists."))
+ return
+
var/list/spell_list = list()
- 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
- if(!S)
+ for(var/datum/action/cooldown/spell/to_add as anything in subtypesof(/datum/action/cooldown/spell))
+ var/spell_name = initial(to_add.name)
+ if(spell_name == "Spell") // abstract or un-named spells should be skipped.
+ continue
+
+ if(which == "Name")
+ spell_list[spell_name] = to_add
+ else
+ spell_list += to_add
+
+ var/chosen_spell = tgui_input_list(usr, "Choose the spell to give to [spell_recipient]", "ABRAKADABRA", sortList(spell_list))
+ if(isnull(chosen_spell))
+ return
+ var/datum/action/cooldown/spell/spell_path = which == "Typepath" ? chosen_spell : spell_list[chosen_spell]
+ if(!ispath(spell_path))
+ return
+
+ var/robeless = (tgui_alert(usr, "Would you like to force this spell to be robeless?", "Robeless Casting?", list("Force Robeless", "Use Spell Setting")) == "Force Robeless")
+
+ if(QDELETED(spell_recipient))
+ to_chat(usr, span_warning("The intended spell recipient no longer exists."))
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(span_adminnotice("[key_name_admin(usr)] gave [key_name_admin(T)] the spell [S]."))
+ log_admin("[key_name(usr)] gave [key_name(spell_recipient)] the spell [chosen_spell][robeless ? " (Forced robeless)" : ""].")
+ message_admins("[key_name_admin(usr)] gave [key_name_admin(spell_recipient)] the spell [chosen_spell][robeless ? " (Forced robeless)" : ""].")
- S = spell_list[S]
- if(T.mind)
- T.mind.AddSpell(new S)
- else
- T.AddSpell(new S)
- message_admins(span_danger("Spells given to mindless mobs will not be transferred in mindswap or cloning!"))
+ var/datum/action/cooldown/spell/new_spell = new spell_path(spell_recipient.mind || spell_recipient)
-/client/proc/remove_spell(mob/T in GLOB.mob_list)
+ if(robeless)
+ new_spell.spell_requirements &= ~SPELL_REQUIRES_WIZARD_GARB
+
+ new_spell.Grant(spell_recipient)
+
+ if(!spell_recipient.mind)
+ to_chat(usr, span_userdanger("Spells given to mindless mobs will belong to the mob and not their mind, \
+ and as such will not be transferred if their mind changes body (Such as from Mindswap)."))
+
+/client/proc/remove_spell(mob/removal_target in GLOB.mob_list)
set category = "Admin.Player Interaction"
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(S)
- T.mind.RemoveSpell(S)
- log_admin("[key_name(usr)] removed the spell [S] from [key_name(T)].")
- message_admins(span_adminnotice("[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!
+ var/list/target_spell_list = list()
+ for(var/datum/action/cooldown/spell/spell in removal_target.actions)
+ target_spell_list[spell.name] = spell
+
+ if(!length(target_spell_list))
+ return
+
+ var/chosen_spell = tgui_input_list(usr, "Choose the spell to remove from [removal_target]", "ABRAKADABRA", sortList(target_spell_list))
+ if(isnull(chosen_spell))
+ return
+ var/datum/action/cooldown/spell/to_remove = target_spell_list[chosen_spell]
+ if(!istype(to_remove))
+ return
+
+ qdel(to_remove)
+ log_admin("[key_name(usr)] removed the spell [chosen_spell] from [key_name(removal_target)].")
+ message_admins("[key_name_admin(usr)] removed the spell [chosen_spell] from [key_name_admin(removal_target)].")
+ 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)
set category = "Admin.Player Interaction"
@@ -666,7 +705,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
if(!holder)
return
- if(has_antag_hud())
+ if(combo_hud_enabled)
toggle_combo_hud()
holder.deactivate()
@@ -799,3 +838,41 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set category = "Misc.Server Debug"
src << output("", "statbrowser:create_debug")
+
+/// Debug verb for seeing at a glance what all spells have as set requirements
+/client/proc/debug_spell_requirements()
+ set name = "Show Spell Requirements"
+ set category = "Debug"
+
+ var/header = "| Name | Requirements | "
+ var/all_requirements = list()
+ for(var/datum/action/cooldown/spell/spell as anything in typesof(/datum/action/cooldown/spell))
+ if(initial(spell.name) == "Spell")
+ continue
+
+ var/list/real_reqs = list()
+ var/reqs = initial(spell.spell_requirements)
+ if(reqs & SPELL_CASTABLE_AS_BRAIN)
+ real_reqs += "Castable as brain"
+ if(reqs & SPELL_CASTABLE_WHILE_PHASED)
+ real_reqs += "Castable phased"
+ if(reqs & SPELL_REQUIRES_HUMAN)
+ real_reqs += "Must be human"
+ if(reqs & SPELL_REQUIRES_MIME_VOW)
+ real_reqs += "Must be miming"
+ if(reqs & SPELL_REQUIRES_MIND)
+ real_reqs += "Must have a mind"
+ if(reqs & SPELL_REQUIRES_NO_ANTIMAGIC)
+ real_reqs += "Must have no antimagic"
+ if(reqs & SPELL_REQUIRES_STATION)
+ real_reqs += "Must be off central command z-level"
+ if(reqs & SPELL_REQUIRES_WIZARD_GARB)
+ real_reqs += "Must have wizard clothes"
+
+ all_requirements += "
|---|
| [initial(spell.name)] | [english_list(real_reqs, "No requirements")] |
"
+
+ var/page_style = ""
+ var/page_contents = "[page_style][header][jointext(all_requirements, "")]
"
+ var/datum/browser/popup = new(mob, "spellreqs", "Spell Requirements", 600, 400)
+ popup.set_content(page_contents)
+ popup.open()
diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm
index 69ccfba383a6..8f656ae4074f 100644
--- a/code/modules/admin/fun_balloon.dm
+++ b/code/modules/admin/fun_balloon.dm
@@ -107,7 +107,7 @@
/obj/effect/forcefield/arena_shuttle
name = "portal"
- timeleft = 0
+ initial_duration = 0
var/list/warp_points = list()
/obj/effect/forcefield/arena_shuttle/Bumped(atom/movable/AM)
@@ -124,7 +124,7 @@
qdel(L.pulling)
var/turf/LA = get_turf(pick(warp_points))
L.forceMove(LA)
- L.hallucination = 0
+ L.remove_status_effect(/datum/status_effect/hallucination)
to_chat(L, "The battle is won. Your bloodlust subsides.")
for(var/obj/item/twohanded/required/chainsaw/doomslayer/chainsaw in L)
qdel(chainsaw)
@@ -142,7 +142,7 @@
/obj/effect/forcefield/arena_shuttle_entrance
name = "portal"
- timeleft = 0
+ initial_duration = 0
var/list/warp_points = list()
/obj/effect/forcefield/arena_shuttle_entrance/Bumped(atom/movable/AM)
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index 5510156c793b..b07dd97847fc 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -86,10 +86,13 @@ GLOBAL_PROTECT(href_token)
GLOB.permissions.deadmins[target] = src
GLOB.permissions.admin_datums -= target
deadmined = TRUE
- var/client/C
- if ((C = owner) || (C = GLOB.directory[target]))
+
+ var/client/client = owner || GLOB.directory[target]
+
+ if (!isnull(client))
disassociate()
- add_verb(C, /client/proc/readmin)
+ add_verb(client, /client/proc/readmin)
+ client.disable_combo_hud()
/datum/admins/proc/associate(client/C, allow_mfa_query = TRUE)
if(IsAdminAdvancedProcCall())
diff --git a/code/modules/admin/team_panel.dm b/code/modules/admin/team_panel.dm
index 40a2abd4ffbe..cccb9b6b158d 100644
--- a/code/modules/admin/team_panel.dm
+++ b/code/modules/admin/team_panel.dm
@@ -35,7 +35,7 @@
var/team_name = stripped_input(user,"Team name ?")
if(!team_name)
return
- var/datum/team/custom/T = new()
+ var/datum/team/T = new()
T.name = team_name
message_admins("[key_name_admin(usr)] created new [name] antagonist team.")
@@ -146,38 +146,3 @@
/datum/team/proc/get_admin_commands()
return list()
-//Custom team subtype created by the panel, allow forcing hud for the team for now
-/datum/team/custom
- var/datum/atom_hud/antag/custom_hud
- var/custom_hud_state = "traitor"
-
-/datum/team/custom/add_member(datum/mind/new_member)
- . = ..()
- if(custom_hud)
- custom_hud.join_hud(new_member.current)
- set_antag_hud(new_member.current,custom_hud_state)
-
-/datum/team/custom/remove_member(datum/mind/member)
- . = ..()
- if(custom_hud)
- custom_hud.leave_hud(member.current)
-
-/datum/team/custom/get_admin_commands()
- . = ..()
- .["Force HUD"] = CALLBACK(src, PROC_REF(admin_force_hud))
-
-//This is here if you want admin created teams to tell each other apart easily.
-/datum/team/custom/proc/admin_force_hud(mob/user)
- var/list/possible_icons = icon_states('icons/mob/hud.dmi')
- var/new_hud_state = input(user,"Choose hud icon state","Custom HUD","traitor") as null|anything in possible_icons
- if(!new_hud_state)
- return
- //suppose could ask for color too
- custom_hud_state = new_hud_state
- custom_hud = new
- custom_hud.self_visible = TRUE
- GLOB.huds += custom_hud //Make it show in admin hud
-
- for(var/datum/mind/M in members)
- custom_hud.join_hud(M.current)
- set_antag_hud(M.current,custom_hud_state)
diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm
index 1dbe8edd71bf..4e815d67b40a 100644
--- a/code/modules/admin/verbs/adminjump.dm
+++ b/code/modules/admin/verbs/adminjump.dm
@@ -15,7 +15,7 @@
continue
turfs.Add(T)
- var/turf/T = safepick(turfs)
+ var/turf/T = pick(turfs)
if(!T)
to_chat(src, "Nowhere to jump to!", confidential=TRUE)
return
diff --git a/code/modules/admin/verbs/bluespacearty.dm b/code/modules/admin/verbs/bluespacearty.dm
index 46f55dd7d926..6529d2615115 100644
--- a/code/modules/admin/verbs/bluespacearty.dm
+++ b/code/modules/admin/verbs/bluespacearty.dm
@@ -21,6 +21,6 @@
target.gib(1, 1)
else
target.adjustBruteLoss(min(99,(target.health - 1)))
- target.Paralyze(400)
- target.stuttering = 20
+ target.Paralyze(40 SECONDS)
+ target.set_stutter_if_lower(20 SECONDS)
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index e0b82e8a04ad..31990ad0b82f 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -20,7 +20,7 @@
/* 21st Sept 2010
Updated by Skie -- Still not perfect but better!
Stuff you can't do:
-Call proc /mob/proc/Dizzy() for some player
+Call proc /mob/proc/adjust_dizzy() for some player
Because if you select a player mob as owner it tries to do the proc for
/mob/living/carbon/human/ instead. And that gives a run-time error.
But you can call procs that are of type /mob/living/carbon/human/proc/ for that player.
@@ -286,34 +286,6 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
spawn(0)
M.Animalize()
-
-/client/proc/makepAI(turf/T in GLOB.mob_list)
- set category = "Admin.Player Interaction"
- set name = "Make pAI"
- set desc = "Specify a location to spawn a pAI device, then specify a key to play that pAI"
-
- var/list/available = list()
- for(var/mob/C in GLOB.mob_list)
- if(C.key)
- available.Add(C)
- var/mob/choice = input(usr, "Choose a player to play the pAI", "Spawn pAI", sortNames(available))
- if(!choice)
- return 0
- if(!isobserver(choice))
- var/confirm = tgui_alert(usr, "[choice.key] isn't ghosting right now. Are you sure you want to yank him out of them out of their body and place them in this pAI?", "Spawn pAI Confirmation", list("Yes", "No"))
- if(confirm != "Yes")
- return 0
- var/obj/item/paicard/card = new(T)
- var/mob/living/silicon/pai/pai = new(card)
- pai.name = input(choice, "Enter your pAI name:", "pAI Name", "Personal AI") as text
- pai.real_name = pai.name
- pai.key = choice.key
- card.setPersonality(pai)
- for(var/datum/paiCandidate/candidate in SSpai.candidates)
- if(candidate.key == choice.key)
- SSpai.candidates.Remove(candidate)
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Make pAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
/client/proc/cmd_admin_alienize(mob/M in GLOB.mob_list)
set category = "Admin.Player Interaction"
set name = "Make Alien"
@@ -611,7 +583,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
for(var/area/A as anything in GLOB.areas)
if(on_station)
- var/turf/picked = safepick(get_area_turfs(A.type))
+ var/turf/picked = pick(get_area_turfs(A.type))
if(picked && is_station_level(picked.z))
if(!(A.type in areas_all) && !is_type_in_typecache(A, station_areas_blacklist))
areas_all.Add(A.type)
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index ade348a1cd60..50be2306a7ea 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -971,32 +971,45 @@ Traitors and the like can also be revived with the previous role mostly intact.
if(!check_rights(R_ADMIN))
return
- var/adding_hud = !has_antag_hud()
+ if (combo_hud_enabled)
+ disable_combo_hud()
+ else
+ enable_combo_hud()
- for(var/hudtype in list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED)) // add data huds
- var/datum/atom_hud/H = GLOB.huds[hudtype]
- (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr)
- for(var/datum/atom_hud/antag/H in GLOB.huds) // add antag huds
- (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr)
+ to_chat(usr, "You toggled your admin combo HUD [combo_hud_enabled ? "ON" : "OFF"].", confidential = TRUE)
+ message_admins("[key_name_admin(usr)] toggled their admin combo HUD [combo_hud_enabled ? "ON" : "OFF"].")
+ log_admin("[key_name(usr)] toggled their admin combo HUD [combo_hud_enabled ? "ON" : "OFF"].")
+ SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Combo HUD", "[combo_hud_enabled ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- if(prefs.toggles & COMBOHUD_LIGHTING)
- if(adding_hud)
- mob.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
- else
- mob.lighting_alpha = initial(mob.lighting_alpha)
+/client/proc/enable_combo_hud()
+ if (combo_hud_enabled)
+ return
+
+ combo_hud_enabled = TRUE
+
+ for (var/hudtype in list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED))
+ var/datum/atom_hud/atom_hud = GLOB.huds[hudtype]
+ atom_hud.show_to(mob)
+
+ for (var/datum/atom_hud/alternate_appearance/basic/antagonist_hud/antag_hud in GLOB.active_alternate_appearances)
+ antag_hud.show_to(mob)
mob.update_sight()
- to_chat(usr, "You toggled your admin combo HUD [adding_hud ? "ON" : "OFF"].", confidential=TRUE)
- message_admins("[key_name_admin(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].")
- log_admin("[key_name(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].")
- SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Combo HUD", "[adding_hud ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+/client/proc/disable_combo_hud()
+ if (!combo_hud_enabled)
+ return
+
+ combo_hud_enabled = FALSE
+ for (var/hudtype in list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED))
+ var/datum/atom_hud/atom_hud = GLOB.huds[hudtype]
+ atom_hud.hide_from(mob)
-/client/proc/has_antag_hud()
- var/datum/atom_hud/A = GLOB.huds[ANTAG_HUD_TRAITOR]
- return A.hudusers[mob]
+ for (var/datum/atom_hud/alternate_appearance/basic/antagonist_hud/antag_hud in GLOB.active_alternate_appearances)
+ antag_hud.hide_from(mob)
+ mob.update_sight()
/client/proc/run_weather()
set category = "Admin.Round Interaction"
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index cd562e987692..25cc532db5cd 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -16,6 +16,10 @@ GLOBAL_LIST_EMPTY(antagonists)
var/task_memory = ""//Optional little objectives that are to be removed on a certain milestone
var/antag_moodlet //typepath of moodlet that the mob will gain with their status
var/can_hijack = HIJACK_NEUTRAL //If these antags are alone on shuttle hijack happens.
+ ///The antag hud's icon file
+ var/hud_icon = 'yogstation/icons/mob/antag_hud.dmi'
+ ///Name of the antag hud we provide to this mob.
+ var/antag_hud_name
//Antag panel properties
var/show_in_antagpanel = TRUE //This will hide adding this antag type in antag panel, use only for internal subtypes that shouldn't be added directly but still show if possessed by mind
@@ -28,6 +32,15 @@ GLOBAL_LIST_EMPTY(antagonists)
/// The typepath for the outfit to show in the preview for the preferences menu.
var/preview_outfit
+ //ANTAG UI
+
+ ///name of the UI that will try to open, right now using a generic ui
+ var/ui_name = "AntagInfoGeneric"
+ ///weakref to button to access antag interface
+ var/datum/weakref/info_button_ref
+ /// A weakref to the HUD shown to teammates, created by `add_team_hud`
+ var/datum/weakref/team_hud_ref
+
/datum/antagonist/New()
GLOB.antagonists += src
@@ -37,6 +50,7 @@ GLOBAL_LIST_EMPTY(antagonists)
GLOB.antagonists -= src
if(owner)
LAZYREMOVE(owner.antag_datums, src)
+ QDEL_NULL(team_hud_ref)
owner = null
return ..()
@@ -61,6 +75,10 @@ GLOBAL_LIST_EMPTY(antagonists)
remove_innate_effects(old_body)
if(old_body && old_body.stat != DEAD && !LAZYLEN(old_body.mind?.antag_datums))
old_body.remove_from_current_living_antags()
+ var/datum/action/antag_info/info_button = info_button_ref?.resolve()
+ if(info_button)
+ info_button.Remove(old_body)
+ info_button.Grant(new_body)
apply_innate_effects(new_body)
if(new_body.stat != DEAD)
new_body.add_to_current_living_antags()
@@ -71,8 +89,21 @@ GLOBAL_LIST_EMPTY(antagonists)
//This handles the removal of antag huds/special abilities
/datum/antagonist/proc/remove_innate_effects(mob/living/mob_override)
+ handle_clown_mutation(mob_override || owner.current, removing = FALSE)
return
+/// Handles adding and removing the clumsy mutation from clown antags. Gets called in apply/remove_innate_effects
+/datum/antagonist/proc/handle_clown_mutation(mob/living/mob_override, message, removing = TRUE)
+ if(!ishuman(mob_override) || owner.assigned_role != "Clown")
+ return
+ var/mob/living/carbon/human/human_override = mob_override
+ if(removing) // They're a clown becoming an antag, remove clumsy
+ human_override.dna.remove_mutation(CLOWNMUT)
+ if(!silent && message)
+ to_chat(human_override, span_boldnotice("[message]"))
+ else
+ human_override.dna.add_mutation(CLOWNMUT) // We're removing their antag status, add back clumsy
+
//Assign default team and creates one for one of a kind team antagonists
/datum/antagonist/proc/create_team(datum/team/team)
return
@@ -80,12 +111,21 @@ GLOBAL_LIST_EMPTY(antagonists)
//Called by the add_antag_datum() mind proc after the instanced datum is added to the mind's antag_datums list.
/datum/antagonist/proc/on_gain()
SHOULD_CALL_PARENT(TRUE)
+ var/datum/action/antag_info/info_button
if(!owner)
CRASH("[src] ran on_gain() without a mind")
if(!owner.current)
CRASH("[src] ran on_gain() on a mind without a mob")
- greet()
- apply_innate_effects(owner.current)
+ if(ui_name)//in the future, this should entirely replace greet.
+ info_button = new(src)
+ info_button.Grant(owner.current)
+ info_button_ref = WEAKREF(info_button)
+ if(!silent)
+ greet()
+ if(ui_name)
+ to_chat(owner.current, span_boldnotice("For more info, read the panel. you can always come back to it using the button in the top left."))
+ info_button.Trigger()
+ apply_innate_effects()
give_antag_moodies()
if(is_banned(owner.current) && replace_banned)
replace_banned_player()
@@ -93,6 +133,8 @@ GLOBAL_LIST_EMPTY(antagonists)
owner.current.client.holder.auto_deadmin()
if(owner.current.stat != DEAD)
owner.current.add_to_current_living_antags()
+
+ SEND_SIGNAL(owner, COMSIG_ANTAGONIST_GAINED, src)
/datum/antagonist/proc/is_banned(mob/M)
if(!M)
@@ -117,17 +159,29 @@ GLOBAL_LIST_EMPTY(antagonists)
//Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion.
/datum/antagonist/proc/on_removal()
SHOULD_CALL_PARENT(TRUE)
- remove_innate_effects(owner.current)
+ if(!owner)
+ CRASH("Antag datum with no owner.")
+
+ remove_innate_effects()
clear_antag_moodies()
- if(owner)
- LAZYREMOVE(owner.antag_datums, src)
- if(!LAZYLEN(owner.antag_datums))
- owner.current.remove_from_current_living_antags()
- if(!silent && owner.current)
- farewell()
+ LAZYREMOVE(owner.antag_datums, src)
+ if(!LAZYLEN(owner.antag_datums))
+ owner.current.remove_from_current_living_antags()
+ if(info_button_ref)
+ QDEL_NULL(info_button_ref)
+ if(!silent && owner.current)
+ farewell()
var/datum/team/team = get_team()
if(team)
team.remove_member(owner)
+ SEND_SIGNAL(owner, COMSIG_ANTAGONIST_REMOVED, src)
+
+ // Remove HUDs that they should no longer see
+ var/mob/living/current = owner.current
+ for (var/datum/atom_hud/alternate_appearance/basic/has_antagonist/antag_hud as anything in GLOB.has_antagonist_huds)
+ if (!antag_hud.mobShouldSee(current))
+ antag_hud.hide_from(current)
+
qdel(src)
/datum/antagonist/proc/greet()
@@ -288,13 +342,36 @@ GLOBAL_LIST_EMPTY(antagonists)
antag_memory = new_memo
task_memory = new_memo
+/// Adds a HUD that will show you other members with the same antagonist.
+/// If an antag typepath is passed to `antag_to_check`, will check that, otherwise will use the source type.
+/datum/antagonist/proc/add_team_hud(mob/target, antag_to_check)
+ QDEL_NULL(team_hud_ref)
+
+ team_hud_ref = WEAKREF(target.add_alt_appearance(
+ /datum/atom_hud/alternate_appearance/basic/has_antagonist,
+ "antag_team_hud_[REF(src)]",
+ hud_image_on(target),
+ antag_to_check || type,
+ ))
+
+ // Add HUDs that they couldn't see before
+ for (var/datum/atom_hud/alternate_appearance/basic/has_antagonist/antag_hud as anything in GLOB.has_antagonist_huds)
+ if (antag_hud.mobShouldSee(owner.current))
+ antag_hud.show_to(owner.current)
+
+/// Takes a location, returns an image drawing "on" it that matches this antag datum's hud icon
+/datum/antagonist/proc/hud_image_on(mob/hud_loc)
+ var/image/hud = image(hud_icon, hud_loc, antag_hud_name)
+ hud.plane = ABOVE_HUD_PLANE //not quite but needed
+ return hud
+
//This one is created by admin tools for custom objectives
/datum/antagonist/custom
antagpanel_category = "Custom"
show_name_in_check_antagonists = TRUE //They're all different
var/datum/team/custom_team
-datum/antagonist/custom/create_team(datum/team/team)
+/datum/antagonist/custom/create_team(datum/team/team)
custom_team = team
/datum/antagonist/custom/get_team()
@@ -307,3 +384,68 @@ datum/antagonist/custom/create_team(datum/team/team)
else
return
..()
+
+///ANTAGONIST UI STUFF
+
+/datum/antagonist/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, ui_name, name)
+ ui.open()
+
+/datum/antagonist/ui_state(mob/user)
+ return GLOB.always_state
+
+///generic helper to send objectives as data through tgui.
+/datum/antagonist/proc/get_objectives()
+ var/objective_count = 1
+ var/list/objective_data = list()
+ //all obj
+ for(var/datum/objective/objective in objectives)
+ objective_data += list(list(
+ "count" = objective_count,
+ "name" = objective.objective_name,
+ "explanation" = objective.explanation_text,
+ "complete" = objective.completed,
+ ))
+ objective_count++
+ return objective_data
+
+/datum/antagonist/ui_static_data(mob/user)
+ var/list/data = list()
+ data["antag_name"] = name
+ data["objectives"] = get_objectives()
+ return data
+
+//button for antags to review their descriptions/info
+
+/datum/action/antag_info
+ name = "Open Antag Information:"
+ button_icon_state = "info"
+ show_to_observers = FALSE
+
+/datum/action/antag_info/New(Target)
+ . = ..()
+ if(!button_icon_state)
+ button_icon_state = initial(button_icon_state)
+ name += " [target]"
+
+/datum/action/antag_info/Trigger()
+ . = ..()
+ if(!.)
+ return
+
+ target.ui_interact(owner)
+
+/datum/action/antag_info/IsAvailable(feedback = FALSE)
+ if(!target)
+ stack_trace("[type] was used without a target antag datum!")
+ return FALSE
+ . = ..()
+ if(!.)
+ return
+ if(!owner.mind)
+ return FALSE
+ if(!(target in owner.mind.antag_datums))
+ return FALSE
+ return TRUE
diff --git a/code/modules/antagonists/_common/antag_helpers.dm b/code/modules/antagonists/_common/antag_helpers.dm
index d99920b9e219..5d48ec944097 100644
--- a/code/modules/antagonists/_common/antag_helpers.dm
+++ b/code/modules/antagonists/_common/antag_helpers.dm
@@ -1,5 +1,6 @@
//Returns MINDS of the assigned antags of given type/subtypes
/proc/get_antag_minds(antag_type,specific = FALSE)
+ RETURN_TYPE(/list/datum/mind)
. = list()
for(var/datum/antagonist/A in GLOB.antagonists)
if(!A.owner)
@@ -16,4 +17,4 @@
continue
var/datum/team/T = A.get_team()
if(!team_type || istype(T,team_type))
- . |= T
\ No newline at end of file
+ . |= T
diff --git a/code/modules/antagonists/_common/antag_hud.dm b/code/modules/antagonists/_common/antag_hud.dm
index de6d0a4f819a..9fcca9388dbf 100644
--- a/code/modules/antagonists/_common/antag_hud.dm
+++ b/code/modules/antagonists/_common/antag_hud.dm
@@ -1,53 +1,92 @@
-/datum/atom_hud/antag
- hud_icons = list(ANTAG_HUD)
- var/self_visible = TRUE
-
-/datum/atom_hud/antag/hidden
- self_visible = FALSE
-
-/datum/atom_hud/antag/proc/join_hud(mob/M)
- //sees_hud should be set to 0 if the mob does not get to see it's own hud type.
- if(!istype(M))
- CRASH("join_hud(): [M] ([M.type]) is not a mob!")
- if(M.mind.antag_hud) //note: please let this runtime if a mob has no mind, as mindless mobs shouldn't be getting antagged
- M.mind.antag_hud.leave_hud(M)
- add_to_hud(M)
- if(self_visible)
- add_hud_to(M)
- M.mind.antag_hud = src
-
-/datum/atom_hud/antag/proc/leave_hud(mob/M)
- if(!M)
- return
- if(!istype(M))
- CRASH("leave_hud(): [M] ([M.type]) is not a mob!")
- remove_from_hud(M)
- remove_hud_from(M)
- if(M.mind)
- M.mind.antag_hud = null
-
-
-//GAME_MODE PROCS
-//called to set a mob's antag icon state
-/proc/set_antag_hud(mob/M, new_icon_state)
- if(!istype(M))
- CRASH("set_antag_hud(): [M] ([M.type]) is not a mob!")
- var/image/holder = M.hud_list[ANTAG_HUD]
- if(holder)
- holder.icon_state = new_icon_state
- if(M.mind || new_icon_state) //in mindless mobs, only null is acceptable, otherwise we're antagging a mindless mob, meaning we should runtime
- M.mind.antag_hud_icon_state = new_icon_state
-
-
-//MIND PROCS
-//these are called by mind.transfer_to()
-/datum/mind/proc/transfer_antag_huds(datum/atom_hud/antag/newhud)
- leave_all_antag_huds()
- set_antag_hud(current, antag_hud_icon_state)
- if(newhud)
- newhud.join_hud(current)
-
-/datum/mind/proc/leave_all_antag_huds()
- for(var/datum/atom_hud/antag/hud in GLOB.huds)
- if(hud.hudusers[current])
- hud.leave_hud(current)
\ No newline at end of file
+/// All active /datum/atom_hud/alternate_appearance/basic/has_antagonist instances
+GLOBAL_LIST_EMPTY_TYPED(has_antagonist_huds, /datum/atom_hud/alternate_appearance/basic/has_antagonist)
+
+/// An alternate appearance that will only show if you have the antag datum
+/datum/atom_hud/alternate_appearance/basic/has_antagonist
+ var/antag_datum_type
+
+/datum/atom_hud/alternate_appearance/basic/has_antagonist/New(key, image/I, antag_datum_type)
+ src.antag_datum_type = antag_datum_type
+ GLOB.has_antagonist_huds += src
+ return ..(key, I, NONE)
+
+/datum/atom_hud/alternate_appearance/basic/has_antagonist/Destroy()
+ GLOB.has_antagonist_huds -= src
+ return ..()
+
+/datum/atom_hud/alternate_appearance/basic/has_antagonist/mobShouldSee(mob/M)
+ return !!M.mind?.has_antag_datum(antag_datum_type)
+
+/// An alternate appearance that will show all the antagonists this mob has
+/datum/atom_hud/alternate_appearance/basic/antagonist_hud
+ var/list/antag_hud_images = list()
+ var/index = 1
+
+ var/datum/mind/mind
+
+/datum/atom_hud/alternate_appearance/basic/antagonist_hud/New(key, datum/mind/mind)
+ src.mind = mind
+
+ antag_hud_images = get_antag_hud_images(mind)
+
+ var/image/first_antagonist = get_antag_image(1) || image(icon('icons/blanks/32x32.dmi', "nothing"), mind.current)
+
+ RegisterSignals(
+ mind,
+ list(COMSIG_ANTAGONIST_GAINED, COMSIG_ANTAGONIST_REMOVED),
+ PROC_REF(update_antag_hud_images)
+ )
+
+ check_processing()
+
+ return ..(key, first_antagonist, NONE)
+
+/datum/atom_hud/alternate_appearance/basic/antagonist_hud/Destroy()
+ QDEL_LIST(antag_hud_images)
+ STOP_PROCESSING(SSantag_hud, src)
+ mind.antag_hud = null
+ mind = null
+
+ return ..()
+
+/datum/atom_hud/alternate_appearance/basic/antagonist_hud/mobShouldSee(mob/mob)
+ return Master.current_runlevel >= RUNLEVEL_POSTGAME || (mob.client?.combo_hud_enabled && !isnull(mob.client?.holder))
+
+/datum/atom_hud/alternate_appearance/basic/antagonist_hud/process(delta_time)
+ index += 1
+ update_icon()
+
+/datum/atom_hud/alternate_appearance/basic/antagonist_hud/proc/check_processing()
+ if (antag_hud_images.len > 1 && !(DF_ISPROCESSING in datum_flags))
+ START_PROCESSING(SSantag_hud, src)
+ else if (antag_hud_images.len <= 1)
+ STOP_PROCESSING(SSantag_hud, src)
+
+/datum/atom_hud/alternate_appearance/basic/antagonist_hud/proc/get_antag_image(index)
+ RETURN_TYPE(/image)
+ if (antag_hud_images.len)
+ return antag_hud_images[(index % antag_hud_images.len) + 1]
+
+/datum/atom_hud/alternate_appearance/basic/antagonist_hud/proc/get_antag_hud_images(datum/mind/mind)
+ var/list/final_antag_hud_images = list()
+
+ for (var/datum/antagonist/antagonist as anything in mind?.antag_datums)
+ if (isnull(antagonist.antag_hud_name))
+ continue
+ final_antag_hud_images += antagonist.hud_image_on(mind.current)
+
+ return final_antag_hud_images
+
+/datum/atom_hud/alternate_appearance/basic/antagonist_hud/proc/update_icon()
+ if (antag_hud_images.len == 0)
+ image.icon = icon('icons/blanks/32x32.dmi', "nothing")
+ else
+ image.icon = icon(get_antag_image(index).icon, get_antag_image(index).icon_state)
+
+/datum/atom_hud/alternate_appearance/basic/antagonist_hud/proc/update_antag_hud_images(datum/mind/source)
+ SIGNAL_HANDLER
+
+ antag_hud_images = get_antag_hud_images(source)
+ index = clamp(index, 1, antag_hud_images.len)
+ update_icon()
+ check_processing()
diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm
index 8ca976860567..83714f146c31 100644
--- a/code/modules/antagonists/_common/antag_spawner.dm
+++ b/code/modules/antagonists/_common/antag_spawner.dm
@@ -4,18 +4,12 @@
w_class = WEIGHT_CLASS_TINY
var/used = FALSE
-/obj/item/antag_spawner/ComponentInitialize()
- . = ..()
- RegisterSignal(src, COMSIG_ITEM_REFUND, PROC_REF(refund_check))
-
/obj/item/antag_spawner/proc/spawn_antag(client/C, turf/T, kind = "", datum/mind/user)
return
/obj/item/antag_spawner/proc/equip_antag(mob/target)
return
-/obj/item/antag_spawner/proc/refund_check()
- return !used
///////////WIZARD
@@ -24,68 +18,55 @@
desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid."
icon = 'icons/obj/wizard.dmi'
icon_state ="scroll2"
+ var/polling = FALSE
- var/unlocked = FALSE
+/obj/item/antag_spawner/contract/can_interact(mob/user)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(polling)
+ balloon_alert(user, "already calling an apprentice!")
+ return FALSE
-/obj/item/antag_spawner/contract/unlocked
- unlocked = TRUE
+/obj/item/antag_spawner/contract/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ApprenticeContract", name)
+ ui.open()
-/obj/item/antag_spawner/contract/attack_self(mob/user)
- if(!unlocked && !user.mind.has_antag_datum(/datum/antagonist/wizard))
- to_chat(user, span_warning("You do not understand the words on this paper."))
- return
- user.set_machine(src)
- var/dat = ""
+/obj/item/antag_spawner/contract/ui_state(mob/user)
if(used)
- dat = "You have already summoned your apprentice.
"
- else
- dat = "Contract of Apprenticeship:
"
- dat += "Using this contract, you may summon an apprentice to aid you on your mission.
"
- dat += "If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.
"
- dat += "Which school of magic is your apprentice studying?:
"
- dat += "Destruction
"
- dat += "Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.
"
- dat += "Bluespace Manipulation
"
- dat += "Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.
"
- dat += "Healing
"
- dat += "Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.
"
- dat += "Robeless
"
- dat += "Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.
"
-
- dat += ""
- user << browse(dat, "window=radio")
- onclose(user, "radio")
- return
+ return GLOB.never_state
+ return GLOB.default_state
-/obj/item/antag_spawner/contract/Topic(href, href_list)
- ..()
- var/mob/living/carbon/human/H = usr
+/obj/item/antag_spawner/contract/ui_assets(mob/user)
+ . = ..()
+ return list(
+ get_asset_datum(/datum/asset/simple/contracts),
+ )
- if(H.stat || H.restrained())
+/obj/item/antag_spawner/contract/ui_act(action, list/params)
+ . = ..()
+ if(used || polling || !ishuman(usr))
+ return
+ INVOKE_ASYNC(src, PROC_REF(poll_for_student), usr, params["school"])
+ SStgui.close_uis(src)
+
+/obj/item/antag_spawner/contract/proc/poll_for_student(mob/living/carbon/human/teacher, apprentice_school)
+ balloon_alert(teacher, "contacting apprentice...")
+ polling = TRUE
+ var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [apprentice_school] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 15 SECONDS, src)
+ polling = FALSE
+ if(!LAZYLEN(candidates))
+ to_chat(teacher, span_warning("Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later."))
return
- if(!ishuman(H))
- return 1
-
- if(loc == H || (in_range(src, H) && isturf(loc)))
- H.set_machine(src)
- if(href_list["school"])
- if(used)
- to_chat(H, "You already used this contract!")
- return
- var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [href_list["school"]] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 150, src)
- if(LAZYLEN(candidates))
- if(QDELETED(src))
- return
- if(used)
- to_chat(H, "You already used this contract!")
- return
- used = TRUE
- var/mob/dead/observer/C = pick(candidates)
- spawn_antag(C.client, get_turf(src), href_list["school"],H.mind)
- else
- to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.")
-
-/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind ,datum/mind/user)
+ if(QDELETED(src) || used)
+ return
+ used = TRUE
+ var/mob/dead/observer/student = pick(candidates)
+ spawn_antag(student.client, get_turf(src), apprentice_school, teacher.mind)
+
+/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind, datum/mind/user)
new /obj/effect/particle_effect/fluid/smoke(T)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.apply_prefs_to(M)
@@ -103,10 +84,8 @@
app.wiz_team = master_wizard.wiz_team
master_wizard.wiz_team.add_member(app_mind)
app_mind.add_antag_datum(app)
- //TODO Kill these if possible
app_mind.assigned_role = "Apprentice"
app_mind.special_role = "apprentice"
- //
SEND_SOUND(M, sound('sound/effects/magic.ogg'))
///////////BORGS AND OPERATIVES
@@ -274,17 +253,16 @@
/obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, kind = "", datum/mind/user)
- var/obj/effect/dummy/crawling/holder = new /obj/effect/dummy/crawling(T) //yogs start
- //var/obj/effect/dummy/phased_mob/holder = new /obj/effect/dummy/phased_mob(T)
- var/mob/living/simple_animal/slaughter/S = new demon_type(holder)
- //S.holder = holder //yogs end
+ var/mob/living/simple_animal/slaughter/S = new demon_type(T)
+ new /obj/effect/dummy/phased_mob(T, S)
+
S.key = C.key
S.mind.assigned_role = S.name
S.mind.special_role = S.name
S.mind.add_antag_datum(antag_type)
to_chat(S, S.playstyle_string)
- to_chat(S, "You are currently not in the same plane of existence as the station. \
- Alt+Click a blood pool to manifest.") //yogs
+ to_chat(S, span_bold("You are currently not currently in the same plane of existence as the station. \
+ Use your Blood Crawl ability near a pool of blood to manifest and wreak havoc.")) //fuck you
/obj/item/antag_spawner/slaughter_demon/laughter
name = "vial of tickles"
diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm
index 185a144886e7..1af357fb8fe7 100644
--- a/code/modules/antagonists/abductor/abductor.dm
+++ b/code/modules/antagonists/abductor/abductor.dm
@@ -5,6 +5,7 @@
roundend_category = "abductors"
antagpanel_category = "Abductor"
job_rank = ROLE_ABDUCTOR
+ antag_hud_name = "abductor"
show_in_antagpanel = FALSE //should only show subtypes
show_to_ghosts = TRUE
var/datum/team/abductor_team/team
@@ -97,8 +98,6 @@
H.forceMove(LM.loc)
break
- update_abductor_icons_added(owner,"abductor")
-
/datum/antagonist/abductor/scientist/on_gain()
ADD_TRAIT(owner.current, TRAIT_ABDUCTOR_SCIENTIST_TRAINING, ABDUCTOR_ANTAGONIST)
ADD_TRAIT(owner.current, TRAIT_SURGEON, ABDUCTOR_ANTAGONIST)
@@ -184,6 +183,7 @@
name = "Abductee"
roundend_category = "abductees"
antagpanel_category = "Abductee"
+ antag_hud_name = "abductee"
/datum/antagonist/abductee/on_gain()
give_objective()
@@ -202,13 +202,6 @@
var/datum/objective/abductee/O = new objtype()
objectives += O
-/datum/antagonist/abductee/apply_innate_effects(mob/living/mob_override)
- update_abductor_icons_added(mob_override ? mob_override.mind : owner,"abductee")
-
-/datum/antagonist/abductee/remove_innate_effects(mob/living/mob_override)
- update_abductor_icons_removed(mob_override ? mob_override.mind : owner)
-
-
// LANDMARKS
/obj/effect/landmark/abductor
var/team_number = 1
@@ -235,13 +228,3 @@
if(E.team_number == T.team_number)
return E.points >= target_amount
return FALSE
-
-/datum/antagonist/proc/update_abductor_icons_added(datum/mind/alien_mind,hud_type)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_ABDUCTOR]
- hud.join_hud(alien_mind.current)
- set_antag_hud(alien_mind.current, hud_type)
-
-/datum/antagonist/proc/update_abductor_icons_removed(datum/mind/alien_mind)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_ABDUCTOR]
- hud.leave_hud(alien_mind.current)
- set_antag_hud(alien_mind.current, null)
diff --git a/code/modules/antagonists/abductor/equipment/abduction_gear.dm b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
index 74b1628350eb..85de3d313956 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_gear.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
@@ -53,7 +53,7 @@
H.update_inv_wear_suit()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.build_all_button_icons()
/obj/item/clothing/suit/armor/abductor/vest/item_action_slot_check(slot, mob/user)
if(slot == SLOT_WEAR_SUIT) //we only give the mob the ability to activate the vest if he's actually wearing it.
@@ -273,7 +273,7 @@
radio_off_mob(M)
/obj/item/abductor/silencer/proc/radio_off_mob(mob/living/carbon/human/M)
- var/list/all_items = M.GetAllContents()
+ var/list/all_items = M.get_all_contents()
for(var/obj/I in all_items)
if(istype(I, /obj/item/radio/))
@@ -518,7 +518,7 @@ Congratulations! You are now trained for invasive xenobiology research!"}
to_chat(user, span_warning("The specimen's tinfoil protection is interfering with the sleep inducement!"))
L.visible_message(span_danger("[user] tried to induced sleep in [L] with [src], but [L.p_their()] tinfoil protection [L.p_them()]!"), \
span_userdanger("You feel a strange wave of heavy drowsiness wash over you, but your tinfoil protection deflects most of it!"))
- L.drowsyness += 2
+ L.adjust_drowsiness(2 SECONDS)
return
L.visible_message(span_danger("[user] has induced sleep in [L] with [src]!"), \
span_userdanger("You suddenly feel very drowsy!"))
@@ -531,7 +531,7 @@ Congratulations! You are now trained for invasive xenobiology research!"}
L.visible_message(span_danger("[user] tried to induce sleep in [L] with [src], but [L.p_their()] tinfoil protection completely protected [L.p_them()]!"), \
span_userdanger("Any sense of drowsiness is quickly diminished as your tinfoil protection deflects the effects!"))
return
- L.drowsyness += 1
+ L.adjust_drowsiness(1 SECONDS)
to_chat(user, span_warning("Sleep inducement works fully only on stunned specimens! "))
L.visible_message(span_danger("[user] tried to induce sleep in [L] with [src]!"), \
span_userdanger("You suddenly feel drowsy!"))
@@ -547,7 +547,7 @@ Congratulations! You are now trained for invasive xenobiology research!"}
span_userdanger("[user] begins shaping an energy field around your hands!"))
if(do_mob(user, C, 30) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()))
if(!C.handcuffed)
- C.handcuffed = new /obj/item/restraints/handcuffs/energy/used(C)
+ C.set_handcuffed(new /obj/item/restraints/handcuffs/energy/used(C))
C.update_handcuffed()
to_chat(user, span_notice("You restrain [C]."))
log_combat(user, C, "handcuffed")
diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm
index a6173dd10162..0aa4fa5d0dd6 100644
--- a/code/modules/antagonists/abductor/equipment/gland.dm
+++ b/code/modules/antagonists/abductor/equipment/gland.dm
@@ -80,7 +80,7 @@
if(initial(uses) == 1)
uses = initial(uses)
var/datum/atom_hud/abductor/hud = GLOB.huds[DATA_HUD_ABDUCTOR]
- hud.remove_from_hud(owner)
+ hud.remove_atom_from_hud(owner)
clear_mind_control()
..()
@@ -89,7 +89,7 @@
if(special != 2 && uses) // Special 2 means abductor surgery
Start()
var/datum/atom_hud/abductor/hud = GLOB.huds[DATA_HUD_ABDUCTOR]
- hud.add_to_hud(owner)
+ hud.add_atom_to_hud(owner)
update_gland_hud()
/obj/item/organ/heart/gland/on_life()
@@ -170,13 +170,13 @@
switch(pick(1,3))
if(1)
to_chat(H, span_userdanger("You hear a loud buzz in your head, silencing your thoughts!"))
- H.Stun(50)
+ H.Stun(5 SECONDS)
if(2)
to_chat(H, span_warning("You hear an annoying buzz in your head."))
- H.confused += 15
+ H.adjust_confusion(15 SECONDS)
H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 160)
if(3)
- H.hallucination += 60
+ H.adjust_hallucinations(1 MINUTES)
/obj/item/organ/heart/gland/mindshock/mind_control(command, mob/living/user)
if(!ownerCheck() || !mind_control_uses || active_mind_control)
diff --git a/code/modules/antagonists/abductor/machinery/camera.dm b/code/modules/antagonists/abductor/machinery/camera.dm
index 5c0878017d28..79cf5623ec04 100644
--- a/code/modules/antagonists/abductor/machinery/camera.dm
+++ b/code/modules/antagonists/abductor/machinery/camera.dm
@@ -60,7 +60,7 @@
/datum/action/innate/teleport_in
name = "Send To"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "beam_down"
/datum/action/innate/teleport_in/Activate()
@@ -75,7 +75,7 @@
/datum/action/innate/teleport_out
name = "Retrieve"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "beam_up"
/datum/action/innate/teleport_out/Activate()
@@ -87,7 +87,7 @@
/datum/action/innate/teleport_self
name = "Send Self"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "beam_down"
/datum/action/innate/teleport_self/Activate()
@@ -102,7 +102,7 @@
/datum/action/innate/vest_mode_swap
name = "Switch Vest Mode"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "vest_mode"
/datum/action/innate/vest_mode_swap/Activate()
@@ -114,7 +114,7 @@
/datum/action/innate/vest_disguise_swap
name = "Switch Vest Disguise"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "vest_disguise"
/datum/action/innate/vest_disguise_swap/Activate()
@@ -125,7 +125,7 @@
/datum/action/innate/set_droppoint
name = "Set Experiment Release Point"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "set_drop"
/datum/action/innate/set_droppoint/Activate()
diff --git a/code/modules/antagonists/ashwalker/ashwalker.dm b/code/modules/antagonists/ashwalker/ashwalker.dm
index f5cf8f89f687..6073694de410 100644
--- a/code/modules/antagonists/ashwalker/ashwalker.dm
+++ b/code/modules/antagonists/ashwalker/ashwalker.dm
@@ -24,19 +24,19 @@
/datum/antagonist/ashwalker/on_body_transfer(mob/living/old_body, mob/living/new_body)
. = ..()
UnregisterSignal(old_body, COMSIG_MOB_EXAMINATE)
- RegisterSignal(new_body, COMSIG_MOB_EXAMINATE, PROC_REF(on_examinate))
+ RegisterSignal(new_body, COMSIG_MOB_EXAMINATE, PROC_REF(on_examine))
/datum/antagonist/ashwalker/on_gain()
. = ..()
var/obj/item/book/granter/crafting_recipe/ashwalker/crafting_book
for(var/datum/crafting_recipe/R in crafting_book.crafting_recipe_types)
owner.teach_crafting_recipe(R)
- RegisterSignal(owner.current, COMSIG_MOB_EXAMINATE, PROC_REF(on_examinate))
+ RegisterSignal(owner.current, COMSIG_MOB_EXAMINATE, PROC_REF(on_examine))
/datum/antagonist/ashwalker/on_removal()
. = ..()
UnregisterSignal(owner.current, COMSIG_MOB_EXAMINATE)
-/datum/antagonist/ashwalker/proc/on_examinate(datum/source, atom/A)
+/datum/antagonist/ashwalker/proc/on_examine(datum/source, atom/A)
if(istype(A, /obj/structure/headpike))
SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "oogabooga", /datum/mood_event/sacrifice_good)
diff --git a/code/modules/antagonists/blob/blob.dm b/code/modules/antagonists/blob/blob.dm
index b7b533b212aa..d84fbe0ab5de 100644
--- a/code/modules/antagonists/blob/blob.dm
+++ b/code/modules/antagonists/blob/blob.dm
@@ -4,8 +4,10 @@
antagpanel_category = "Blob"
show_to_ghosts = TRUE
job_rank = ROLE_BLOB
-
+ ui_name = "AntagInfoBlob"
+ /// Action to release a blob infection
var/datum/action/innate/blobpop/pop_action
+ /// Initial points for a human blob
var/starting_points_human_blob = 60
var/point_rate_human_blob = 2
@@ -69,7 +71,7 @@
/datum/action/innate/blobpop
name = "Pop"
desc = "Unleash the blob"
- icon_icon = 'icons/mob/blob.dmi'
+ button_icon = 'icons/mob/blob.dmi'
button_icon_state = "blob"
var/autoplace_time = OVERMIND_STARTING_AUTO_PLACE_TIME
diff --git a/code/modules/antagonists/blob/blobstrains/blazing_oil.dm b/code/modules/antagonists/blob/blobstrains/blazing_oil.dm
index 750b5881d78c..b24801466bdd 100644
--- a/code/modules/antagonists/blob/blobstrains/blazing_oil.dm
+++ b/code/modules/antagonists/blob/blobstrains/blazing_oil.dm
@@ -39,7 +39,7 @@
/datum/reagent/blob/blazing_oil/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
M.adjust_fire_stacks(round(reac_volume/10))
- M.IgniteMob()
+ M.ignite_mob()
if(M)
M.apply_damage(0.8*reac_volume, BURN, wound_bonus=CANT_WOUND)
if(iscarbon(M))
diff --git a/code/modules/antagonists/blob/blobstrains/pressurized_slime.dm b/code/modules/antagonists/blob/blobstrains/pressurized_slime.dm
index bc49345f786e..5ca962a61af9 100644
--- a/code/modules/antagonists/blob/blobstrains/pressurized_slime.dm
+++ b/code/modules/antagonists/blob/blobstrains/pressurized_slime.dm
@@ -30,7 +30,7 @@
O.extinguish()
for(var/mob/living/L in T)
L.adjust_fire_stacks(-2.5)
- L.ExtinguishMob()
+ L.extinguish_mob()
/datum/reagent/blob/pressurized_slime
name = "Pressurized Slime"
@@ -43,9 +43,9 @@
if(istype(T) && prob(reac_volume))
T.MakeSlippery(TURF_WET_LUBE, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS)
M.adjust_fire_stacks(-(reac_volume / 10))
- M.ExtinguishMob()
+ M.extinguish_mob()
M.apply_damage(0.4*reac_volume, BRUTE, wound_bonus=CANT_WOUND)
if(M)
M.apply_damage(0.4*reac_volume, OXY)
if(M)
- M.adjustStaminaLoss(reac_volume)
\ No newline at end of file
+ M.adjustStaminaLoss(reac_volume)
diff --git a/code/modules/antagonists/blob/structures/_blob.dm b/code/modules/antagonists/blob/structures/_blob.dm
index a65b79e33b41..faab39fc9ded 100644
--- a/code/modules/antagonists/blob/structures/_blob.dm
+++ b/code/modules/antagonists/blob/structures/_blob.dm
@@ -309,7 +309,7 @@
/obj/structure/blob/examine(mob/user)
. = ..()
var/datum/atom_hud/hud_to_check = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- if(user.research_scanner || hud_to_check.hudusers[user])
+ if(user.research_scanner || hud_to_check.hud_users[user])
. += "Your HUD displays an extensive report...
"
if(overmind)
. += overmind.blobstrain.examine(user)
diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_assets.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_assets.dm
new file mode 100644
index 000000000000..77f8501eff1d
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/bloodsucker_assets.dm
@@ -0,0 +1,15 @@
+/datum/asset/simple/bloodsucker_icons
+
+/datum/asset/simple/bloodsucker_icons/register()
+ for(var/datum/bloodsucker_clan/clans as anything in typesof(/datum/bloodsucker_clan))
+ if(!initial(clans.joinable_clan))
+ continue
+ add_bloodsucker_icon(initial(clans.join_icon), initial(clans.join_icon_state))
+
+ for(var/datum/action/cooldown/bloodsucker/power as anything in subtypesof(/datum/action/cooldown/bloodsucker))
+ add_bloodsucker_icon(initial(power.button_icon), initial(power.button_icon_state))
+
+ return ..()
+
+/datum/asset/simple/bloodsucker_icons/proc/add_bloodsucker_icon(bloodsucker_icon, bloodsucker_icon_state)
+ assets[sanitize_filename("bloodsucker.[bloodsucker_icon_state].png")] = icon(bloodsucker_icon, bloodsucker_icon_state)
diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_daylight.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_daylight.dm
deleted file mode 100644
index ca02ccc0c14f..000000000000
--- a/code/modules/antagonists/bloodsuckers/bloodsucker_daylight.dm
+++ /dev/null
@@ -1,192 +0,0 @@
-/// 45 seconds
-#define TIME_BLOODSUCKER_DAY 45
-/// 15 minutes
-#define TIME_BLOODSUCKER_NIGHT 900
-/// 1.5 minutes
-#define TIME_BLOODSUCKER_DAY_WARN 90
-/// 30 seconds
-#define TIME_BLOODSUCKER_DAY_FINAL_WARN 30
-/// 5 seconds
-#define TIME_BLOODSUCKER_BURN_INTERVAL 5
-
-/// Over Time, tick down toward a "Solar Flare" of UV buffeting the station. This period is harmful to vamps.
-/obj/effect/sunlight
- ///If the Sun is currently out our not
- var/amDay = FALSE
- ///The time between the next cycle
- var/time_til_cycle = TIME_BLOODSUCKER_NIGHT
- ///If Bloodsuckers have been given their level yet
- var/issued_XP = FALSE
-
-/obj/effect/sunlight/Initialize()
- . = ..()
- START_PROCESSING(SSprocessing, src)
-
-/obj/effect/sunlight/Destroy()
- STOP_PROCESSING(SSprocessing, src)
- return ..()
-
-/obj/effect/sunlight/process()
- /// Update all Bloodsucker sunlight huds
- for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
- if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current))
- continue
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker)
- if(istype(bloodsuckerdatum))
- bloodsuckerdatum.update_sunlight(max(0, time_til_cycle), amDay) // This pings all HUDs
- time_til_cycle--
- if(amDay)
- if(time_til_cycle > 0)
- punish_vamps()
- if(!issued_XP && time_til_cycle <= 15)
- issued_XP = TRUE
- /// Cycle through all vamp antags and check if they're inside a closet.
- for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
- if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current))
- continue
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker)
- if(bloodsuckerdatum)
- // Rank up! Must still be in a coffin to level!
- bloodsuckerdatum.RankUp()
- if(time_til_cycle <= 1)
- warn_daylight(5, span_announce("The solar flare has ended, and the daylight danger has passed...for now."), \
- span_announce("The solar flare has ended, and the daylight danger has passed...for now."), \
- "")
- amDay = FALSE
- issued_XP = FALSE
- time_til_cycle = TIME_BLOODSUCKER_NIGHT
- message_admins("BLOODSUCKER NOTICE: Daylight Ended. Resetting to Night (Lasts for [TIME_BLOODSUCKER_NIGHT / 60] minutes.)")
- for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
- if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current))
- continue
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker)
- if(!istype(bloodsuckerdatum))
- continue
- take_home_power()
- else
- switch(time_til_cycle)
- if(TIME_BLOODSUCKER_DAY_WARN)
- warn_daylight(1, span_danger("Solar Flares will bombard the station with dangerous UV in [TIME_BLOODSUCKER_DAY_WARN / 60] minutes. Prepare to seek cover in a coffin or closet."), \
- "", \
- "")
- give_home_power()
- if(TIME_BLOODSUCKER_DAY_FINAL_WARN)
- message_admins("BLOODSUCKER NOTICE: Daylight beginning in [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds.")
- warn_daylight(2, span_userdanger("Solar Flares are about to bombard the station! You have [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds to find cover!"), \
- span_danger("In [TIME_BLOODSUCKER_DAY_FINAL_WARN / 10], your master will be at risk of a Solar Flare. Make sure they find cover!"), \
- "")
- if(TIME_BLOODSUCKER_BURN_INTERVAL)
- warn_daylight(3, span_userdanger("Seek cover, for Sol rises!"), \
- "", \
- "")
- if(0)
- amDay = TRUE
- time_til_cycle = TIME_BLOODSUCKER_DAY
- for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
- if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current))
- continue
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker)
- if(!istype(bloodsuckerdatum))
- continue
- if(bloodsuckerdatum.my_clan == CLAN_GANGREL)
- give_transform_power()
- if(bloodsuckerdatum.altar_uses > 0)
- to_chat(bloodsuckerdatum, span_notice("Your Altar uses have been reset!"))
- bloodsuckerdatum.altar_uses = 0
- warn_daylight(4, span_userdanger("Solar flares bombard the station with deadly UV light!
Stay in cover for the next [TIME_BLOODSUCKER_DAY] seconds or risk Final Death!"), \
- span_userdanger("Solar flares bombard the station with UV light!"), \
- span_userdanger("The sunlight is visible throughout the station, the Bloodsuckers must be asleep by now!"))
- message_admins("BLOODSUCKER NOTICE: Daylight Beginning (Lasts for [TIME_BLOODSUCKER_DAY] seconds.)")
-
-/obj/effect/sunlight/proc/warn_daylight(danger_level = 0, vampwarn = "", vassalwarn = "", hunteralert = "")
- for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
- if(!istype(bloodsucker_minds))
- continue
- to_chat(bloodsucker_minds, vampwarn)
- if(bloodsucker_minds.current)
- switch(danger_level)
- if(1)
- bloodsucker_minds.current.playsound_local(null, 'sound/effects/griffin_3.ogg', 50 + danger_level, TRUE)
- if(2)
- bloodsucker_minds.current.playsound_local(null, 'sound/effects/griffin_5.ogg', 50 + danger_level, TRUE)
- if(3)
- bloodsucker_minds.current.playsound_local(null, 'sound/effects/alert.ogg', 75, TRUE)
- if(4)
- bloodsucker_minds.current.playsound_local(null, 'sound/ambience/ambimystery.ogg', 100, TRUE)
- if(5)
- bloodsucker_minds.current.playsound_local(null, 'sound/spookoween/ghosty_wind.ogg', 90, TRUE)
- if(vassalwarn != "")
- for(var/datum/mind/vassal_minds as anything in get_antag_minds(/datum/antagonist/vassal))
- if(!istype(vassal_minds))
- continue
- if(vassal_minds.has_antag_datum(/datum/antagonist/bloodsucker))
- continue
- to_chat(vassal_minds, vassalwarn)
- if(hunteralert != "")
- for(var/datum/mind/monsterhunter_minds as anything in get_antag_minds(/datum/antagonist/monsterhunter))
- if(!istype(monsterhunter_minds))
- continue
- to_chat(monsterhunter_minds, hunteralert)
-
-/// Cycle through all vamp antags and check if they're inside a closet.
-/obj/effect/sunlight/proc/punish_vamps()
- for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
- if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current))
- continue
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker)
- if(!istype(bloodsuckerdatum))
- continue
- if(isstructure(bloodsucker_minds.current.loc))
- if(istype(bloodsucker_minds.current.loc, /obj/structure/closet/crate/coffin)) // Coffins offer the BEST protection
- SEND_SIGNAL(bloodsucker_minds.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/coffinsleep)
- continue
- if(COOLDOWN_FINISHED(bloodsuckerdatum, bloodsucker_spam_sol_burn)) // Closets offer SOME protection
- to_chat(bloodsucker_minds, span_warning("Your skin sizzles. [bloodsucker_minds.current.loc] doesn't protect well against UV bombardment."))
- COOLDOWN_START(bloodsuckerdatum, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol
- bloodsucker_minds.current.adjustFireLoss(0.5 + bloodsuckerdatum.bloodsucker_level / 2)
- bloodsucker_minds.current.updatehealth()
- SEND_SIGNAL(bloodsucker_minds.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_1)
- else // Out in the Open?
- if(COOLDOWN_FINISHED(bloodsuckerdatum, bloodsucker_spam_sol_burn))
- if(bloodsuckerdatum.bloodsucker_level > 0)
- to_chat(bloodsucker_minds, span_userdanger("The solar flare sets your skin ablaze!"))
- else
- to_chat(bloodsucker_minds, span_userdanger("The solar flare scalds your neophyte skin!"))
- COOLDOWN_START(bloodsuckerdatum, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol
- if(bloodsucker_minds.current.fire_stacks <= 0)
- bloodsucker_minds.current.fire_stacks = 0
- if(bloodsuckerdatum.bloodsucker_level > 0)
- bloodsucker_minds.current.adjust_fire_stacks(0.2 + bloodsuckerdatum.bloodsucker_level / 10)
- bloodsucker_minds.current.IgniteMob()
- bloodsucker_minds.current.adjustFireLoss(2 + bloodsuckerdatum.bloodsucker_level)
- bloodsucker_minds.current.updatehealth()
- SEND_SIGNAL(bloodsucker_minds.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_2)
-
-/// It's late, give the "Vanishing Act" (gohome) power to Bloodsuckers.
-/obj/effect/sunlight/proc/give_home_power()
- for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
- if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current) || !iscarbon(bloodsucker_minds.current))
- continue
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker)
- if(istype(bloodsuckerdatum) && bloodsuckerdatum.lair && !(locate(/datum/action/bloodsucker/gohome) in bloodsuckerdatum.powers))
- bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/gohome)
-
-/// It's over now, remove the "Vanishing Act" (gohome) power from Bloodsuckers.
-/obj/effect/sunlight/proc/take_home_power()
- for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
- if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current))
- continue
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker)
- for(var/datum/action/bloodsucker/power in bloodsuckerdatum.powers)
- if(istype(power, /datum/action/bloodsucker/gohome))
- bloodsuckerdatum.RemovePower(power)
-
-/obj/effect/sunlight/proc/give_transform_power()
- for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
- if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current))
- continue
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker)
- if(bloodsuckerdatum.my_clan != CLAN_GANGREL)
- continue
- if(!(locate(/datum/action/bloodsucker/gangrel/transform) in bloodsuckerdatum.powers))
- bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/gangrel/transform)
diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_flaws.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_flaws.dm
index f3f21fcc52e3..a1900f6728ba 100644
--- a/code/modules/antagonists/bloodsuckers/bloodsucker_flaws.dm
+++ b/code/modules/antagonists/bloodsuckers/bloodsucker_flaws.dm
@@ -1,100 +1,26 @@
-/////////////////////////////////////////////////////////////////////////////////////////
-// Any changes to clans have to be reflected in '/obj/item/book/kindred' /search proc. //
-/////////////////////////////////////////////////////////////////////////////////////////
-/datum/antagonist/bloodsucker/proc/AssignClanAndBane(tzimisce = FALSE)
- var/mob/living/carbon/human/bloodsucker = owner.current
+/datum/antagonist/bloodsucker/proc/assign_clan_and_bane(tzimisce = FALSE)
if(tzimisce)
- my_clan = CLAN_TZIMISCE
- to_chat(owner, span_announce("As you arrive near the station, you recall all you know and why you are here.\n\
- * As a part of the Tzimisce Clan, you are able to Dice corpses into muscle pieces.\n\
- * With muscle pieces you are able to mutilate dead husked vassals into greater beings,\n\
- * The more you climb up the Ranks the more research you gather about fleshcrafting and beings you can make.\n\
- * Remember to fuel a vassalrack with the muscles to be able to toy with those dead vassals, you are also able to ressurect people this way.\n\
- * Finally, your Favorite Vassal will turn into a battle monster to help you in combat."))
- AddHumanityLost(5.6)
- BuyPower(new /datum/action/bloodsucker/targeted/dice)
- bloodsucker.faction |= "bloodhungry" //flesh monster's clan
- var/list/powerstoremove = list(/datum/action/bloodsucker/veil, /datum/action/bloodsucker/masquerade)
- for(var/datum/action/bloodsucker/P in powers)
- if(is_type_in_list(P, powerstoremove))
- RemovePower(P)
+ my_clan = new /datum/bloodsucker_clan/tzimisce(owner.current)
+ return
+ if(my_clan)
return
- var/static/list/clans = list(
- CLAN_GANGREL,
- CLAN_LASOMBRA,
- CLAN_TOREADOR,
- )
var/list/options = list()
- options = clans
- // Brief descriptions in case they don't read the Wiki.
- to_chat(owner, span_announce("List of all Clans:\n\
- Gangrel - Prone to Frenzy, strange outcomes from being on frenzy, special power.\n\
- Lasombra - Life in the shadows, very weak to fire but no brute damage, upgradable abilities through tasks.\n\
- Toreador - More human then other bloodsucker, easily disguise among crew, but bound with morals."))
+ var/list/radial_display = list()
+ for(var/datum/bloodsucker_clan/all_clans as anything in typesof(/datum/bloodsucker_clan))
+ if(!initial(all_clans.joinable_clan)) //flavortext only
+ continue
+ options[initial(all_clans.name)] = all_clans
+ var/datum/radial_menu_choice/option = new
+ option.image = image(icon = initial(all_clans.join_icon), icon_state = initial(all_clans.join_icon_state))
+ option.info = "[initial(all_clans.name)] - [span_boldnotice(initial(all_clans.join_description))]"
+ radial_display[initial(all_clans.name)] = option
- var/answer = input("You have Ranked up far enough to remember your clan. Which clan are you part of?", "Your mind feels luxurious...") in options
- if(!answer)
- to_chat(owner, span_warning("You have willfully decided to stay ignorant."))
+ var/chosen_clan = show_radial_menu(owner.current, owner.current, radial_display)
+ chosen_clan = options[chosen_clan]
+ if(QDELETED(src) || QDELETED(owner.current))
+ return FALSE
+ if(!chosen_clan)
+ to_chat(owner, span_announce("You choose to remain ignorant, for now."))
return
- switch(answer)
- if(CLAN_GANGREL)
- my_clan = CLAN_GANGREL
- to_chat(owner, span_announce("You have Ranked up enough to learn: You are part of the Gangrel Clan!\n\
- * As part of the Gangrel Clan, your inner beast has a stronger impact in your undead life.\n\
- * You are prone to falling into a frenzy, and will unleash a wild beast form when doing so,\n\
- * Though once per night you are able to unleash your inner beast to help you in combat.\n\
- * Due to growing more feral you've also strayed away from other bloodsuckers and will only be able to maintain one vassal.\n\
- * Finally, your Favorite Vassal will gain the Minor Beast Form ability to help you in combat."))
- AddHumanityLost(16.8)
- BuyPower(new /datum/action/bloodsucker/gangrel/transform)
- bloodsucker.faction |= "bloodhungry" //i love animals i love animals
- RemovePower(/datum/action/bloodsucker/masquerade)
- var/datum/objective/bloodsucker/frenzy/gangrel_objective = new
- gangrel_objective.owner = owner
- objectives += gangrel_objective
- if(CLAN_LASOMBRA)
- my_clan = CLAN_LASOMBRA
- to_chat(owner, span_announce("You have Ranked up enough to learn: You are part of the Lasombra Clan!\n\
- * As part of the Lasombra Clan, your past teachings have shown you how to become in touch with the Abyss and practice its prophecies.\n\
- * It'll take long before the Abyss can break through this plane's veil, but you'll try to salvage any of the energy that comes through,\n\
- * To harness it's energy you must first find an influence and make a Resting Place altar to feed the harvested essence to.\n\
- * On the Resting Place you can give ranks or blood in exchange for shadowpoints, that can be spent on the table to ascend your abilities.\n\
- * The Abyss has blackened your veins and made you immune to brute damage but highly receptive to burn, so you might need to be extra careful when in Torpor.\n\
- * Finally, your Favorite Vassal will gain the Lesser Glare and Shadow Walk abilities to help you in combat."))
- bloodsucker.physiology.burn_mod *= 2
- bloodsucker.physiology.brute_mod *= 0
- bloodsucker.eye_color = "f00"
- ADD_TRAIT(bloodsucker, CULT_EYES, BLOODSUCKER_TRAIT)
- bloodsucker.faction |= "bloodhungry"
- bloodsucker.update_body()
- var/obj/item/organ/heart/nightmare/nightmarish_heart = new
- nightmarish_heart.Insert(bloodsucker)
- nightmarish_heart.Stop()
- for(var/obj/item/light_eater/blade in bloodsucker.held_items)
- QDEL_NULL(blade)
- GLOB.reality_smash_track.AddMind(owner)
- var/datum/objective/bloodsucker/hierarchy/lasombra_objective = new
- lasombra_objective.owner = owner
- objectives += lasombra_objective
- to_chat(owner, span_notice("You have also learned how to channel the abyss's power into an iron knight's armor that can be build in the structure ta and activated as a trap for your lair."))
- owner.teach_crafting_recipe(/datum/crafting_recipe/possessedarmor)
- owner.teach_crafting_recipe(/datum/crafting_recipe/restingplace)
- if(CLAN_TOREADOR)
- my_clan = CLAN_TOREADOR
- to_chat(owner, span_announce("You have Ranked up enough to learn: You are part of the Toreador Clan!\n\
- * As part of the Toreador, you can't ignore your own emotions and you disrespect inhuman actions.\n\
- * Being in Masquerade doesn't spend your blood, or have any negative effects on your immortal powers.\n\
- * You passively raise morale of your living vassals around you.\n\
- * Also you get really sad when comitting inhumane actions, but your humanity loss can't go above a certain threshold.\n\
- * Remember that those bloodsuckers who dare to act inhuman or break the Masquerade shouldn't be forgiven, and deserve only death.\n\
- * Finally, your Favorite Vassal will gain the Mesmerise ability to help you in non-lethally dealing with enemies or vassalising people."))
- if(owner.current && ishuman(owner.current) && !owner.current.GetComponent(/datum/component/mood))
- owner.current.AddComponent(/datum/component/mood) //You are not a emotionless beast!
-
- for(var/datum/action/bloodsucker/masquerade/masquarade_spell in powers)
- if(!istype(masquarade_spell))
- continue
- masquarade_spell.bloodcost = 0
- masquarade_spell.constant_bloodcost = 0 //Wow very cool code, good job
-
+ my_clan = new chosen_clan(src)
owner.announce_objectives()
diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_frenzy.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_frenzy.dm
index 18d54afff461..0d3c71cc937f 100644
--- a/code/modules/antagonists/bloodsuckers/bloodsucker_frenzy.dm
+++ b/code/modules/antagonists/bloodsuckers/bloodsucker_frenzy.dm
@@ -11,10 +11,9 @@
/datum/martial_art/frenzygrab/grab_act(mob/living/user, mob/living/target)
if(user != target)
target.grabbedby(user)
- user.grab_state = GRAB_AGGRESSIVE
- restraining = TRUE
+ target.grippedby(user, instant = TRUE)
return TRUE
- ..()
+ return ..()
/**
* # Status effect
@@ -28,7 +27,7 @@
status_type = STATUS_EFFECT_UNIQUE
duration = -1
tick_interval = 10
- examine_text = span_notice("They look feral and inhuman!")
+ examine_text = span_notice("They seem... inhumane, and feral!")
alert_type = /atom/movable/screen/alert/status_effect/frenzy
/// Store whether they were an advancedtooluser, to give the trait back upon exiting.
var/was_tooluser = FALSE
@@ -40,6 +39,7 @@
desc = "You are in a Frenzy! You are entirely feral, and depending on your Clan - fighting for your life! Feeding while in this state is instant!"
icon = 'icons/mob/actions/actions_bloodsucker.dmi'
icon_state = "power_recover"
+ alerttooltipstyle = "cult"
/atom/movable/screen/alert/status_effect/masquerade/MouseEntered(location,control,params)
desc = initial(desc)
@@ -50,9 +50,11 @@
bloodsuckerdatum = IS_BLOODSUCKER(user)
// Disable ALL Powers and notify their entry
- bloodsuckerdatum.DisableAllPowers()
+ bloodsuckerdatum.DisableAllPowers(forced = TRUE)
to_chat(owner, span_userdanger("Blood! You need Blood, now! You enter a total Frenzy! Your skin starts sizzling...."))
to_chat(owner, span_announce("* Bloodsucker Tip: While in Frenzy, you instantly Aggresively grab, have stun resistance, and cannot speak, hear, or use any powers outside of Feed and Trespass (If you have it)."))
+ owner.balloon_alert(owner, "you enter a frenzy!")
+
// Stamina resistances
user.physiology.stamina_mod *= 0.4
@@ -70,8 +72,8 @@
var/obj/item/cuffs = user.get_item_by_slot(SLOT_HANDCUFFED)
var/obj/item/legcuffs = user.get_item_by_slot(SLOT_LEGCUFFED)
if(user.handcuffed || user.legcuffed)
- user.clear_cuffs(cuffs, TRUE)
- user.clear_cuffs(legcuffs, TRUE)
+ user.clear_cuffs(cuffs, TRUE, TRUE)
+ user.clear_cuffs(legcuffs, TRUE, TRUE)
// Keep track of how many times we've entered a Frenzy.
bloodsuckerdatum.frenzies++
bloodsuckerdatum.frenzied = TRUE
@@ -79,7 +81,7 @@
/datum/status_effect/frenzy/on_remove()
var/mob/living/carbon/human/user = owner
- to_chat(owner, span_warning("You come back to your senses."))
+ owner.balloon_alert(owner, "you come back to your senses.")
REMOVE_TRAIT(owner, TRAIT_MUTE, FRENZY_TRAIT)
REMOVE_TRAIT(owner, TRAIT_DEAF, FRENZY_TRAIT)
REMOVE_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, FRENZY_TRAIT)
@@ -90,7 +92,7 @@
owner.remove_movespeed_modifier(type)
bloodsuckerdatum.frenzygrab.remove(user)
owner.remove_client_colour(/datum/client_colour/cursed_heart_blood)
- owner.Dizzy(3 SECONDS)
+ owner.adjust_dizzy(3 SECONDS)
owner.Paralyze(2 SECONDS)
user.physiology.stamina_mod /= 0.4
@@ -102,8 +104,9 @@
var/obj/item/cuffs = user.get_item_by_slot(SLOT_HANDCUFFED)
var/obj/item/legcuffs = user.get_item_by_slot(SLOT_LEGCUFFED)
if(user.handcuffed || user.legcuffed)
- user.clear_cuffs(cuffs, TRUE)
- user.clear_cuffs(legcuffs, TRUE)
+ user.clear_cuffs(cuffs, TRUE, TRUE)
+ user.clear_cuffs(legcuffs, TRUE, TRUE)
+ user.balloon_alert_to_viewers("snaps [user.p_their()] restraints!", "you break free of your restraints!")
if(!bloodsuckerdatum.frenzied)
return
- user.adjustFireLoss(min(0.5 + (bloodsuckerdatum.humanity_lost / 15), bloodsuckerdatum.my_clan == CLAN_GANGREL ? 2 : 100))
+ user.adjustFireLoss(min(0.5 + (bloodsuckerdatum.humanity_lost / 15), bloodsuckerdatum.my_clan?.get_clan() == CLAN_GANGREL ? 2 : 100)) //:D
diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_hud.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_hud.dm
new file mode 100644
index 000000000000..53000dc14d30
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/bloodsucker_hud.dm
@@ -0,0 +1,105 @@
+/// 1 tile down
+#define UI_BLOOD_DISPLAY "WEST:6,CENTER-1:0"
+/// 2 tiles down
+#define UI_VAMPRANK_DISPLAY "WEST:6,CENTER-2:-5"
+/// 6 pixels to the right, zero tiles & 5 pixels DOWN.
+#define UI_SUNLIGHT_DISPLAY "WEST:6,CENTER-0:0"
+
+///Maptext define for Bloodsucker HUDs
+#define FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, value) MAPTEXT("[round(value,1)]
")
+///Maptext define for Bloodsucker Sunlight HUDs
+#define FORMAT_BLOODSUCKER_SUNLIGHT_TEXT(valuecolor, value) MAPTEXT("[value]
")
+
+
+/atom/movable/screen/bloodsucker
+ icon = 'icons/mob/actions/actions_bloodsucker.dmi'
+
+/atom/movable/screen/bloodsucker/blood_counter
+ name = "Blood Consumed"
+ icon_state = "blood_display"
+ screen_loc = UI_BLOOD_DISPLAY
+
+/atom/movable/screen/bloodsucker/rank_counter
+ name = "Bloodsucker Rank"
+ icon_state = "rank"
+ screen_loc = UI_VAMPRANK_DISPLAY
+
+/atom/movable/screen/bloodsucker/sunlight_counter
+ name = "Solar Flare Timer"
+ icon_state = "sunlight"
+ screen_loc = UI_SUNLIGHT_DISPLAY
+#ifdef BLOODSUCKER_TESTING
+ var/datum/controller/subsystem/sunlight/sunlight_subsystem
+
+/atom/movable/screen/bloodsucker/sunlight_counter/New(loc, ...)
+ . = ..()
+ sunlight_subsystem = SSsunlight
+#endif
+
+///Updates the counter on said HUD
+/atom/movable/screen/bloodsucker/proc/update_counter()
+
+/atom/movable/screen/bloodsucker/blood_counter/update_counter(value, valuecolor)
+ . = ..()
+ maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, value)
+
+/atom/movable/screen/bloodsucker/rank_counter/update_counter(value, valuecolor)
+ . = ..()
+ maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, value)
+
+/atom/movable/screen/bloodsucker/sunlight_counter/update_counter(value, valuecolor)
+ . = ..()
+ maptext = FORMAT_BLOODSUCKER_SUNLIGHT_TEXT(valuecolor, value)
+
+/// Update Blood Counter + Rank Counter
+/datum/antagonist/bloodsucker/proc/update_hud()
+ var/valuecolor
+ if(bloodsucker_blood_volume > BLOOD_VOLUME_SAFE(owner.current))
+ valuecolor = "#FFDDDD"
+ else if(bloodsucker_blood_volume > BLOOD_VOLUME_BAD(owner.current))
+ valuecolor = "#FFAAAA"
+
+ if(blood_display)
+ blood_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, bloodsucker_blood_volume)
+
+ if(vamprank_display)
+ if(bloodsucker_level_unspent)
+ vamprank_display.icon_state = "[initial(vamprank_display.icon_state)]_up"
+ else
+ vamprank_display.icon_state = initial(vamprank_display.icon_state)
+ vamprank_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, bloodsucker_level)
+
+ if(sunlight_display)
+ if(SSsunlight.sunlight_active)
+ valuecolor = "#FF5555"
+ sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_day"
+ else
+ switch(round(SSsunlight.time_til_cycle, 1))
+ if(0 to 30)
+ sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_30"
+ valuecolor = "#FFCCCC"
+ if(31 to 60)
+ sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_60"
+ valuecolor = "#FFE6CC"
+ if(61 to 90)
+ sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_90"
+ valuecolor = "#FFFFCC"
+ else
+ sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_night"
+ valuecolor = "#FFFFFF"
+ sunlight_display.maptext = FORMAT_BLOODSUCKER_SUNLIGHT_TEXT( \
+ valuecolor, \
+ (SSsunlight.time_til_cycle >= 60) ? "[round(SSsunlight.time_til_cycle / 60, 1)] m" : "[round(SSsunlight.time_til_cycle, 1)] s" \
+ )
+
+/// 1 tile down
+#undef UI_BLOOD_DISPLAY
+/// 2 tiles down
+#undef UI_VAMPRANK_DISPLAY
+/// 6 pixels to the right, zero tiles & 5 pixels DOWN.
+#undef UI_SUNLIGHT_DISPLAY
+
+///Maptext define for Bloodsucker HUDs
+#undef FORMAT_BLOODSUCKER_HUD_TEXT
+///Maptext define for Bloodsucker Sunlight HUDs
+#undef FORMAT_BLOODSUCKER_SUNLIGHT_TEXT
diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_integration.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_integration.dm
index 09f8932ba553..68dbf800d737 100644
--- a/code/modules/antagonists/bloodsuckers/bloodsucker_integration.dm
+++ b/code/modules/antagonists/bloodsuckers/bloodsucker_integration.dm
@@ -1,14 +1,7 @@
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// TG OVERWRITES
-
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-/// Gives Curators their abilities
-/datum/outfit/job/curator/post_equip(mob/living/carbon/human/user, visualsOnly = FALSE)
- . = ..()
-
- ADD_TRAIT(user, TRAIT_BLOODSUCKER_HUNTER, JOB_TRAIT)
+/*
+ * OVERWRITES
+*/
/datum/species/jelly/slime/spec_life(mob/living/carbon/human/user)
// Prevents Slimeperson 'gaming
@@ -31,7 +24,7 @@
. = ..()
// Used to keep track of how much Blood we've drank so far
-/mob/living/carbon/human/get_status_tab_items()
+/mob/living/get_status_tab_items()
. = ..()
if(mind)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(/datum/antagonist/bloodsucker)
@@ -61,7 +54,7 @@
return TRUE
// EXAMINING
-/mob/living/carbon/human/proc/ReturnVampExamine(mob/living/viewer)
+/mob/living/carbon/proc/return_vamp_examine(mob/living/viewer)
if(!mind || !viewer.mind)
return ""
// Target must be a Vamp
@@ -79,7 +72,7 @@
if(!(HAS_TRAIT(viewer, TRAIT_BLOODSUCKER_HUNTER) && bloodsuckerdatum.broke_masquerade))
return ""
// Default String
- var/returnString = "\[[bloodsuckerdatum.ReturnFullName(1)]\]"
+ var/returnString = "\[[bloodsuckerdatum.return_full_name(1)]\]"
var/returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "bloodsucker")]"
// In Disguise (Veil)?
@@ -107,7 +100,7 @@
returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal")]"
// Am I someone ELSE'S Vassal?
else if(IS_BLOODSUCKER(viewer) || IS_MONSTERHUNTER(viewer))
- returnString += "This [dna.species.name] bears the mark of [vassaldatum.master.ReturnFullName(vassaldatum.master.owner.current,TRUE)][vassaldatum.master.broke_masquerade ? " who has broken the Masquerade" : ""]"
+ returnString += "This [dna.species.name] bears the mark of [vassaldatum.master.return_full_name(vassaldatum.master.owner.current,TRUE)][vassaldatum.master.broke_masquerade ? " who has broken the Masquerade" : ""]"
returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal_grey")]"
// Are you serving the same master as I am?
else if(viewer.mind.has_antag_datum(/datum/antagonist/vassal) in vassaldatum?.master.vassals)
diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm
index 9bf707dac7e3..25744e988319 100644
--- a/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm
+++ b/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm
@@ -196,6 +196,7 @@
user.blood_volume += 10
adjustFireLoss(2.5)
updatehealth() //3 minutes to die
+
if(satiation >= 3)
to_chat(src, span_notice("It has been fed. You turn back to normal."))
qdel(src)
diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_objectives.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_objectives.dm
index 646a07bd6097..4aecd287743d 100644
--- a/code/modules/antagonists/bloodsuckers/bloodsucker_objectives.dm
+++ b/code/modules/antagonists/bloodsuckers/bloodsucker_objectives.dm
@@ -47,7 +47,7 @@
// WIN CONDITIONS?
/datum/objective/bloodsucker/lair/check_completion()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker)
- if(bloodsuckerdatum && bloodsuckerdatum.coffin && bloodsuckerdatum.lair)
+ if(bloodsuckerdatum && bloodsuckerdatum.coffin && bloodsuckerdatum.bloodsucker_lair_area)
return TRUE
return FALSE
@@ -100,7 +100,7 @@
target_department = VASSALIZE_COMMAND
// Vassalize a certain department
else
- target_amount = rand(2,3)
+ target_amount = rand(2, 3)
target_department = pick(departments)
..()
@@ -198,7 +198,7 @@
if(!owner.current)
return FALSE
- var/list/all_items = owner.current.GetAllContents()
+ var/list/all_items = owner.current.get_all_contents()
var/heart_count = 0
for(var/obj/item/organ/heart/current_hearts in all_items)
if(current_hearts.organ_flags & ORGAN_SYNTHETIC) // No robo-hearts allowed
@@ -350,12 +350,12 @@
name = "frenzy"
/datum/objective/bloodsucker/frenzy/New()
- target_amount = rand(3,4)
+ target_amount = rand(1, 2)
..()
/datum/objective/bloodsucker/frenzy/update_explanation_text()
. = ..()
- explanation_text = "Enter Frenzy [target_amount] of times without succumbing to Final Death."
+ explanation_text = "Enter Frenzy [target_amount == 1 ? "atleast once" : "2 times"] without succumbing to Final Death."
/datum/objective/bloodsucker/frenzy/check_completion()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker)
@@ -369,12 +369,12 @@
name = "hierarchy"
/datum/objective/bloodsucker/hierarchy/New()
- target_amount = rand(4,5)
+ target_amount = rand(1, 2)
..()
/datum/objective/bloodsucker/hierarchy/update_explanation_text()
. = ..()
- explanation_text = "Ascend [target_amount] abilities using a Resting Place altar."
+ explanation_text = "Ascend [target_amount == 1 ? "atleast 1 ability" : "2 abilities"] using a Resting Place altar."
/datum/objective/bloodsucker/hierarchy/check_completion()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker)
@@ -413,17 +413,17 @@
name = "leader"
/datum/objective/bloodsucker/leader/New()
- target_amount = rand(2,3)
+ target_amount = rand(2, 3)
..()
// EXPLANATION
/datum/objective/bloodsucker/leader/update_explanation_text()
. = ..()
- explanation_text = "Convert [target_amount] of Vassals into your vassals."
+ explanation_text = "Convert [target_amount] mortal beings into your vassals."
// WIN CONDITIONS?
/datum/objective/bloodsucker/leader/check_completion()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(bloodsuckerdatum && bloodsuckerdatum.vassals >= target_amount)
+ if(LAZYLEN(bloodsuckerdatum?.vassals) >= target_amount)
return TRUE
return FALSE
diff --git a/code/modules/antagonists/bloodsuckers/bloodsuckers.dm b/code/modules/antagonists/bloodsuckers/bloodsuckers.dm
index 40a17f692902..1c85510956c6 100644
--- a/code/modules/antagonists/bloodsuckers/bloodsuckers.dm
+++ b/code/modules/antagonists/bloodsuckers/bloodsuckers.dm
@@ -4,8 +4,12 @@
roundend_category = "bloodsuckers"
antagpanel_category = "Bloodsucker"
job_rank = ROLE_BLOODSUCKER
+ antag_hud_name = "bloodsucker"
+ show_to_ghosts = TRUE
show_name_in_check_antagonists = TRUE
can_coexist_with_others = FALSE
+ ui_name = "AntagInfoBloodsucker"
+ preview_outfit = /datum/outfit/bloodsucker_outfit
// TIMERS //
///Timer between alerts for Burn messages
@@ -24,6 +28,8 @@
var/humanity_lost = 0
///Have we been broken the Masquerade?
var/broke_masquerade = FALSE
+ ///How many Masquerade Infractions do we have?
+ var/masquerade_infractions = 0
///If we are currently in a Frenzy
var/frenzied = FALSE
///If we have a task assigned
@@ -32,13 +38,9 @@
var/altar_uses = 0
///ALL Powers currently owned
- var/list/datum/action/powers = list()
- ///Bloodsucker Clan - Used for dealing with Sol
- var/datum/team/vampireclan/clan
+ var/list/datum/action/cooldown/bloodsucker/powers = list()
///Frenzy Grab Martial art given to Bloodsuckers in a Frenzy
var/datum/martial_art/frenzygrab/frenzygrab = new
- ///You get assigned a Clan once you Rank up enough
- var/my_clan = NONE
///How many clan points you have -> Used in clans in order to assert certain limits // Upgrades and stuff
var/clanpoints = 0
///How much progress have you done on your clan
@@ -46,18 +48,23 @@
///Vassals under my control. Periodically remove the dead ones.
var/list/datum/antagonist/vassal/vassals = list()
- ///Have we selected our Favorite Vassal yet?
- var/has_favorite_vassal = FALSE
+ ///Special vassals I own, to not have double of the same type.
+ var/list/datum/antagonist/vassal/special_vassals = list()
var/bloodsucker_level
var/bloodsucker_level_unspent = 1
var/passive_blood_drain = -0.1
var/additional_regen
var/bloodsucker_regen_rate = 0.3
+ /// How much blood we have, starting off at default blood levels.
+ var/bloodsucker_blood_volume = BLOOD_VOLUME_GENERIC
+ /// How much blood we can have at once, increases per level.
var/max_blood_volume = 600
+ var/datum/bloodsucker_clan/my_clan
+
// Used for Bloodsucker Objectives
- var/area/lair
+ var/area/bloodsucker_lair_area
var/obj/structure/closet/crate/coffin
var/total_blood_drank = 0
var/frenzy_blood_drank = 0
@@ -65,14 +72,19 @@
var/task_blood_required = 0
var/task_blood_drank = 0
var/frenzies = 0
+ ///Blood display HUD
+ var/atom/movable/screen/bloodsucker/blood_counter/blood_display
+ ///Vampire level display HUD
+ var/atom/movable/screen/bloodsucker/rank_counter/vamprank_display
+ ///Sunlight timer HUD
+ var/atom/movable/screen/bloodsucker/sunlight_counter/sunlight_display
/// Static typecache of all bloodsucker powers.
- var/static/list/all_bloodsucker_powers = typecacheof(/datum/action/bloodsucker, TRUE)
+ var/static/list/all_bloodsucker_powers = typecacheof(/datum/action/cooldown/bloodsucker, TRUE)
/// Antagonists that cannot be Vassalized no matter what
- var/list/vassal_banned_antags = list(
+ var/static/list/vassal_banned_antags = list(
/datum/antagonist/bloodsucker,
/datum/antagonist/monsterhunter,
- /datum/antagonist/xeno,
)
///Default Bloodsucker traits
var/static/list/bloodsucker_traits = list(
@@ -94,33 +106,70 @@
TRAIT_RESISTDAMAGESLOWDOWN,
)
-/mob/living/proc/explain_powers()
- set name = "Bloodsucker Help"
- set category = "Mentor"
-
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(/datum/antagonist/bloodsucker)
- var/choice = input(usr, "What Power are you looking into?", "Mentorhelp v2") in bloodsuckerdatum.powers
- if(!choice)
- return
- var/datum/action/bloodsucker/power = choice
- to_chat(usr, span_warning("[power.power_explanation]"))
-
-/// These handles the application of antag huds/special abilities
+/**
+ * Apply innate effects is everything given to the mob
+ * When a body is tranferred, this is called on the new mob
+ * while on_gain is called ONCE per ANTAG, this is called ONCE per BODY.
+ */
/datum/antagonist/bloodsucker/apply_innate_effects(mob/living/mob_override)
- RegisterSignal(owner.current, COMSIG_LIVING_BIOLOGICAL_LIFE, PROC_REF(LifeTick))
- if((owner.assigned_role == "Clown"))
- var/mob/living/carbon/H = owner.current
- if(H && istype(H))
- if(!silent)
- H.dna.remove_mutation(CLOWNMUT)
- to_chat(owner, "As a vampiric clown, you are no longer a danger to yourself. Your clownish nature has been subdued by your thirst for blood.")
+ . = ..()
+ var/mob/living/current_mob = mob_override || owner.current
+ RegisterSignal(current_mob, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(current_mob, COMSIG_LIVING_BIOLOGICAL_LIFE, PROC_REF(LifeTick))
+ RegisterSignal(current_mob, COMSIG_LIVING_DEATH, PROC_REF(on_death))
+ handle_clown_mutation(current_mob, mob_override ? null : "As a vampiric clown, you are no longer a danger to yourself. Your clownish nature has been subdued by your thirst for blood.")
+ add_team_hud(current_mob)
+
+ if(current_mob.hud_used)
+ on_hud_created()
+ else
+ RegisterSignal(current_mob, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created))
+#ifdef BLOODSUCKER_TESTING
+ var/turf/user_loc = get_turf(current_mob)
+ new /obj/structure/closet/crate/coffin(user_loc)
+ new /obj/structure/bloodsucker/vassalrack(user_loc)
+#endif
+
+/**
+ * Remove innate effects is everything given to the mob
+ * When a body is tranferred, this is called on the new mob
+ * while on_removal is called ONCE per ANTAG, this is called ONCE per BODY.
+ */
/datum/antagonist/bloodsucker/remove_innate_effects(mob/living/mob_override)
- UnregisterSignal(owner.current, COMSIG_LIVING_BIOLOGICAL_LIFE)
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/H = owner.current
- if(H && istype(H))
- H.dna.add_mutation(CLOWNMUT)
+ . = ..()
+ var/mob/living/current_mob = mob_override || owner.current
+ UnregisterSignal(current_mob, list(COMSIG_LIVING_BIOLOGICAL_LIFE, COMSIG_PARENT_EXAMINE, COMSIG_LIVING_DEATH))
+
+ if(current_mob.hud_used)
+ var/datum/hud/hud_used = current_mob.hud_used
+
+ hud_used.infodisplay -= blood_display
+ hud_used.infodisplay -= vamprank_display
+ hud_used.infodisplay -= sunlight_display
+
+ QDEL_NULL(blood_display)
+ QDEL_NULL(vamprank_display)
+ QDEL_NULL(sunlight_display)
+
+/datum/antagonist/bloodsucker/proc/on_hud_created(datum/source)
+ SIGNAL_HANDLER
+
+ var/datum/hud/bloodsucker_hud = owner.current.hud_used
+
+ blood_display = new /atom/movable/screen/bloodsucker/blood_counter()
+ blood_display.hud = bloodsucker_hud
+ bloodsucker_hud.infodisplay += blood_display
+
+ vamprank_display = new /atom/movable/screen/bloodsucker/rank_counter()
+ vamprank_display.hud = bloodsucker_hud
+ bloodsucker_hud.infodisplay += vamprank_display
+
+ sunlight_display = new /atom/movable/screen/bloodsucker/sunlight_counter()
+ sunlight_display.hud = bloodsucker_hud
+ bloodsucker_hud.infodisplay += sunlight_display
+
+ INVOKE_ASYNC(bloodsucker_hud, TYPE_PROC_REF(/datum/hud/, show_hud), bloodsucker_hud.hud_version)
/datum/antagonist/bloodsucker/get_admin_commands()
. = ..()
@@ -133,13 +182,20 @@
else
.["Break Masquerade"] = CALLBACK(src, PROC_REF(break_masquerade))
-/// Called by the add_antag_datum() mind proc after the instanced datum is added to the mind's antag_datums list.
+///Called when you get the antag datum, called only ONCE per antagonist.
/datum/antagonist/bloodsucker/on_gain()
- if(IS_VASSAL(owner.current)) // Vassals shouldnt be getting the same benefits as Bloodsuckers.
+ RegisterSignal(SSsunlight, COMSIG_SOL_RANKUP_BLOODSUCKERS, PROC_REF(sol_rank_up))
+ RegisterSignal(SSsunlight, COMSIG_SOL_NEAR_START, PROC_REF(sol_near_start))
+ RegisterSignal(SSsunlight, COMSIG_SOL_END, PROC_REF(on_sol_end))
+ RegisterSignal(SSsunlight, COMSIG_SOL_RISE_TICK, PROC_REF(handle_sol))
+ RegisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN, PROC_REF(give_warning))
+
+ if(IS_FAVORITE_VASSAL(owner.current)) // Vassals shouldnt be getting the same benefits as Bloodsuckers.
bloodsucker_level_unspent = 0
+ show_in_roundend = FALSE
else
// Start Sunlight if first Bloodsucker
- clan.check_start_sunlight()
+ check_start_sunlight()
// Name and Titles
SelectFirstName()
SelectTitle(am_fledgling = TRUE)
@@ -148,23 +204,24 @@
forge_bloodsucker_objectives()
. = ..()
- update_bloodsucker_icons_added(owner.current)
// Assign Powers
- AssignStarterPowersAndStats()
+ give_starting_powers()
+ assign_starting_stats()
/// Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion.
/datum/antagonist/bloodsucker/on_removal()
/// End Sunlight? (if last Vamp)
- clan.check_cancel_sunlight()
- update_bloodsucker_icons_removed(owner.current)
+ UnregisterSignal(SSsunlight, list(COMSIG_SOL_RANKUP_BLOODSUCKERS, COMSIG_SOL_NEAR_START, COMSIG_SOL_END, COMSIG_SOL_RISE_TICK, COMSIG_SOL_WARNING_GIVEN))
ClearAllPowersAndStats()
+ check_cancel_sunlight() //check if sunlight should end
+ QDEL_NULL(my_clan)
return ..()
/datum/antagonist/bloodsucker/on_body_transfer(mob/living/old_body, mob/living/new_body)
. = ..()
if(istype(new_body, /mob/living/simple_animal/hostile/bloodsucker) || istype(old_body, /mob/living/simple_animal/hostile/bloodsucker))
return
- for(var/datum/action/bloodsucker/all_powers as anything in powers)
+ for(var/datum/action/cooldown/bloodsucker/all_powers as anything in powers)
all_powers.Remove(old_body)
all_powers.Grant(new_body)
var/old_punchdamagelow
@@ -206,7 +263,7 @@
/datum/antagonist/bloodsucker/greet()
. = ..()
- var/fullname = ReturnFullName(TRUE)
+ var/fullname = return_full_name(TRUE)
to_chat(owner, span_userdanger("You are [fullname], a strain of vampire known as a Bloodsucker!"))
owner.announce_objectives()
if(bloodsucker_level_unspent >= 2)
@@ -217,13 +274,10 @@
/datum/antagonist/bloodsucker/farewell()
to_chat(owner.current, span_userdanger("With a snap, your curse has ended. You are no longer a Bloodsucker. You live once more!"))
// Refill with Blood so they don't instantly die.
- owner.current.blood_volume = max(owner.current.blood_volume, BLOOD_VOLUME_NORMAL(owner.current))
-
-/datum/antagonist/bloodsucker/proc/add_objective(datum/objective/added_objective)
- objectives += added_objective
-
-/datum/antagonist/bloodsucker/proc/remove_objectives(datum/objective/removed_objective)
- objectives -= removed_objective
+ if(ishuman(owner.current))
+ var/mob/living/carbon/user = owner.current
+ if(!LAZYFIND(user.dna.species.species_traits, NOBLOOD))
+ owner.current.blood_volume = max(owner.current.blood_volume, BLOOD_VOLUME_NORMAL(owner.current))
// Called when using admin tools to give antag status, admin spawned bloodsuckers don't get turned human if plasmaman.
/datum/antagonist/bloodsucker/admin_add(datum/mind/new_owner, mob/admin)
@@ -236,60 +290,58 @@
log_admin("[key_name(usr)][msg]")
new_owner.add_antag_datum(src)
-/**
- * # Vampire Clan
- *
- * This is used for dealing with the Vampire Clan.
- * This handles Sol for Bloodsuckers, making sure to not have several.
- * None of this should appear in game, we are using it JUST for Sol. All Bloodsuckers should have their individual report.
- */
+/datum/antagonist/bloodsucker/ui_data(mob/user)
+ var/list/data = list()
-/datum/team/vampireclan
- name = "Clan"
+ data["in_clan"] = !!my_clan
+ var/list/clan_data = list()
+ if(my_clan)
+ clan_data["clan_name"] = my_clan.name
+ clan_data["clan_description"] = my_clan.description
+ clan_data["clan_icon"] = my_clan.join_icon_state
- /// Sunlight Timer. Created on first Bloodsucker assign. Destroyed on last removed Bloodsucker.
- var/obj/effect/sunlight/bloodsucker_sunlight
+ data["clan"] += list(clan_data)
-/datum/antagonist/bloodsucker/create_team(datum/team/vampireclan/team)
- if(!team)
- for(var/datum/antagonist/bloodsucker/bloodsuckerdatums in GLOB.antagonists)
- if(!bloodsuckerdatums.owner)
- continue
- if(bloodsuckerdatums.clan)
- clan = bloodsuckerdatums.clan
- return
- clan = new /datum/team/vampireclan
- return
- if(!istype(team))
- stack_trace("Wrong team type passed to [type] initialization.")
- clan = team
+ return data
+
+/datum/antagonist/bloodsucker/ui_static_data(mob/user)
+ var/list/data = list()
+ //we don't need to update this that much.
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
+ var/list/power_data = list()
+
+ power_data["power_name"] = power.name
+ power_data["power_explanation"] = power.power_explanation
+ power_data["power_icon"] = power.button_icon_state
+
+ data["power"] += list(power_data)
-/datum/antagonist/bloodsucker/get_team()
- return clan
+ return data + ..()
-/datum/team/vampireclan/roundend_report()
- if(members.len <= 0)
+/datum/antagonist/bloodsucker/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/simple/bloodsucker_icons),
+ )
+
+/datum/antagonist/bloodsucker/ui_act(action, params, datum/tgui/ui)
+ . = ..()
+ if(.)
return
- var/list/report = list()
- report += "
"
- for(var/datum/mind/mind_members in members)
- for(var/datum/antagonist/bloodsucker/individual_bloodsuckers in mind_members.antag_datums)
- if(mind_members.has_antag_datum(/datum/antagonist/vassal)) // Skip over Ventrue's Favorite Vassal
- continue
- report += individual_bloodsuckers.roundend_report()
- return "[report.Join("
")]
"
+ switch(action)
+ if("join_clan")
+ assign_clan_and_bane()
+ ui?.send_full_update(force = TRUE)
+ return
-/// Individual roundend report
/datum/antagonist/bloodsucker/roundend_report()
- // Get the default Objectives
var/list/report = list()
+
// Vamp name
- report += "
"
+ report += "
[span_header("\[[return_full_name(TRUE)]\]")]"
report += printplayer(owner)
- // Clan (Actual Clan, not Team) name
- if(my_clan != NONE)
- report += "They were part of the [my_clan]!"
+ if(my_clan)
+ report += "They were part of the [my_clan.name]!"
// Default Report
var/objectives_complete = TRUE
@@ -306,21 +358,31 @@
break
// Now list their vassals
- if(vassals.len > 0)
- report += ""
- for(var/datum/antagonist/vassal/all_vassals in vassals)
- if(all_vassals.owner)
- var/jobname = all_vassals.owner.assigned_role ? "the [all_vassals.owner.assigned_role]" : ""
- report += "[all_vassals.owner.name] [jobname][all_vassals.favorite_vassal == TRUE ? " and was the Favorite Vassal" : ""]"
-
- if(objectives.len == 0 || objectives_complete && optional_objectives_complete)
+ if(vassals.len)
+ report += span_header("Their Vassals were...")
+ for(var/datum/antagonist/vassal/all_vassals as anything in vassals)
+ if(!all_vassals.owner)
+ continue
+ var/list/vassal_report = list()
+ vassal_report += "[all_vassals.owner.name]"
+
+ if(all_vassals.owner.assigned_role)
+ vassal_report += " the [all_vassals.owner.assigned_role]"
+ if(IS_FAVORITE_VASSAL(all_vassals.owner.current))
+ vassal_report += " and was the Favorite Vassal"
+ else if(IS_REVENGE_VASSAL(all_vassals.owner.current))
+ vassal_report += " and was the Revenge Vassal"
+ report += vassal_report.Join()
+
+ if(!LAZYLEN(objectives) || objectives_complete && optional_objectives_complete)
report += span_greentext(span_big("The [name] was successful!"))
else if(objectives_complete && !optional_objectives_complete)
report += span_marooned("The [name] survived, but has not made a name for [owner.current.p_them()]self...")
else
report += span_redtext(span_big("The [name] has failed!"))
report += get_flavor(objectives_complete, optional_objectives_complete)
- return report
+
+ return report.Join("
")
/// Evaluates the conditions of the bloodsucker at the end of each round to pick a flavor message to add
/datum/antagonist/bloodsucker/proc/get_flavor(objectives_complete, optional_objectives_complete)
@@ -385,53 +447,66 @@
else
//perish or just fuck up and fail your primary objectives
flavor_message += pick(list(
- "Thus ends the story of [ReturnFullName(TRUE)]. No doubt future generations will look back on your legacy and reflect on the lessons of the past. If they remember you at all."
+ "Thus ends the story of [return_full_name(TRUE)]. No doubt future generations will look back on your legacy and reflect on the lessons of the past. If they remember you at all."
))
flavor += "[flavor_message]"
return "[flavor.Join("
")]
"
/**
- * # Assigning Sol
+ * # Vampire Clan
*
- * Sol is the sunlight, during this period, all Bloodsuckers must be in their coffin, else they burn.
- * This was originally dealt with by the gamemode, but as gamemodes no longer exist, it is dealt with by the team.
+ * This is used for dealing with the Vampire Clan.
+ * This handles Sol for Bloodsuckers, making sure to not have several.
+ * None of this should appear in game, we are using it JUST for Sol. All Bloodsuckers should have their individual report.
*/
-/// Start Sol, called when someone is assigned Bloodsucker
-/datum/team/vampireclan/proc/check_start_sunlight()
- if(members.len <= 1)
- message_admins("New Sol has been created due to Bloodsucker assignment.")
- bloodsucker_sunlight = new()
-
-/// End Sol, if you're the last Bloodsucker
-/datum/team/vampireclan/proc/check_cancel_sunlight()
- // No minds in the clan? Delete Sol.
- if(members.len <= 1)
- message_admins("Sol has been deleted due to the lack of Bloodsuckers")
- QDEL_NULL(bloodsucker_sunlight)
+///Ranks the Bloodsucker up, called by Sol.
+/datum/antagonist/bloodsucker/proc/sol_rank_up(atom/source)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(RankUp))
+
+///Called when Sol is near starting.
+/datum/antagonist/bloodsucker/proc/sol_near_start(atom/source)
+ SIGNAL_HANDLER
+ if(bloodsucker_lair_area && !(locate(/datum/action/cooldown/bloodsucker/gohome) in powers))
+ BuyPower(new /datum/action/cooldown/bloodsucker/gohome)
+ if(my_clan?.get_clan() == CLAN_GANGREL && !(locate(/datum/action/cooldown/bloodsucker/gangrel/transform) in powers))
+ BuyPower(new /datum/action/cooldown/bloodsucker/gangrel/transform)
+
+///Called when Sol first ends.
+/datum/antagonist/bloodsucker/proc/on_sol_end(atom/source)
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, PROC_REF(check_end_torpor))
+ for(var/datum/action/cooldown/bloodsucker/power in powers)
+ if(istype(power, /datum/action/cooldown/bloodsucker/gohome))
+ RemovePower(power)
+ if(altar_uses)
+ to_chat(owner, span_boldnotice("Your Altar uses have been reset!"))
+ altar_uses = 0
/// Buying powers
-/datum/antagonist/bloodsucker/proc/BuyPower(datum/action/bloodsucker/power)
+/datum/antagonist/bloodsucker/proc/BuyPower(datum/action/cooldown/bloodsucker/power)
+ for(var/datum/action/cooldown/bloodsucker/current_powers as anything in powers)
+ if(current_powers.type == power.type)
+ return FALSE
powers += power
power.Grant(owner.current)
+ return TRUE
-/datum/antagonist/bloodsucker/proc/RemovePower(datum/action/bloodsucker/power)
- for(var/datum/action/bloodsucker/all_powers as anything in powers)
- if(initial(power.name) == all_powers.name)
- power = all_powers
- break
+/datum/antagonist/bloodsucker/proc/RemovePower(datum/action/cooldown/bloodsucker/power)
if(power.active)
power.DeactivatePower()
powers -= power
power.Remove(owner.current)
-/datum/antagonist/bloodsucker/proc/AssignStarterPowersAndStats()
- // Purchase Roundstart Powers
- BuyPower(new /datum/action/bloodsucker/feed)
- BuyPower(new /datum/action/bloodsucker/masquerade)
- if(!IS_VASSAL(owner.current)) // Favorite Vassal gets their own.
- BuyPower(new /datum/action/bloodsucker/veil)
- add_verb(owner.current, /mob/living/proc/explain_powers)
+/datum/antagonist/bloodsucker/proc/give_starting_powers()
+ for(var/datum/action/cooldown/bloodsucker/all_powers as anything in all_bloodsucker_powers)
+ if(!(initial(all_powers.purchase_flags) & BLOODSUCKER_DEFAULT_POWER))
+ continue
+ BuyPower(new all_powers)
+
+/datum/antagonist/bloodsucker/proc/assign_starting_stats()
// Traits: Species
var/mob/living/carbon/human/user = owner.current
if(ishuman(owner.current))
@@ -451,18 +526,12 @@
owner.current.grant_all_languages(FALSE, FALSE, TRUE)
owner.current.grant_language(/datum/language/vampiric)
/// Clear Disabilities & Organs
- HealVampireOrgans()
+ heal_vampire_organs()
/datum/antagonist/bloodsucker/proc/ClearAllPowersAndStats()
- /// Remove huds
- remove_hud()
// Powers
- remove_verb(owner.current, /mob/living/proc/explain_powers)
- while(powers.len)
- var/datum/action/bloodsucker/power = pick(powers)
- powers -= power
- power.Remove(owner.current)
- // owner.RemoveSpell(power)
+ for(var/datum/action/cooldown/bloodsucker/all_powers as anything in powers)
+ RemovePower(all_powers)
/// Stats
if(ishuman(owner.current))
var/mob/living/carbon/human/user = owner.current
@@ -490,12 +559,23 @@
user_eyes.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
user.update_sight()
+/datum/antagonist/bloodsucker/proc/give_masquerade_infraction()
+ if(broke_masquerade)
+ return
+ masquerade_infractions++
+ if(masquerade_infractions >= 3)
+ break_masquerade()
+ else
+ to_chat(owner.current, span_cultbold("You violated the Masquerade! Break the Masquerade [3 - masquerade_infractions] more times and you will become a criminal to the Bloodsucker's Cause!"))
+
+
/datum/antagonist/bloodsucker/proc/RankUp()
- set waitfor = FALSE
- var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(owner.current)
- if(!owner || !owner.current || vassaldatum)
+ if(!owner || !owner.current || IS_FAVORITE_VASSAL(owner.current))
return
bloodsucker_level_unspent++
+ if(!my_clan)
+ to_chat(owner.current, span_notice("You have gained a rank. Join a Clan to spend it."))
+ return
passive_blood_drain -= 0.03 * bloodsucker_level //do something. It's here because if you are gaining points through other means you are doing good
// Spend Rank Immediately?
if(!istype(owner.current.loc, /obj/structure/closet/crate/coffin))
@@ -508,96 +588,30 @@
/datum/antagonist/bloodsucker/proc/RankDown()
bloodsucker_level_unspent--
-/datum/antagonist/bloodsucker/proc/remove_nondefault_powers()
- for(var/datum/action/bloodsucker/power as anything in powers)
- if(istype(power, /datum/action/bloodsucker/feed) || istype(power, /datum/action/bloodsucker/masquerade) || istype(power, /datum/action/bloodsucker/veil))
+/datum/antagonist/bloodsucker/proc/remove_nondefault_powers(return_levels = FALSE)
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
+ if(istype(power, /datum/action/cooldown/bloodsucker/feed) || istype(power, /datum/action/cooldown/bloodsucker/masquerade) || istype(power, /datum/action/cooldown/bloodsucker/veil))
continue
RemovePower(power)
+ if(return_levels)
+ bloodsucker_level_unspent++
/datum/antagonist/bloodsucker/proc/LevelUpPowers()
- for(var/datum/action/bloodsucker/power as anything in powers)
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
power.level_current++
power.UpdateDesc()
///Disables all powers, accounting for torpor
-/datum/antagonist/bloodsucker/proc/DisableAllPowers()
- for(var/datum/action/bloodsucker/power as anything in powers)
- if((power.check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT(owner.current, TRAIT_NODEATH))
+/datum/antagonist/bloodsucker/proc/DisableAllPowers(forced = FALSE)
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
+ if(forced || ((power.check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT(owner.current, TRAIT_NODEATH)))
if(power.active)
power.DeactivatePower()
-/datum/antagonist/bloodsucker/proc/SpendRank(spend_rank = TRUE, ask = TRUE)
- set waitfor = FALSE
-
- if(!owner || !owner.current || !owner.current.client || (spend_rank && bloodsucker_level_unspent <= 0.5))
+/datum/antagonist/bloodsucker/proc/SpendRank(mob/living/carbon/human/target, cost_rank = TRUE, blood_cost, ask = TRUE)
+ if(!owner || !owner.current || !owner.current.client || (cost_rank && bloodsucker_level_unspent <= 0.5))
return
- // Purchase Power Prompt
- var/list/options = list()
- for(var/datum/action/bloodsucker/power as anything in all_bloodsucker_powers)
- if(initial(power.purchase_flags) & BLOODSUCKER_CAN_BUY && !(locate(power) in powers))
- options[initial(power.name)] = power
-
- if(options.len < 1)
- to_chat(owner.current, span_notice("You grow more ancient by the night!"))
- else
- // Give them the UI to purchase a power.
- var/choice = tgui_input_list(owner.current, "You have the opportunity to grow more ancient, increasing the level of all your powers by 1. Select a power to advance your Rank.", "Your Blood Thickens...", options)
- // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
- if(spend_rank && bloodsucker_level_unspent <= 0)
- return
- // Did you choose a power?
- if(!choice || !options[choice])
- to_chat(owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later."))
- return
- if((locate(options[choice]) in powers))
- to_chat(owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later."))
- return
- // Prevent Bloodsuckers from purchasing a power while outside of their Coffin.
- if(!istype(owner.current.loc, /obj/structure/closet/crate/coffin))
- to_chat(owner.current, span_warning("You must be in your Coffin to purchase Powers."))
- return
-
- // Good to go - Buy Power!
- var/datum/action/bloodsucker/purchased_power = options[choice]
- BuyPower(new purchased_power)
- to_chat(owner.current, span_notice("You have learned how to use [choice]!"))
-
- // Advance Powers - Includes the one you just purchased.
- LevelUpPowers()
- // Bloodsucker-only Stat upgrades
- bloodsucker_regen_rate += 0.05
- max_blood_volume += 100
- // Misc. Stats Upgrades
- if(ishuman(owner.current))
- var/mob/living/carbon/human/user = owner.current
- var/datum/species/user_species = user.dna.species
- user_species.punchdamagelow += 0.5
- // This affects the hitting power of Brawn.
- user_species.punchdamagehigh += 0.5
- user_species.punchstunthreshold += 0.5
-
- // We're almost done - Spend your Rank now.
- bloodsucker_level++
- if(spend_rank)
- bloodsucker_level_unspent--
- // Ranked up enough? Let them join a Clan.
- if(bloodsucker_level == 3 && my_clan == NONE) //updated for tzimisce
- AssignClanAndBane()
-
- // Ranked up enough to get your true Reputation?
- if(bloodsucker_level == 4)
- SelectReputation(am_fledgling = FALSE, forced = TRUE)
-
- // Done! Let them know & Update their HUD.
- to_chat(owner.current, span_notice("You are now a rank [bloodsucker_level] Bloodsucker. Your strength, health, feed rate, regen rate, and maximum blood capacity have all increased!\n\
- * Your existing powers have all ranked up as well!"))
- update_hud(owner.current)
- owner.current.playsound_local(null, 'sound/effects/pope_entry.ogg', 25, TRUE, pressure_affected = FALSE)
- if(bloodsucker_level_unspent && spend_rank)
- if(ask)
- if(tgui_alert(owner.current, "You have leftover ranks, do you want to spend them all?", "Time Management Team", list("Yes", "No")) == "No")
- return
- SpendRank(ask = FALSE)
+ SEND_SIGNAL(src, BLOODSUCKER_RANK_UP, target, cost_rank, blood_cost, ask)
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -615,13 +629,15 @@
// Objective 1: Vassalize a Head/Command, or a specific target
var/list/rolled_objectives = list()
- switch(rand(1, 3))
+ switch(rand(1, 4))
if(1) //Drink Objective
rolled_objectives = list(new /datum/objective/bloodsucker/gourmand)
if(2) //Protege Objective
rolled_objectives = list(new /datum/objective/bloodsucker/protege)
- if(3) //Heart Thief
+ if(3) //Heart Thief Objective
rolled_objectives = list(new /datum/objective/bloodsucker/heartthief)
+ if(4) //Vassal Specific Objective
+ rolled_objectives = list(new /datum/objective/bloodsucker/vassalhim)
for(var/datum/objective/bloodsucker/objective in rolled_objectives)
objective.owner = owner
@@ -630,7 +646,7 @@
/// Name shown on antag list
/datum/antagonist/bloodsucker/antag_listing_name()
- return ..() + "([ReturnFullName(TRUE)])"
+ return ..() + "([return_full_name(TRUE)])"
/// Whatever interesting things happened to the antag admins should know about
/// Include additional information about antag in this part
@@ -682,7 +698,7 @@
bloodsucker_title = pick ("Count","Baron","Viscount","Prince","Duke","Tzar","Dreadlord","Lord","Master")
else
bloodsucker_title = pick ("Countess","Baroness","Viscountess","Princess","Duchess","Tzarina","Dreadlady","Lady","Mistress")
- to_chat(owner, span_announce("You have earned a title! You are now known as [ReturnFullName(TRUE)]!"))
+ to_chat(owner, span_announce("You have earned a title! You are now known as [return_full_name(TRUE)]!"))
// Titles [Fledgling]
else
bloodsucker_title = null
@@ -716,13 +732,13 @@
"Corrupt","Hellspawn","Tyrant","Sanguineous",
)
- to_chat(owner, span_announce("You have earned a reputation! You are now known as [ReturnFullName(TRUE)]!"))
+ to_chat(owner, span_announce("You have earned a reputation! You are now known as [return_full_name(TRUE)]!"))
/datum/antagonist/bloodsucker/proc/AmFledgling()
return !bloodsucker_title
-/datum/antagonist/bloodsucker/proc/ReturnFullName(include_rep = FALSE)
+/datum/antagonist/bloodsucker/proc/return_full_name(include_rep = FALSE)
var/fullname
// Name First
@@ -744,20 +760,9 @@
to_chat(owner.current, span_cultboldtalic("You have broken the Masquerade!"))
to_chat(owner.current, span_warning("Bloodsucker Tip: When you break the Masquerade, you become open for termination by fellow Bloodsuckers, and your Vassals are no longer completely loyal to you, as other Bloodsuckers can steal them for themselves!"))
broke_masquerade = TRUE
- set_antag_hud(owner.current, "masquerade_broken")
- for(var/datum/mind/clan_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker))
- if(owner == clan_minds)
- continue
- if(!isliving(clan_minds.current))
- continue
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = clan_minds.has_antag_datum(/datum/antagonist/bloodsucker)
- to_chat(clan_minds, span_userdanger("[owner.current] has broken the Masquerade![bloodsuckerdatum.my_clan == CLAN_TOREADOR ? "Ensure they are eliminated at all costs!" : ""]"))
- if(bloodsuckerdatum.my_clan == CLAN_TOREADOR)
- var/datum/objective/assassinate/masquerade_objective = new /datum/objective/assassinate
- masquerade_objective.target = owner.current
- masquerade_objective.explanation_text = "Ensure [owner.current], who has broken the Masquerade, suffers Final Death."
- bloodsuckerdatum.objectives += masquerade_objective
- clan_minds.announce_objectives()
+ antag_hud_name = "masquerade_broken"
+ add_team_hud(owner.current)
+ SEND_GLOBAL_SIGNAL(COMSIG_BLOODSUCKER_BROKE_MASQUERADE)
///This is admin-only of reverting a broken masquerade.
/datum/antagonist/bloodsucker/proc/fix_masquerade()
@@ -765,191 +770,21 @@
return
to_chat(owner.current, span_cultboldtalic("You have re-entered the Masquerade."))
broke_masquerade = FALSE
- set_antag_hud(owner.current, "bloodsucker")
-
-
-/////////////////////////////////////
-// BLOOD COUNTER & RANK MARKER ! //
-/////////////////////////////////////
-/datum/antagonist/bloodsucker/proc/remove_hud()
- owner.current.hud_used.blood_display.invisibility = INVISIBILITY_ABSTRACT
- owner.current.hud_used.vamprank_display.invisibility = INVISIBILITY_ABSTRACT
- owner.current.hud_used.sunlight_display.invisibility = INVISIBILITY_ABSTRACT
-
-/// Update Blood Counter + Rank Counter
-/datum/antagonist/bloodsucker/proc/update_hud(updateRank = FALSE)
- if(!owner.current.hud_used)
- return
- var/valuecolor
- if(owner.current.hud_used && owner.current.hud_used.blood_display)
- if(owner.current.blood_volume > BLOOD_VOLUME_SAFE(owner.current))
- valuecolor = "#FFDDDD"
- else if(owner.current.blood_volume > BLOOD_VOLUME_BAD(owner.current))
- valuecolor = "#FFAAAA"
- owner.current.hud_used.blood_display.update_counter(owner.current.blood_volume, valuecolor)
- if(owner.current.hud_used && owner.current.hud_used.vamprank_display)
- owner.current.hud_used.vamprank_display.update_counter(bloodsucker_level, valuecolor)
- /// Only change icon on special request.
- if(updateRank)
- owner.current.hud_used.vamprank_display.icon_state = (bloodsucker_level_unspent > 0) ? "rank_up" : "rank"
-
-/// Update Sun Time
-/datum/antagonist/bloodsucker/proc/update_sunlight(value, amDay = FALSE)
- if(!owner.current.hud_used)
- return
- var/valuecolor
- if(owner.current.hud_used && owner.current.hud_used.sunlight_display)
- var/sunlight_display_icon = "sunlight_"
- if(amDay)
- sunlight_display_icon += "day"
- valuecolor = "#FF5555"
- else
- switch(round(value, 1))
- if(0 to 30)
- sunlight_display_icon += "30"
- valuecolor = "#FFCCCC"
- if(31 to 60)
- sunlight_display_icon += "60"
- valuecolor = "#FFE6CC"
- if(61 to 90)
- sunlight_display_icon += "90"
- valuecolor = "#FFFFCC"
- else
- sunlight_display_icon += "night"
- valuecolor = "#FFFFFF"
-
- var/value_string = (value >= 60) ? "[round(value / 60, 1)] m" : "[round(value, 1)] s"
- owner.current.hud_used.sunlight_display.update_counter(value_string, valuecolor)
- owner.current.hud_used.sunlight_display.icon_state = sunlight_display_icon
-
-/atom/movable/screen/bloodsucker/blood_counter/update_counter(value, valuecolor)
- ..()
- maptext = "[round(value,1)]
"
-
-/atom/movable/screen/bloodsucker/rank_counter/update_counter(value, valuecolor)
- ..()
- maptext = "[round(value,1)]
"
-
-/atom/movable/screen/bloodsucker/sunlight_counter/update_counter(value, valuecolor)
- ..()
- maptext = "[value]
"
-
-
-/**
- * # Assigning Bloodsucker status
- *
- * Here we assign the Bloodsuckers themselves, ensuring they arent Plasmamen
- * Also deals with Vassalization status.
- */
-
-/datum/mind/proc/prepare_bloodsucker(datum/mind/convertee, datum/mind/converter)
- // Species Must have a HEART (Sorry Plasmamen)
- var/mob/living/carbon/human/user = convertee.current
- if(!(user.dna?.species) || !(user.mob_biotypes & MOB_ORGANIC))
- user.set_species(/datum/species/human)
- user.apply_pref_name(/datum/preference/name/real_name, user.client)
- // Check for Fledgeling
- if(converter)
- message_admins("[convertee] has become a Bloodsucker, and was created by [converter].")
- log_admin("[convertee] has become a Bloodsucker, and was created by [converter].")
- return TRUE
-
-/datum/mind/proc/make_bloodsucker(datum/mind/bloodsucker)
- if(bloodsucker)
- var/mob/living/carbon/human/user = bloodsucker.current
- if(!(user.dna?.species) || !(user.mob_biotypes & MOB_ORGANIC))
- prepare_bloodsucker(bloodsucker)
- add_antag_datum(/datum/antagonist/bloodsucker)
- return TRUE
- else
- return
-
-/datum/mind/proc/remove_bloodsucker()
- var/datum/antagonist/bloodsucker/removed_bloodsucker = has_antag_datum(/datum/antagonist/bloodsucker)
- if(removed_bloodsucker)
- remove_antag_datum(/datum/antagonist/bloodsucker)
- special_role = null
-
-/datum/antagonist/bloodsucker/proc/can_make_vassal(mob/living/converted, datum/mind/converter, can_vassal_sleeping = FALSE)//, check_antag_or_loyal=FALSE)
- // Not Correct Type: Abort
- if(!iscarbon(converted) || !converter)
- return FALSE
- if(converted.stat > UNCONSCIOUS && !can_vassal_sleeping)
- return FALSE
- // No Mind!
- if(!converted.mind)
- to_chat(converter, span_danger("[converted] isn't self-aware enough to be made into a Vassal."))
- return FALSE
- // Already MY Vassal
- var/datum/antagonist/vassal/vassaldatum = converted.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(istype(vassaldatum) && vassaldatum.master)
- if(vassaldatum.master.owner == converter)
- to_chat(converter, span_danger("[converted] is already your loyal Vassal!"))
- else
- to_chat(converter, span_danger("[converted] is the loyal Vassal of another Bloodsucker!"))
- return FALSE
- // Already Antag or Loyal (Vamp Hunters count as antags)
- if(!isnull(converted.mind.enslaved_to) || AmInvalidAntag(converted))
- to_chat(converter, span_danger("[converted] resists the power of your blood to dominate their mind!"))
- return FALSE
- return TRUE
-
-/datum/antagonist/bloodsucker/proc/AmValidAntag(mob/target)
- /// Check if they are an antag, if so, check if they're Invalid.
- if(target.mind?.special_role || !isnull(target.mind?.antag_datums))
- return !AmInvalidAntag(target)
- /// Otherwise, just cancel out.
- return FALSE
-
-/datum/antagonist/bloodsucker/proc/AmInvalidAntag(mob/target)
- /// Not an antag?
- if(!is_special_character(target))
- return FALSE
- /// Checks if the person is an antag banned from being vassalized, stored in bloodsucker's datum.
- for(var/datum/antagonist/antag_datum in target.mind.antag_datums)
- if(antag_datum.type in vassal_banned_antags)
- //message_admins("DEBUG VASSAL: Found Invalid: [antag_datum] // [antag_datum.type]")
- return TRUE
-// message_admins("DEBUG VASSAL: Valid Antags! (total of [target.antag_datums.len])")
- // WHEN YOU DELETE THE ABOVE: Remove the 3 second timer on converting the vassal too.
- return FALSE
-
-/datum/antagonist/bloodsucker/proc/attempt_turn_vassal(mob/living/carbon/convertee, can_vassal_sleeping = FALSE)
- convertee.silent = 0
- return make_vassal(convertee, owner, can_vassal_sleeping)
-
-/datum/antagonist/bloodsucker/proc/make_vassal(mob/living/convertee, datum/mind/converter, sleeping = FALSE)
- if(!can_make_vassal(convertee, converter, can_vassal_sleeping = sleeping))
- return FALSE
- // Make Vassal
- var/datum/antagonist/vassal/vassaldatum = new(convertee.mind)
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = converter.has_antag_datum(/datum/antagonist/bloodsucker)
- vassaldatum.master = bloodsuckerdatum
- convertee.mind.add_antag_datum(vassaldatum, vassaldatum.master.get_team())
- // Update Bloodsucker Title
- bloodsuckerdatum.SelectTitle(am_fledgling = FALSE) // Only works if you have no title yet.
- // Log it
- message_admins("[convertee] has become a Vassal, and is enslaved to [converter].")
- log_admin("[convertee] has become a Vassal, and is enslaved to [converter].")
- return TRUE
-
-/**
- * # HUD
- */
-/datum/antagonist/bloodsucker/proc/update_bloodsucker_icons_added(datum/mind/m)
- var/datum/atom_hud/antag/vamphud = GLOB.huds[ANTAG_HUD_BLOODSUCKER]
- vamphud.join_hud(owner.current)
- set_antag_hud(owner.current, "bloodsucker")
+/datum/antagonist/bloodsucker/get_preview_icon()
+ var/icon/final_icon = render_preview_outfit(/datum/outfit/bloodsucker_outfit)
+ final_icon.Blend(icon('icons/effects/blood.dmi', "uniformblood"), ICON_OVERLAY)
-/datum/antagonist/bloodsucker/proc/update_bloodsucker_icons_removed(datum/mind/m)
- var/datum/atom_hud/antag/vamphud = GLOB.huds[ANTAG_HUD_BLOODSUCKER]
- vamphud.leave_hud(owner.current)
- set_antag_hud(owner.current, null)
+ return finish_preview_icon(final_icon)
-/datum/antagonist/bloodsucker/get_preview_icon()
- var/icon/bloodsucker_icon = icon('icons/mob/bloodsucker_mobs.dmi', "batform")
+/datum/outfit/bloodsucker_outfit
+ name = "Bloodsucker outfit (Preview only)"
+ suit = /obj/item/clothing/suit/dracula
- bloodsucker_icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)
+/datum/outfit/bloodsucker_outfit/post_equip(mob/living/carbon/human/enrico, visualsOnly=FALSE)
+ enrico.hair_style = "Undercut"
+ enrico.hair_color = "FFF"
+ enrico.skin_tone = "african2"
- return bloodsucker_icon
+ enrico.update_body()
+ enrico.update_hair()
diff --git a/code/modules/antagonists/bloodsuckers/bloodsuckers_objects.dm b/code/modules/antagonists/bloodsuckers/bloodsuckers_objects.dm
index 8e5cca8f492a..53a024bd218f 100644
--- a/code/modules/antagonists/bloodsuckers/bloodsuckers_objects.dm
+++ b/code/modules/antagonists/bloodsuckers/bloodsuckers_objects.dm
@@ -17,32 +17,31 @@
/obj/item/restraints/legcuffs/beartrap/bloodsucker/attack_self(mob/user)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- lair_area = bloodsuckerdatum.lair
- lair_owner = user
+ lair_area = bloodsuckerdatum?.bloodsucker_lair_area
START_PROCESSING(SSobj, src)
if(!bloodsuckerdatum)
- to_chat(user, span_notice("Although it seems simple you have no idea how to reactivate the stake trap."))
+ to_chat(user, span_notice("Although it seems simple you have no idea how to reactivate [src]."))
return
if(armed)
STOP_PROCESSING(SSobj,src)
return ..() //disarm it, otherwise continue to try and place
- if(!bloodsuckerdatum.lair)
+ if(!bloodsuckerdatum.bloodsucker_lair_area)
to_chat(user, span_danger("You don't have a lair. Claim a coffin to make that location your lair."))
return
if(lair_area != get_area(src))
to_chat(user, span_danger("You may only activate this trap in your lair: [lair_area]."))
return
- lair_area = bloodsuckerdatum.lair
+ lair_area = bloodsuckerdatum.bloodsucker_lair_area
lair_owner = user
START_PROCESSING(SSobj, src)
- ..()
+ return ..()
/obj/item/restraints/legcuffs/beartrap/bloodsucker/Crossed(AM as mob|obj)
var/mob/living/carbon/human/user = AM
if(armed && (IS_BLOODSUCKER(user) || IS_VASSAL(user)))
to_chat(user, span_notice("You gracefully step over the blood puddle and avoid triggering the trap"))
return
- ..()
+ return ..()
/obj/item/restraints/legcuffs/beartrap/bloodsucker/close_trap()
STOP_PROCESSING(SSobj, src)
@@ -73,10 +72,7 @@
//////////////////////
/// Do I have a stake in my heart?
-/mob/proc/AmStaked()
- return FALSE
-
-/mob/living/AmStaked()
+/mob/living/proc/am_staked()
var/obj/item/bodypart/chosen_bodypart = get_bodypart(BODY_ZONE_CHEST)
if(!chosen_bodypart)
return FALSE
@@ -198,6 +194,97 @@
. = ..()
AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50)
+//////////////////////
+// TOOLS //
+//////////////////////
+
+/obj/item/bloodsucker
+ icon = 'icons/obj/vamp_obj.dmi'
+ lefthand_file = 'icons/mob/inhands/antag/bs_leftinhand.dmi'
+ righthand_file = 'icons/mob/inhands/antag/bs_rightinhand.dmi'
+
+/obj/item/bloodsucker/chisel
+ name = "chisel"
+ desc = "Despite not being the most precise or faster tool, it feels the best to work with nonetheless."
+ icon_state = "chisel"
+ slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_POCKET
+ w_class = WEIGHT_CLASS_SMALL
+ attack_verb = list("chiseled", "stabbed", "poked")
+ sharpness = SHARP_POINTY
+ force = 6
+ throwforce = 4
+
+/* #define PAINTING_TYPE_MESMERIZE "Mesmerizing Painting"
+#define PAINTING_TYPE_CHARM "Charming Painting"
+#define PAINTING_TYPE_CREEPY "Creepy Painting"
+
+/obj/item/bloodsucker/bloodybrush
+ name = "paintbrush"
+ desc = "Draws from a source that should never run dry, an artist's dream."
+ icon_state = "brush"
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/bloodsucker/bloodybrush/proc/paint(mob/living/painter, obj/item/canvas/raw_piece)
+ var/list/possible_effects = list()
+ var/painting_type = tgui_input_list(painter, "You paint a...", "Conscience Flux", possible_effects)
+ var/base_x = painter.pixel_x
+ var/base_y = painter.pixel_y
+ animate(painter, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS, loop = -1)
+ balloon_alert(painter, "a little bit here...")
+ var/message = TRUE
+ for(var/i in 1 to 25)
+ var/x_offset = base_x + rand(-3, 3)
+ var/y_offset = base_y + rand(-3, 3)
+ animate(pixel_x = x_offset, pixel_y = y_offset, time = 0.1 SECONDS)
+ if(message)
+ balloon_alert(painter, "..fill that up there...")
+ message = FALSE
+ if(!do_after(painter, 10 SECONDS, raw_piece))
+ animate(painter, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS)
+ balloon_alert(painter, "..ah... dammit.")
+ return FALSE
+ animate(painter, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS)
+ balloon_alert(painter, "..and it's done.")
+ qdel(raw_piece)
+ var/obj/structure/sign/painting/sign_to_crop = new /obj/structure/sign/painting(get_turf(painter))
+ var/obj/item/wirecutters/cutters = new /obj/item/wirecutters(get_turf(painter))
+ sign_to_crop.load_persistent()
+ sign_to_crop.C.special_effect = painting_type
+ sign_to_crop.wirecutter_act(painter, cutters)
+ qdel(sign_to_crop)
+ qdel(cutters)
+
+/obj/item/canvas/attackby(obj/item/I, mob/living/user, params)
+ if(!istype(I, /obj/item/bloodsucker/bloodybrush))
+ return ..()
+ if(!IS_BLOODSUCKER(user))
+ return
+ var/obj/item/bloodsucker/bloodybrush/brush = I
+ var/turf/current_turf = get_turf(src)
+ if(!LAZYFIND(current_turf.contents, /obj/structure/easel))
+ to_chat(user, span_warning("You need a easel to support your canvas while you paint!"))
+ return
+ brush.paint(user, src)
+
+#undef PAINTING_TYPE_MESMERIZE
+#undef PAINTING_TYPE_CHARM
+#undef PAINTING_TYPE_CREEPY */
+
+//////////////////////
+// MISC //
+//////////////////////
+
+/obj/item/bloodsucker/abyssal_essence
+ name = "abyssal essence"
+ desc = "As you glare at the abyssal essence, you feel it glaring back."
+ icon_state = "abyssal_essence"
+ item_state = "abyssal_essence"
+ throwforce = 0
+ w_class = WEIGHT_CLASS_TINY
+ throw_speed = 3
+ throw_range = 7
+ pressure_resistance = 10
+
//////////////////////
// ARCHIVES //
//////////////////////
@@ -253,141 +340,54 @@
// target is the person being hit here
/obj/item/book/kindred/afterattack(mob/living/target, mob/living/user, flag, params)
. = ..()
- if(!user.can_read(src))
+ if(!user.can_read(src) || in_use || (target == user) || !ismob(target))
return
// Curator/Tremere using it
- if(HAS_TRAIT(user, TRAIT_BLOODSUCKER_HUNTER))
- if(in_use || (target == user) || !ismob(target))
- return
- user.visible_message(span_notice("[user] begins to quickly look through [src], repeatedly looking back up at [target]."))
- in_use = TRUE
- if(!do_mob(user, target, 3 SECONDS, NONE, TRUE))
- to_chat(user, span_notice("You quickly close [src]."))
- in_use = FALSE
+ if(!HAS_TRAIT(user.mind, TRAIT_BLOODSUCKER_HUNTER))
+ if(IS_BLOODSUCKER(user))
+ to_chat(user, span_notice("[src] seems to be too complicated for you. It would be best to leave this for someone else to take."))
return
- in_use = FALSE
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(target)
- // Are we a Bloodsucker | Are we on Masquerade. If one is true, they will fail.
- if(IS_BLOODSUCKER(target) && !HAS_TRAIT(target, TRAIT_MASQUERADE))
- if(bloodsuckerdatum.broke_masquerade)
- to_chat(user, span_warning("[target], also known as '[bloodsuckerdatum.ReturnFullName(TRUE)]', is indeed a Bloodsucker, but you already knew this."))
- return
- else
- to_chat(user, span_warning("You found the one! [target], also known as '[bloodsuckerdatum.ReturnFullName(TRUE)]', is not knowingly part of a Clan. You quickly note this information down, memorizing it."))
- bloodsuckerdatum.break_masquerade()
- else
- to_chat(user, span_notice("You fail to draw any conclusions to [target] being a Bloodsucker."))
- // Bloodsucker using it
- else if(IS_BLOODSUCKER(user))
- to_chat(user, span_notice("[src] seems to be too complicated for you. It would be best to leave this for someone else to take."))
- else
to_chat(user, span_warning("[src] burns your hands as you try to use it!"))
- user.apply_damage(12, BURN, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
-
-/*
- * # Reading the Book
- */
-/obj/item/book/kindred/attack_self(mob/living/carbon/user)
-// Don't call parent since it handles reading the book.
-// . = ..()
- if(!user.can_read(src))
+ user.apply_damage(3, BURN, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
return
- // Curator/Tremere using it
- if(HAS_TRAIT(user, TRAIT_BLOODSUCKER_HUNTER))
+
+ in_use = TRUE
+ user.balloon_alert_to_viewers(user, "reading book...", "looks at [target] and [src]")
+ if(!do_after(user, 3 SECONDS, target))
+ to_chat(user, span_notice("You quickly close [src]."))
+ in_use = FALSE
+ return
+ if(HAS_TRAIT(user.mind, TRAIT_BLOODSUCKER_HUNTER))
user.visible_message(span_notice("[user] opens [src] and begins reading intently."))
ui_interact(user)
return
- // Bloodsucker using it
- if(IS_BLOODSUCKER(user))
- to_chat(user, span_notice("[src] seems to be too complicated for you. It would be best to leave this for someone else to take."))
- return
- to_chat(user, span_warning("You feel your eyes burn as you begin to read through [src]!"))
- var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES)
- user.blur_eyes(5)
- eyes.applyOrganDamage(5)
+ in_use = FALSE
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(target)
+ // Are we a Bloodsucker | Are we on Masquerade. If one is true, they will fail.
+ if(IS_BLOODSUCKER(target) && !HAS_TRAIT(target, TRAIT_MASQUERADE))
+ if(bloodsuckerdatum.broke_masquerade)
+ to_chat(user, span_warning("[target], also known as '[bloodsuckerdatum.return_full_name()]', is indeed a Bloodsucker, but you already knew this."))
+ return
+ to_chat(user, span_warning("[target], also known as '[bloodsuckerdatum.return_full_name()]', [bloodsuckerdatum.my_clan ? "is part of the [bloodsuckerdatum.my_clan]!" : "is not part of a clan."] You quickly note this information down, memorizing it."))
+ bloodsuckerdatum.break_masquerade()
+ else
+ to_chat(user, span_notice("You fail to draw any conclusions to [target] being a Bloodsucker."))
+
+/obj/item/book/kindred/attack_self(mob/living/user)
+ ui_interact(user)
/obj/item/book/kindred/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, "KindredArchives", name)
+ ui = new(user, src, "KindredBook", name)
ui.open()
-/obj/item/book/kindred/ui_act(action, params)
- . = ..()
- if(.)
- return
- if(!action)
- return FALSE
- SStgui.close_uis(src)
- INVOKE_ASYNC(src, PROC_REF(search), usr, action)
-
-// Flavortext stuff
-/obj/item/book/kindred/proc/search(mob/reader, clan)
- dat = "This is all knowledge about the Clan:
"
- switch(clan)
- if(CLAN_BRUJAH)
- dat += "This Clan has proven to be the strongest in melee combat, boasting a powerful punch.
\
- They also appear to be more calm than the others, entering their 'frenzies' whenever they want, but dont seem affected.
\
- Be wary, as they are fearsome warriors, rebels and anarchists, with an inclination towards Frenzy.
\
- Favorite Vassal: Their favorite Vassal gains the Brawn ability. \
- Strength: Frenzy will not kill them, punches deal a lot of damage.
\
- Weakness: They have to spend Blood on powers while in Frenzy too."
- if(CLAN_TOREADOR)
- dat += "The most charming Clan of them all, being borderline party animals, allowing them to very easily disguise among the crew.
\
- They are more in touch with their morals, so they suffer and benefit more strongly from the humanity cost or gain of their actions.
\
- They can be best defined as 'The most humane kind of vampire', due to their kindred with an obsession with perfectionism and beauty
\
- Favorite Vassal: Their favorite Vassal gains the Mesmerize ability \
- Strength: Highly charismatic and influential.
\
- Weakness: Morally weak."
- if(CLAN_NOSFERATU)
- dat += "This Clan has been the most obvious to find information about.
\
- They are disfigured, ghoul-like vampires upon embrace by their Sire, scouts that travel through desolate paths to avoid violating the Masquerade.
\
- They make no attempts at hiding themselves within the crew, and have a terrible taste for heavy items.
\
- They also seem to manage to fit themsleves into small spaces such as vents.
\
- Favorite Vassal: Their Favorite Vassal gains the ability to ventcrawl while naked and becomes disfigured. \
- Strength: Ventcrawl.
\
- Weakness: Can't disguise themselves, permanently pale, can easily be discovered by their DNA or Blood Level."
- if(CLAN_TREMERE)
- dat += "This Clan seems to hate entering the Chapel.
\
- They are a secluded Clan, they are Vampires who've mastered the power of blood, and seek knowledge.
\
- They appear to be focused more on their Blood Magic than their other Powers, getting stronger faster the more Vassals they have.
\
- They have 3 different paths they can take, from reviving people as Vassals, to stealing blood with beams made of the same essence.
\
- Favorite Vassal: Their Favorite Vassal gains the ability to shift into a Bat at will. \
- Strength: 3 different Powers that get stupidly strong overtime.
\
- Weakness: Cannot get regular Powers, with no way to get stun resistance outside of Frenzy."
- if(CLAN_GANGREL)
- dat += "This Clan seems to be closer to Animals than to other Vampires.
\
- They also go by the name of Werewolves, as that is what appears when they enter a Frenzy.
\
- Despite this, they appear to be scared of 'True Faith', someone's ultimate and undying Faith, which itself doesn't require being something Religious.
\
- They hate seeing many people, and tend to avoid Stations that have more crewmembers than Nanotrasen's average. Due to this, they are harder to find than others.
\
- Favorite Vassal: Their Favorite Vassal turns into a Werewolf whenever their Master does.. \
- Strength: Feral, Werewolf during Frenzy.
\
- Weakness: Weak to True Faith."
- if(CLAN_VENTRUE)
- dat += "This Clan seems to despise drinking from non sentient organics.
\
- They are Masters of manipulation, Greedy and entitled. Authority figures between the kindred society.
\
- They seem to take their Vassal's lives very seriously, going as far as to give Vassals some of their own Blood.
\
- Compared to other types, this one relies on their Vassals, rather than fighting for themselves.
\
- Favorite Vassal: Their Favorite Vassal will slowly be turned into a Bloodsucker overtime. \
- Strength: Slowly turns a Vassal into a Bloodsucker.
\
- Weakness: Does not gain more abilities overtime, it is best to target the Bloodsucker over the Vassal."
- if(CLAN_MALKAVIAN)
- dat += "There is barely any information known about this Clan.
\
- Members of this Clan seems to mumble things to themselves, unaware of their surroundings.
\
- They also seem to enter and dissapear into areas randomly, as if not even they know where they are.
\
- Favorite Vassal: Unknown. \
- Strength: Unknown.
\
- Weakness: Unknown."
- if(CLAN_LASOMBRA)
- dat += "This Clan seems to adore living in the Shadows and worshipping it's secrets.
\
- They take their research and vanity seriously, they are always very proud of themselves after even minor achievements.
\
- They appear to be in search of a station with a veil weakness to be able to channel their shadow's abyssal powers.
\
- Their research into this sector has lead them to adopt red eyes as to view better in the shadows, which leads unmasked Lasombras to be easily identifiable.
\
- Homewer they have appeared to have also evolved a hard chintin in their veins, which makes them invulnerable to brute damage.
\
- Favorite Vassal: Their Favorite Vassal appears to have been imbued with abyssal essence and is able to blend in with the shadows. \
- Strength: They are able to slowly advance their abilities.
\
- Weakness: Immensely weak to burn damage."
- if(CLAN_TZIMISCE)
- dat += "The page is covered in blood..."
-
- reader << browse("Penned by [author].
" + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]")
+/obj/item/book/kindred/ui_static_data(mob/user)
+ var/data = list()
+ for(var/datum/bloodsucker_clan/clans as anything in subtypesof(/datum/bloodsucker_clan))
+ var/clan_data = list()
+ clan_data["clan_name"] = initial(clans.name)
+ clan_data["clan_desc"] = initial(clans.description)
+ data["clans"] += list(clan_data)
+
+ return data
diff --git a/code/modules/antagonists/bloodsuckers/clans/_clan.dm b/code/modules/antagonists/bloodsuckers/clans/_clan.dm
new file mode 100644
index 000000000000..b7de430e0442
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/clans/_clan.dm
@@ -0,0 +1,262 @@
+/**
+ * Bloodsucker clans
+ *
+ * Handles everything related to clans.
+ * the entire idea of datumizing this came to me in a dream.
+ */
+/datum/bloodsucker_clan
+ ///The bloodsucker datum that owns this clan. Use this over 'source', because while it's the same thing, this is more consistent (and used for deletion).
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum
+ ///The name of the clan we're in.
+ var/name = CLAN_NONE
+ ///Description of what the clan is, given when joining and through your antag UI.
+ var/description = "The Caitiff is as basic as you can get with Bloodsuckers. \n\
+ Entirely Clan-less, they are blissfully unaware of who they really are. \n\
+ No additional abilities is gained, nothing is lost, if you want a plain Bloodsucker, this is it. \n\
+ The Favorite Vassal will gain the Brawn ability, to help in combat."
+ ///The clan objective that is required to greentext.
+ var/datum/objective/bloodsucker/clan_objective
+ ///The icon of the radial icon to join this clan.
+ var/join_icon = 'icons/mob/bloodsucker_clan_icons.dmi'
+ ///Same as join_icon, but the state
+ var/join_icon_state = "caitiff"
+ ///Description shown when trying to join the clan.
+ var/join_description = "The default, Classic Bloodsucker."
+ ///Whether the clan can be joined by players. FALSE for flavortext-only clans.
+ var/joinable_clan = TRUE
+
+ ///How the Bloodsucker ranks up, if they do.
+ var/rank_up_type = BLOODSUCKER_RANK_UP_NORMAL
+ ///Whether they become entirely stun immune when entering Frenzy.
+ var/frenzy_stun_immune = FALSE
+ ///The clan's manipulation specialty.
+ var/control_type = BLOODSUCKER_CONTROL_BLOOD
+ ///How we will drink blood using Feed.
+ var/blood_drink_type = BLOODSUCKER_DRINK_NORMAL
+
+/datum/bloodsucker_clan/New(datum/antagonist/bloodsucker/owner_datum)
+ . = ..()
+ src.bloodsuckerdatum = owner_datum
+
+ RegisterSignal(bloodsuckerdatum, COMSIG_BLOODSUCKER_ON_LIFETICK, PROC_REF(handle_clan_life))
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_RANK_UP, PROC_REF(on_spend_rank))
+
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_PRE_MAKE_FAVORITE, PROC_REF(on_offer_favorite))
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_MAKE_FAVORITE, PROC_REF(on_favorite_vassal))
+
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_MADE_VASSAL, PROC_REF(on_vassal_made))
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_EXIT_TORPOR, PROC_REF(on_exit_torpor))
+ RegisterSignal(bloodsuckerdatum, BLOODSUCKER_FINAL_DEATH, PROC_REF(on_final_death))
+
+ give_clan_objective()
+
+/datum/bloodsucker_clan/Destroy(force)
+ UnregisterSignal(bloodsuckerdatum, list(
+ COMSIG_BLOODSUCKER_ON_LIFETICK,
+ BLOODSUCKER_RANK_UP,
+ BLOODSUCKER_PRE_MAKE_FAVORITE,
+ BLOODSUCKER_MAKE_FAVORITE,
+ BLOODSUCKER_MADE_VASSAL,
+ BLOODSUCKER_EXIT_TORPOR,
+ BLOODSUCKER_FINAL_DEATH,
+ ))
+ remove_clan_objective()
+ bloodsuckerdatum = null
+ return ..()
+
+///legacy code support
+/datum/bloodsucker_clan/proc/get_clan()
+ return name
+
+/datum/bloodsucker_clan/proc/give_clan_objective()
+ if(isnull(clan_objective))
+ return
+ clan_objective = new clan_objective()
+ clan_objective.objective_name = "Clan Objective"
+ clan_objective.owner = bloodsuckerdatum.owner
+ bloodsuckerdatum.objectives += clan_objective
+ bloodsuckerdatum.owner.announce_objectives()
+
+/datum/bloodsucker_clan/proc/remove_clan_objective()
+ bloodsuckerdatum.objectives -= clan_objective
+ QDEL_NULL(clan_objective)
+ bloodsuckerdatum.owner.announce_objectives()
+
+/**
+ * Called when a Bloodsucker exits Torpor
+ * args:
+ * source - the Bloodsucker exiting Torpor
+ */
+/datum/bloodsucker_clan/proc/on_exit_torpor(datum/antagonist/bloodsucker/source)
+ SIGNAL_HANDLER
+
+
+/**
+ * Called when a Bloodsucker enters Final Death
+ * args:
+ * source - the Bloodsucker exiting Torpor
+ */
+/datum/bloodsucker_clan/proc/on_final_death(datum/antagonist/bloodsucker/source)
+ SIGNAL_HANDLER
+ return FALSE
+
+/**
+ * Called during Bloodsucker's LifeTick
+ * args:
+ * bloodsuckerdatum - the antagonist datum of the Bloodsucker running this.
+ */
+/datum/bloodsucker_clan/proc/handle_clan_life(datum/antagonist/bloodsucker/source)
+ SIGNAL_HANDLER
+
+/**
+ * Called when a Bloodsucker successfully Vassalizes someone.
+ * args:
+ * bloodsuckerdatum - the antagonist datum of the Bloodsucker running this.
+ */
+/datum/bloodsucker_clan/proc/on_vassal_made(datum/antagonist/bloodsucker/source, mob/living/user, mob/living/target)
+ SIGNAL_HANDLER
+ user.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
+ target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
+ target.do_jitter_animation(15)
+ INVOKE_ASYNC(target, TYPE_PROC_REF(/mob, emote), "laugh")
+
+/**
+ * Called when a Bloodsucker successfully starts spending their Rank
+ * args:
+ * bloodsuckerdatum - the antagonist datum of the Bloodsucker running this.
+ * target - The Vassal (if any) we are upgrading.
+ * cost_rank - TRUE/FALSE on whether this will cost us a rank when we go through with it.
+ * blood_cost - A number saying how much it costs to rank up
+ * ask - If they want to automatically spend the rest of their ranks
+ */
+/datum/bloodsucker_clan/proc/on_spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost, ask)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(spend_rank), bloodsuckerdatum, cost_rank, blood_cost, ask)
+
+/datum/bloodsucker_clan/proc/spend_rank(datum/antagonist/bloodsucker/source, cost_rank = TRUE, blood_cost, ask)
+ // Purchase Power Prompt
+ var/list/options = list()
+ for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers)
+ if(initial(power.purchase_flags) & BLOODSUCKER_CAN_BUY && !(locate(power) in bloodsuckerdatum.powers))
+ options[initial(power.name)] = power
+
+ if(!LAZYLEN(options))
+ to_chat(bloodsuckerdatum.owner.current, span_notice("You grow more ancient by the night!"))
+ else
+ // Give them the UI to purchase a power.
+ var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to grow more ancient. Select a power to advance your Rank.", "Your Blood Thickens...", options)
+ // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
+ if(cost_rank && bloodsuckerdatum.bloodsucker_level_unspent <= 0)
+ return
+ // Did you choose a power?
+ if(!choice || !options[choice])
+ to_chat(bloodsuckerdatum.owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later."))
+ return
+ // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
+ if(locate(options[choice]) in bloodsuckerdatum.powers)
+ to_chat(bloodsuckerdatum.owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later."))
+ return
+ // Prevent Bloodsuckers from purchasing a power while outside of their Coffin.
+ if(!istype(bloodsuckerdatum.owner.current.loc, /obj/structure/closet/crate/coffin))
+ to_chat(bloodsuckerdatum.owner.current, span_warning("You must be in your Coffin to purchase Powers."))
+ return
+
+ // Good to go - Buy Power!
+ var/datum/action/cooldown/bloodsucker/purchased_power = options[choice]
+ bloodsuckerdatum.BuyPower(new purchased_power)
+ bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "learned [choice]!")
+ to_chat(bloodsuckerdatum.owner.current, span_notice("You have learned how to use [choice]!"))
+
+ finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost, ask)
+
+/datum/bloodsucker_clan/proc/finalize_spend_rank(datum/antagonist/bloodsucker/source, cost_rank = TRUE, blood_cost, ask)
+ bloodsuckerdatum.LevelUpPowers()
+ bloodsuckerdatum.bloodsucker_regen_rate += 0.05
+ bloodsuckerdatum.max_blood_volume += 100
+
+ // Misc. Stats Upgrades
+ if(ishuman(bloodsuckerdatum.owner.current))
+ var/mob/living/carbon/human/user = bloodsuckerdatum.owner.current
+ var/datum/species/user_species = user.dna.species
+ user_species.punchdamagelow += 0.5
+ // This affects the hitting power of Brawn.
+ user_species.punchdamagehigh += 0.5
+ user_species.punchstunthreshold += 0.5
+
+ // We're almost done - Spend your Rank now.
+ bloodsuckerdatum.bloodsucker_level++
+ if(cost_rank)
+ bloodsuckerdatum.bloodsucker_level_unspent--
+ if(blood_cost)
+ bloodsuckerdatum.AddBloodVolume(-blood_cost)
+
+ // Ranked up enough to get your true Reputation?
+ if(bloodsuckerdatum.bloodsucker_level == 4)
+ bloodsuckerdatum.SelectReputation(am_fledgling = FALSE, forced = TRUE)
+
+ to_chat(bloodsuckerdatum.owner.current, span_notice("You are now a rank [bloodsuckerdatum.bloodsucker_level] Bloodsucker. \
+ Your strength, health, feed rate, regen rate, and maximum blood capacity have all increased! \n\
+ * Your existing powers have all ranked up as well!"))
+ if(ask) //please no
+ bloodsuckerdatum.owner.current.playsound_local(null, 'sound/effects/pope_entry.ogg', 25, TRUE, pressure_affected = FALSE)
+ bloodsuckerdatum.update_hud()
+ if(bloodsuckerdatum.bloodsucker_level_unspent && cost_rank)
+ if(ask)
+ if(tgui_alert(bloodsuckerdatum.owner.current, "You have leftover ranks, do you want to spend them all?", "Time Management Team", list("Yes", "No")) == "No")
+ return
+ spend_rank(bloodsuckerdatum, cost_rank, blood_cost, FALSE)
+
+/**
+ * Called when we are trying to turn someone into a Favorite Vassal
+ * args:
+ * bloodsuckerdatum - the antagonist datum of the Bloodsucker performing this.
+ * vassaldatum - the antagonist datum of the Vassal being offered up.
+ */
+/datum/bloodsucker_clan/proc/on_offer_favorite(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(offer_favorite), bloodsuckerdatum, vassaldatum)
+
+/datum/bloodsucker_clan/proc/offer_favorite(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ if(vassaldatum.special_type)
+ to_chat(bloodsuckerdatum.owner.current, span_notice("This Vassal was already assigned a special position."))
+ return FALSE
+ if(!vassaldatum.owner.can_make_special(creator = bloodsuckerdatum.owner))
+ to_chat(bloodsuckerdatum.owner.current, span_notice("This Vassal is unable to gain a Special rank due to innate features."))
+ return FALSE
+
+ var/list/options = list()
+ var/list/radial_display = list()
+ for(var/datum/antagonist/vassal/vassaldatums as anything in subtypesof(/datum/antagonist/vassal))
+ if(bloodsuckerdatum.special_vassals[initial(vassaldatums.special_type)])
+ continue
+ options[initial(vassaldatums.name)] = vassaldatums
+
+ var/datum/radial_menu_choice/option = new
+ option.image = image(icon = initial(vassaldatums.hud_icon), icon_state = initial(vassaldatums.antag_hud_name))
+ option.info = "[initial(vassaldatums.name)] - [span_boldnotice(initial(vassaldatums.vassal_description))]"
+ radial_display[initial(vassaldatums.name)] = option
+
+ if(!options.len)
+ return
+
+ to_chat(bloodsuckerdatum.owner.current, span_notice("You can change who this Vassal is, who are they to you?"))
+ var/vassal_response = show_radial_menu(bloodsuckerdatum.owner.current, vassaldatum.owner.current, radial_display)
+ if(!vassal_response)
+ return
+ vassal_response = options[vassal_response]
+ if(QDELETED(src) || QDELETED(bloodsuckerdatum.owner.current) || QDELETED(vassaldatum.owner.current))
+ return FALSE
+ vassaldatum.make_special(vassal_response)
+ bloodsuckerdatum.bloodsucker_blood_volume -= 150
+
+/**
+ * Called when we are successfully turn a Vassal into a Favorite Vassal
+ * args:
+ * bloodsuckerdatum - antagonist datum of the Bloodsucker who turned them into a Vassal.
+ * vassaldatum - the antagonist datum of the Vassal being offered up.
+ */
+/datum/bloodsucker_clan/proc/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ SIGNAL_HANDLER
+ vassaldatum.BuyPower(new /datum/action/cooldown/bloodsucker/targeted/brawn)
diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_flavortext.dm b/code/modules/antagonists/bloodsuckers/clans/clan_flavortext.dm
new file mode 100644
index 000000000000..4b53ba1bb6c9
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/clans/clan_flavortext.dm
@@ -0,0 +1,48 @@
+/datum/bloodsucker_clan/ventrue
+ name = CLAN_VENTRUE
+ description = "The Ventrue Clan is extremely snobby with their meals, and refuse to drink blood from people without a mind. \n\
+ There is additionally no way to rank themselves up, instead will have to rank their Favorite vassal through a Persuasion Rack. \n\
+ The Favorite Vassal will slowly turn into a Bloodsucker this way, until they finally lose their last bits of Humanity."
+ joinable_clan = FALSE
+// clan_objective = /datum/objective/bloodsucker/embrace
+ join_icon_state = "ventrue"
+ join_description = "Lose the ability to drink from mindless mobs, can't level up or gain new powers, \
+ instead you raise a vassal into a Bloodsucker."
+// rank_up_type = BLOODSUCKER_RANK_UP_VASSAL
+ blood_drink_type = BLOODSUCKER_DRINK_SNOBBY
+
+/datum/bloodsucker_clan/tremere
+ name = CLAN_TREMERE
+ description = "The Tremere Clan is extremely weak to True Faith, and will burn when entering areas considered such, like the Chapel. \n\
+ Additionally, a whole new moveset is learned, built on Blood magic rather than Blood abilities, which are upgraded overtime. \n\
+ More ranks can be gained by Vassalizing crewmembers. \n\
+ The Favorite Vassal gains the Batform spell, being able to morph themselves at will."
+ joinable_clan = FALSE
+// clan_objective = /datum/objective/bloodsucker/tremere_power
+ join_icon_state = "tremere"
+ join_description = "You will burn if you enter the Chapel, lose all default powers, \
+ but gain Blood Magic instead, powers you level up overtime."
+
+/datum/bloodsucker_clan/nosferatu
+ name = CLAN_NOSFERATU
+ description = "The Nosferatu Clan is unable to blend in with the crew, with no abilities such as Masquerade and Veil. \n\
+ Additionally, has a permanent bad back and looks like a Bloodsucker upon a simple examine, and is entirely unidentifiable, \n\
+ they can fit in the vents regardless of their form and equipment. \n\
+ The Favorite Vassal is permanetly disfigured, and can also ventcrawl, but only while entirely nude."
+ joinable_clan = FALSE
+// clan_objective = /datum/objective/bloodsucker/kindred
+ join_icon_state = "nosferatu"
+ join_description = "You are permanetly disfigured, look like a Bloodsucker to all who examine you, \
+ lose your Masquerade ability, but gain the ability to Ventcrawl even while clothed."
+ blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY
+
+/datum/bloodsucker_clan/malkavian
+ name = CLAN_MALKAVIAN
+ description = "Little is documented about Malkavians. Complete insanity is the most common theme. \n\
+ The Favorite Vassal will suffer the same fate as the Master."
+ join_icon_state = "malkavian"
+ join_description = "Completely insane. You gain constant hallucinations, become a prophet with unintelligable rambling, \
+ and become the enforcer of the Masquerade code."
+ joinable_clan = FALSE
+ frenzy_stun_immune = TRUE
+ blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY
diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_gangrel.dm b/code/modules/antagonists/bloodsuckers/clans/clan_gangrel.dm
new file mode 100644
index 000000000000..7e385c86554a
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/clans/clan_gangrel.dm
@@ -0,0 +1,23 @@
+/datum/bloodsucker_clan/gangrel
+ name = CLAN_GANGREL
+ description = "Closer to Animals than Bloodsuckers, known as Werewolves waiting to happen, \n\
+ these are the most fearful of True Faith, being the most lethal thing they would ever see the night of. \n\
+ Full Moons do not seem to have an effect, despite common-told stories. \n\
+ The Favorite Vassal turns into a Werewolf whenever their Master does."
+ clan_objective = /datum/objective/bloodsucker/frenzy
+ join_icon_state = "gangrel"
+ join_description = "Purely animalistic, full of transformation abilities, and special frenzy, an active threat at all times."
+ frenzy_stun_immune = TRUE
+ blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY
+
+/datum/bloodsucker_clan/gangrel/New(datum/antagonist/bloodsucker/owner_datum)
+ . = ..()
+ bloodsuckerdatum.AddHumanityLost(16.8)
+ bloodsuckerdatum.BuyPower(new /datum/action/cooldown/bloodsucker/gangrel/transform)
+ bloodsuckerdatum.owner.current.faction |= "bloodhungry" //i love animals i love animals
+ for(var/datum/action/cooldown/bloodsucker/masquerade/masquerade_power in bloodsuckerdatum.powers)
+ bloodsuckerdatum.RemovePower(masquerade_power)
+
+/datum/bloodsucker_clan/malkavian/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ var/datum/action/cooldown/spell/shapeshift/bat/batform = new(vassaldatum.owner || vassaldatum.owner.current)
+ batform.Grant(vassaldatum.owner.current)
diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm b/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm
new file mode 100644
index 000000000000..e5d4b0787cc5
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm
@@ -0,0 +1,41 @@
+/datum/bloodsucker_clan/lasombra
+ name = CLAN_LASOMBRA
+ description = "This Clan seems to adore living in the Shadows, worshipping it's secrets. \n\
+ They take their research and vanity seriously, they are always very proud of themselves after even minor achievements. \n\
+ They appear to be in search of a station with a veil weakness to be able to channel their shadow's abyssal powers. \n\
+ Thanks to this, they have also evolved a dark liquid in their veins, which makes them able to manipulate shadows. \n\
+ Their Favorite Vassal appears to have been imbued with abyssal essence and is able to blend in with the shadows."
+ clan_objective = /datum/objective/bloodsucker/hierarchy
+ join_icon_state = "lasombra"
+ join_description = "Heal more on the dark, transform abilties into upgraded ones, become one with the darkness."
+
+/datum/bloodsucker_clan/lasombra/New(datum/antagonist/bloodsucker/owner_datum)
+ . = ..()
+ bloodsuckerdatum.BuyPower(new /datum/action/cooldown/bloodsucker/targeted/lasombra)
+ if(ishuman(bloodsuckerdatum.owner.current))
+ var/mob/living/carbon/human/human_user = bloodsuckerdatum.owner.current
+ human_user.eye_color = BLOODCULT_EYE
+ human_user.updateappearance()
+ ADD_TRAIT(bloodsuckerdatum.owner.current, CULT_EYES, BLOODSUCKER_TRAIT)
+ bloodsuckerdatum.owner.current.faction |= "bloodhungry"
+ bloodsuckerdatum.owner.current.update_body()
+ var/obj/item/organ/heart/nightmare/nightmarish_heart = new
+ nightmarish_heart.Insert(bloodsuckerdatum.owner.current)
+ nightmarish_heart.Stop()
+ for(var/obj/item/light_eater/blade in bloodsuckerdatum.owner.current.held_items)
+ QDEL_NULL(blade)
+ GLOB.reality_smash_track.AddMind(bloodsuckerdatum.owner)
+ to_chat(bloodsuckerdatum.owner.current, span_notice("You have also learned how to channel the abyss's power into an iron knight's armor that can be build in the structure tab and activated as a trap for your lair."))
+ bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/possessedarmor)
+ bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/restingplace)
+
+/datum/bloodsucker_clan/lasombra/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ if(ishuman(vassaldatum.owner.current))
+ var/mob/living/carbon/human/vassal = vassaldatum.owner.current
+ vassal.see_in_dark = 8
+ vassal.eye_color = BLOODCULT_EYE
+ vassal.updateappearance()
+ var/list/powers = list(/datum/action/cooldown/spell/pointed/lesser_glare, /datum/action/cooldown/spell/jaunt/shadow_walk)
+ for(var/datum/action/cooldown/spell/power in powers)
+ power = new(vassaldatum.owner.current)
+ power.Grant(vassaldatum.owner.current)
diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_toreador.dm b/code/modules/antagonists/bloodsuckers/clans/clan_toreador.dm
new file mode 100644
index 000000000000..fc388629c123
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/clans/clan_toreador.dm
@@ -0,0 +1,43 @@
+/datum/bloodsucker_clan/toreador
+ name = CLAN_TOREADOR
+ description = "The most charming Clan of them all, allowing them to very easily disguise among the crew. \n\
+ More in touch with their morals, they suffer and benefit more strongly from humanity cost or gain of their actions. \n\
+ Known as 'The most humane kind of vampire', they have an obsession with perfectionism and beauty \n\
+ The Favorite Vassal gains the Mesmerize ability."
+ clan_objective = /datum/objective/bloodsucker/leader
+ join_icon_state = "toreador"
+ join_description = "Powerful Mesmerize, build statues to boost your status and mood, gather a large audience for your performance."
+ blood_drink_type = BLOODSUCKER_DRINK_SNOBBY
+ control_type = BLOODSUCKER_CONTROL_METAL
+
+/datum/bloodsucker_clan/toreador/New(datum/antagonist/bloodsucker/owner_datum)
+ . = ..()
+ RegisterSignal(SSdcs, COMSIG_BLOODSUCKER_BROKE_MASQUERADE, PROC_REF(on_bloodsucker_broke_masquerade))
+ if(bloodsuckerdatum.owner.current && ishuman(bloodsuckerdatum.owner.current) && !bloodsuckerdatum.owner.current.GetComponent(/datum/component/mood))
+ bloodsuckerdatum.owner.current.AddComponent(/datum/component/mood) //You are not a emotionless beast! //trolled!
+// user.mind.teach_crafting_recipe(/datum/crafting_recipe/bloodybrush)
+ bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/moldingstone)
+ bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/chisel)
+
+ for(var/datum/action/cooldown/bloodsucker/masquerade/masquarade_spell in bloodsuckerdatum.powers)
+ if(!istype(masquarade_spell))
+ continue
+ masquarade_spell.bloodcost = 0
+ masquarade_spell.constant_bloodcost = 0
+
+/datum/bloodsucker_clan/toreador/Destroy(force)
+ UnregisterSignal(SSdcs, COMSIG_BLOODSUCKER_BROKE_MASQUERADE)
+ return ..()
+
+/datum/bloodsucker_clan/toreador/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ vassaldatum.BuyPower(new /datum/action/cooldown/bloodsucker/targeted/mesmerize)
+
+/datum/bloodsucker_clan/toreador/proc/on_bloodsucker_broke_masquerade(datum/antagonist/bloodsucker/masquerade_breaker)
+ SIGNAL_HANDLER
+ to_chat(bloodsuckerdatum.owner.current, span_userdanger("[masquerade_breaker.owner.current] has broken the Masquerade! Ensure [masquerade_breaker.owner.current.p_they()] [masquerade_breaker.owner.current.p_are()] eliminated at all costs!"))
+ var/datum/objective/assassinate/masquerade_objective = new()
+ masquerade_objective.target = masquerade_breaker.owner.current
+ masquerade_objective.objective_name = "Clan Objective"
+ masquerade_objective.explanation_text = "Ensure [masquerade_breaker.owner.current], who has broken the Masquerade, succumbs to Final Death."
+ bloodsuckerdatum.objectives += masquerade_objective
+ bloodsuckerdatum.owner.announce_objectives()
diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_tzimisce.dm b/code/modules/antagonists/bloodsuckers/clans/clan_tzimisce.dm
new file mode 100644
index 000000000000..d938dd386fc1
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/clans/clan_tzimisce.dm
@@ -0,0 +1,33 @@
+/datum/bloodsucker_clan/tzimisce
+ name = CLAN_TZIMISCE
+ description = "The page is covered in blood..."
+ join_icon_state = "tzimisce"
+// clan_objective = TBD
+ joinable_clan = FALSE //important
+ blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY
+ control_type = BLOODSUCKER_CONTROL_FLESH
+
+/datum/bloodsucker_clan/tzimisce/New(datum/antagonist/bloodsucker/owner_datum)
+ . = ..()
+ bloodsuckerdatum.AddHumanityLost(5.6)
+ bloodsuckerdatum.BuyPower(new /datum/action/cooldown/bloodsucker/targeted/dice)
+ bloodsuckerdatum.owner.current.faction |= "bloodhungry" //flesh monster's clan
+ var/list/powerstoremove = list(/datum/action/cooldown/bloodsucker/veil, /datum/action/cooldown/bloodsucker/masquerade)
+ for(var/datum/action/cooldown/bloodsucker/banned_power in bloodsuckerdatum.powers)
+ if(is_type_in_list(banned_power, powerstoremove))
+ bloodsuckerdatum.RemovePower(banned_power)
+
+/datum/bloodsucker_clan/tzimisce/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ if(!ishuman(vassaldatum.owner.current))
+ return
+ var/mob/living/carbon/human/vassal = vassaldatum.owner.current
+ if(!INVOKE_ASYNC(src, PROC_REF(slash_vassal), bloodsuckerdatum.owner.current, 1 SECONDS, vassal))
+ return
+ playsound(vassal.loc, 'sound/weapons/slash.ogg', 50, TRUE, -1)
+ if(!INVOKE_ASYNC(src, PROC_REF(slash_vassal), bloodsuckerdatum.owner.current, 1 SECONDS, vassal))
+ return
+ playsound(vassal.loc, 'sound/effects/splat.ogg', 50, TRUE)
+ INVOKE_ASYNC(vassal, TYPE_PROC_REF(/mob/, set_species), /datum/species/szlachta)
+
+/datum/bloodsucker_clan/tzimisce/proc/slash_vassal(mob/living/bloodsucker, time, mob/living/vassal)
+ do_after(bloodsucker, time, vassal, FALSE, TRUE, null, FALSE) //necessary becaues of how signal handler works
diff --git a/code/modules/antagonists/bloodsuckers/powers/_powers.dm b/code/modules/antagonists/bloodsuckers/powers/_powers.dm
index 39c7b8ad0e90..8a254766731b 100644
--- a/code/modules/antagonists/bloodsuckers/powers/_powers.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/_powers.dm
@@ -1,15 +1,20 @@
-/datum/action/bloodsucker
+/datum/action/cooldown/bloodsucker
name = "Vampiric Gift"
desc = "A vampiric gift."
- //This is the FILE for the background icon
- button_icon = 'icons/mob/actions/actions_bloodsucker.dmi'
- //This is the ICON_STATE for the background icon
+ background_icon = 'icons/mob/actions/actions_bloodsucker.dmi'
background_icon_state = "vamp_power_off"
- var/background_icon_state_on = "vamp_power_on"
- var/background_icon_state_off = "vamp_power_off"
- icon_icon = 'icons/mob/actions/actions_bloodsucker.dmi'
+ button_icon = 'icons/mob/actions/actions_bloodsucker.dmi'
button_icon_state = "power_feed"
buttontooltipstyle = "cult"
+ transparent_when_unavailable = TRUE
+
+ /// Cooldown you'll have to wait between each use, decreases depending on level.
+ cooldown_time = 2 SECONDS
+
+ ///Background icon when the Power is active.
+ active_background_icon_state = "vamp_power_on"
+ ///Background icon when the Power is NOT active.
+ base_background_icon_state = "vamp_power_off"
/// The text that appears when using the help verb, meant to explain how the Power changes when ranking up.
var/power_explanation = ""
@@ -24,15 +29,9 @@
/// Who can purchase the Power
var/purchase_flags = NONE // BLOODSUCKER_CAN_BUY|LASOMBRA_CAN_BUY|GANGREL_CAN_BUY|VASSAL_CAN_BUY|HUNTER_CAN_BUY
- // COOLDOWNS //
- ///Timer between Power uses.
- COOLDOWN_DECLARE(bloodsucker_power_cooldown)
-
// VARS //
/// If the Power is currently active.
var/active = FALSE
- /// Cooldown you'll have to wait between each use, decreases depending on level.
- var/cooldown = 2 SECONDS
///Can increase to yield new abilities - Each Power ranks up each Rank
var/level_current = 0
///The cost to ACTIVATE this Power
@@ -43,12 +42,11 @@
var/additional_text = ""
// Modify description to add cost.
-/datum/action/bloodsucker/New(Target)
+/datum/action/cooldown/bloodsucker/New(Target)
. = ..()
UpdateDesc()
- START_PROCESSING(SSfastprocess, src)
-/datum/action/bloodsucker/proc/UpdateDesc()
+/datum/action/cooldown/bloodsucker/proc/UpdateDesc()
desc = initial(desc)
if(length(additional_text) > 0)
desc += "
ASCENDED: [additional_text]"
@@ -60,65 +58,64 @@
desc += "
SINGLE USE: [name] can only be used once per night."
if(level_current > 0)
desc += "
LEVEL: [name] is currently level [level_current]."
- if(cooldown > 0)
- desc += "
COOLDOWN: [name] has a cooldown of [cooldown / 10] seconds."
+ if(cooldown_time > 0)
+ desc += "
COOLDOWN: [name] has a cooldown of [cooldown_time / 10] seconds."
-/datum/action/bloodsucker/Destroy()
+/datum/action/cooldown/bloodsucker/Destroy()
bloodsuckerdatum_power = null
- STOP_PROCESSING(SSfastprocess, src)
return ..()
-/datum/action/bloodsucker/process()
- cooldown_overlay?.tick()
-
-
-/datum/action/bloodsucker/IsAvailable()
- return TRUE
+/datum/action/cooldown/bloodsucker/IsAvailable(feedback = FALSE)
+ return next_use_time <= world.time
-/datum/action/bloodsucker/Grant(mob/user)
+/datum/action/cooldown/bloodsucker/Grant(mob/user)
. = ..()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(owner)
if(bloodsuckerdatum)
bloodsuckerdatum_power = bloodsuckerdatum
-/**
- * # NOTES
- *
- * click.dm <--- Where we can take over mouse clicks
- * spells.dm /add_ranged_ability() <--- How we take over the mouse click to use a power on a target.
- */
-
//This is when we CLICK on the ability Icon, not USING.
-/datum/action/bloodsucker/Trigger(trigger_flags)
- if(active && CheckCanDeactivate()) // Active? DEACTIVATE AND END!
+/datum/action/cooldown/bloodsucker/Trigger(trigger_flags)
+ if(active && can_deactivate()) // Active? DEACTIVATE AND END!
DeactivatePower()
return FALSE
- if(!CheckCanPayCost() || !CheckCanUse(owner))
+ if(!can_pay_cost() || !CanUse(owner))
return FALSE
- PayCost()
+ pay_cost()
ActivatePower()
- if(power_flags & BP_AM_SINGLEUSE)
- RemoveAfterUse()
+ if(!(power_flags & BP_AM_TOGGLE) || !active)
+ StartCooldown()
return TRUE
-/datum/action/bloodsucker/proc/CheckCanPayCost()
+/datum/action/cooldown/bloodsucker/proc/can_pay_cost()
if(!owner || !owner.mind)
return FALSE
// Cooldown?
- if(!COOLDOWN_FINISHED(src, bloodsucker_power_cooldown))
- to_chat(owner, span_warning("[src] on cooldown!"))
- return FALSE
+ if(!COOLDOWN_FINISHED(src, next_use_time))
+ owner.balloon_alert(owner, "power unavailable!")
+ to_chat(owner, "[src] on cooldown!")
+ return FALSE
+ if(!bloodsuckerdatum_power)
+ var/mob/living/living_owner = owner
+ if(living_owner.blood_volume < bloodcost)
+ to_chat(owner, span_warning("You need at least [bloodcost] blood to activate [name]"))
+ return FALSE
+ return TRUE
+
// Have enough blood? Bloodsuckers in a Frenzy don't need to pay them
- var/mob/living/user = owner
- if(bloodsuckerdatum_power?.frenzied)
+ if(bloodsuckerdatum_power.frenzied)
return TRUE
- if(user.blood_volume < bloodcost)
+ if(bloodsuckerdatum_power.bloodsucker_blood_volume < bloodcost)
to_chat(owner, span_warning("You need at least [bloodcost] blood to activate [name]"))
return FALSE
return TRUE
+///Called when the Power is upgraded.
+/datum/action/cooldown/bloodsucker/proc/upgrade_power()
+ level_current++
+
///Checks if the Power is available to use.
-/datum/action/bloodsucker/proc/CheckCanUse(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/proc/CanUse(mob/living/carbon/user)
if(!owner)
return FALSE
if(!isliving(user))
@@ -132,7 +129,7 @@
to_chat(user, span_warning("You cannot use powers while in a Frenzy!"))
return FALSE
// Stake?
- if((check_flags & BP_CANT_USE_WHILE_STAKED) && user.AmStaked())
+ if((check_flags & BP_CANT_USE_WHILE_STAKED) && user.am_staked())
to_chat(user, span_warning("You have a stake in your chest! Your powers are useless."))
return FALSE
// Conscious? -- We use our own (AB_CHECK_CONSCIOUS) here so we can control it more, like the error message.
@@ -144,86 +141,84 @@
to_chat(user, span_warning("Not while you're incapacitated!"))
return FALSE
// Constant Cost (out of blood)
- if(constant_bloodcost > 0 && user.blood_volume <= 0)
+ if(constant_bloodcost && bloodsuckerdatum_power?.bloodsucker_blood_volume <= 0)
to_chat(user, span_warning("You don't have the blood to upkeep [src]."))
return FALSE
return TRUE
/// NOTE: With this formula, you'll hit half cooldown at level 8 for that power.
-/datum/action/bloodsucker/proc/StartCooldown()
- // Alpha Out
- button.color = rgb(128,0,0,128)
- button.alpha = 100
+/datum/action/cooldown/bloodsucker/StartCooldown()
// Calculate Cooldown (by power's level)
- var/this_cooldown
if(power_flags & BP_AM_STATIC_COOLDOWN)
- this_cooldown = cooldown
+ cooldown_time = initial(cooldown_time)
else
- this_cooldown = max(cooldown / 2, cooldown - (cooldown / 16 * (level_current-1)))
-
- // Wait for cooldown
- COOLDOWN_START(src, bloodsucker_power_cooldown, this_cooldown)
- cooldown_overlay = start_cooldown(button,world.time + this_cooldown)
- addtimer(CALLBACK(src, PROC_REF(alpha_in)), this_cooldown)
+ cooldown_time = max(initial(cooldown_time) / 2, initial(cooldown_time) - (initial(cooldown_time) / 16 * (level_current-1)))
-/datum/action/bloodsucker/proc/alpha_in()
- if(cooldown_overlay)
- QDEL_NULL(cooldown_overlay)
- button.color = rgb(255,255,255,255)
- button.alpha = 255
+ return ..()
-/datum/action/bloodsucker/proc/CheckCanDeactivate()
+/datum/action/cooldown/bloodsucker/proc/can_deactivate()
return TRUE
-/datum/action/bloodsucker/UpdateButtonIcon(force = FALSE)
- background_icon_state = active ? background_icon_state_on : background_icon_state_off
- . = ..()
+/datum/action/cooldown/bloodsucker/is_action_active()
+ return active
-/datum/action/bloodsucker/proc/PayCost()
+/datum/action/cooldown/bloodsucker/proc/pay_cost()
+ // Non-bloodsuckers will pay in other ways.
+ if(!bloodsuckerdatum_power)
+ var/mob/living/carbon/living_owner = owner
+ if(!LAZYFIND(living_owner.dna.species.species_traits, NOBLOOD))
+ living_owner.blood_volume -= bloodcost
+ return
// Bloodsuckers in a Frenzy don't have enough Blood to pay it, so just don't.
- if(bloodsuckerdatum_power?.frenzied)
+ if(bloodsuckerdatum_power.frenzied)
return
- var/mob/living/carbon/human/user = owner
- user.blood_volume -= bloodcost
+ bloodsuckerdatum_power.bloodsucker_blood_volume -= bloodcost
bloodsuckerdatum_power?.update_hud()
-/datum/action/bloodsucker/proc/ActivatePower()
+/datum/action/cooldown/bloodsucker/proc/ActivatePower()
active = TRUE
if(power_flags & BP_AM_TOGGLE)
- RegisterSignal(owner, COMSIG_LIVING_BIOLOGICAL_LIFE, PROC_REF(UsePower))
+ START_PROCESSING(SSprocessing, src)
+
owner.log_message("used [src][bloodcost != 0 ? " at the cost of [bloodcost]" : ""].", LOG_ATTACK, color="red")
- UpdateButtonIcon()
+ build_all_button_icons()
-/datum/action/bloodsucker/proc/DeactivatePower()
+/datum/action/cooldown/bloodsucker/proc/DeactivatePower()
+ if(!active) //Already inactive? Return
+ return
if(power_flags & BP_AM_TOGGLE)
- UnregisterSignal(owner, COMSIG_LIVING_BIOLOGICAL_LIFE)
+ STOP_PROCESSING(SSprocessing, src)
if(power_flags & BP_AM_SINGLEUSE)
- RemoveAfterUse()
+ remove_after_use()
return
active = FALSE
- UpdateButtonIcon()
StartCooldown()
+ build_all_button_icons()
///Used by powers that are continuously active (That have BP_AM_TOGGLE flag)
-/datum/action/bloodsucker/proc/UsePower(mob/living/user)
- if(!active) // Power isn't active? Then stop here, so we dont keep looping UsePower for a non existent Power.
- return FALSE
- if(!ContinueActive(user)) // We can't afford the Power? Deactivate it.
+/datum/action/cooldown/bloodsucker/process(seconds_per_tick)
+ SHOULD_CALL_PARENT(TRUE) //Need this to call parent so the cooldown system works
+ . = ..()
+ if(!ContinueActive(owner)) // We can't afford the Power? Deactivate it.
DeactivatePower()
return FALSE
// We can keep this up (For now), so Pay Cost!
- if(!(power_flags & BP_AM_COSTLESS_UNCONSCIOUS) && user.stat != CONSCIOUS)
- bloodsuckerdatum_power?.AddBloodVolume(-constant_bloodcost)
+ if(!(power_flags & BP_AM_COSTLESS_UNCONSCIOUS) && owner.stat != CONSCIOUS)
+ if(bloodsuckerdatum_power)
+ bloodsuckerdatum_power.AddBloodVolume(-constant_bloodcost)
+ else
+ var/mob/living/living_owner = owner
+ living_owner.blood_volume -= constant_bloodcost
return TRUE
/// Checks to make sure this power can stay active
-/datum/action/bloodsucker/proc/ContinueActive(mob/living/user, mob/living/target)
+/datum/action/cooldown/bloodsucker/proc/ContinueActive(mob/living/user, mob/living/target)
if(!user)
return FALSE
- if(!constant_bloodcost > 0 || user.blood_volume > 0)
+ if(!constant_bloodcost > 0 || bloodsuckerdatum_power.bloodsucker_blood_volume)
return TRUE
/// Used to unlearn Single-Use Powers
-/datum/action/bloodsucker/proc/RemoveAfterUse()
+/datum/action/cooldown/bloodsucker/proc/remove_after_use()
bloodsuckerdatum_power?.powers -= src
Remove(owner)
diff --git a/code/modules/antagonists/bloodsuckers/powers/cloak.dm b/code/modules/antagonists/bloodsuckers/powers/cloak.dm
index 512c7b04b241..7d3191d6a396 100644
--- a/code/modules/antagonists/bloodsuckers/powers/cloak.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/cloak.dm
@@ -1,8 +1,8 @@
-/datum/action/bloodsucker/cloak
+/datum/action/cooldown/bloodsucker/cloak
name = "Cloak of Darkness"
desc = "Blend into the shadows and become invisible to the untrained and Artificial eye."
button_icon_state = "power_cloak"
- power_explanation = "Cloak of Darkness:\n\
+ power_explanation = "Cloak of Darkness<:\n\
Activate this Power in the shadows and you will slowly turn nearly invisible.\n\
While using Cloak of Darkness, attempting to run will crush you.\n\
Additionally, while Cloak is active, you are completely invisible to the AI.\n\
@@ -12,21 +12,21 @@
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 5
constant_bloodcost = 0.2
- cooldown = 5 SECONDS
+ cooldown_time = 5 SECONDS
var/was_running
var/runbound = TRUE
/// Must have nobody around to see the cloak
-/datum/action/bloodsucker/cloak/CheckCanUse(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/cloak/CanUse(mob/living/carbon/user)
. = ..()
if(!.)
return FALSE
for(var/mob/living/watchers in viewers(9, owner) - owner)
- to_chat(owner, span_warning("You can only vanish unseen."))
+ owner.balloon_alert(owner, "you can only vanish unseen.")
return FALSE
return TRUE
-/datum/action/bloodsucker/cloak/ActivatePower()
+/datum/action/cooldown/bloodsucker/cloak/ActivatePower()
. = ..()
var/mob/living/user = owner
was_running = (user.m_intent == MOVE_INTENT_RUN)
@@ -35,22 +35,25 @@
user.toggle_move_intent()
user.digitalinvis = 1
user.digitalcamo = 1
- to_chat(user, span_notice("You put your Cloak of Darkness on."))
+ user.balloon_alert(user, "cloak turned on.")
-/datum/action/bloodsucker/cloak/UsePower(mob/living/user)
+/datum/action/cooldown/bloodsucker/cloak/process()
// Checks that we can keep using this.
. = ..()
if(!.)
return
+ if(!active)
+ return
+ var/mob/living/user = owner
animate(user, alpha = max(25, owner.alpha - min(75, 10 + 5 * level_current)), time = 1.5 SECONDS)
// Prevents running while on Cloak of Darkness
if(runbound)
if(user.m_intent != MOVE_INTENT_WALK)
- to_chat(owner, span_warning("You attempt to run, crushing yourself."))
+ owner.balloon_alert(owner, "you attempt to run, crushing yourself.")
user.toggle_move_intent()
user.adjustBruteLoss(rand(5,15))
-/datum/action/bloodsucker/cloak/ContinueActive(mob/living/user, mob/living/target)
+/datum/action/cooldown/bloodsucker/cloak/ContinueActive(mob/living/user, mob/living/target)
. = ..()
if(!.)
return FALSE
@@ -60,7 +63,7 @@
return FALSE
return TRUE
-/datum/action/bloodsucker/cloak/DeactivatePower()
+/datum/action/cooldown/bloodsucker/cloak/DeactivatePower()
. = ..()
var/mob/living/user = owner
animate(user, alpha = 255, time = 1 SECONDS)
@@ -69,15 +72,15 @@
if(runbound)
if(was_running && user.m_intent == MOVE_INTENT_WALK)
user.toggle_move_intent()
- to_chat(user, span_notice("You take your Cloak of Darkness off."))
+ user.balloon_alert(user, "cloak turned off.")
-/datum/action/bloodsucker/cloak/shadow
+/datum/action/cooldown/bloodsucker/cloak/shadow
name = "Cloak of Shadows"
desc = "Empowered to the abyss, fortitude will now grant you a shadow armor, making your grip harder to escape and reduce projectile damage while in darkness."
+ background_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ active_background_icon_state = "lasombra_power_on"
+ base_background_icon_state = "lasombra_power_off"
button_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
- background_icon_state_on = "lasombra_power_on"
- background_icon_state_off = "lasombra_power_off"
- icon_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
button_icon_state = "power_state"
additional_text = "Additionally allows you to run during cloak and gain a physical cloak while in darkness."
purchase_flags = LASOMBRA_CAN_BUY
@@ -91,7 +94,7 @@
mob_overlay_icon = 'icons/obj/vamp_obj.dmi'
icon_state = "cloak"
item_state = "cloak"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 10, "acid" = 100) //good if you haven nothing
+ armor = list(MELEE = 0, BULLET = 0, LASER = 10, ENERGY = 10, BOMB = 0, BIO = 0, RAD = 0, FIRE = 10, ACID = 100) //good if you haven nothing
/obj/item/clothing/neck/yogs/sith_cloak/cloak/Initialize()
. = ..()
@@ -101,20 +104,20 @@
/obj/item/clothing/neck/yogs/sith_cloak/cloak/process()
var/turf/T = get_turf(src)
var/light_amount = T.get_lumcount()
- if(light_amount > 0.2)
+ if(light_amount > LIGHTING_TILE_IS_DARK)
qdel(src)
STOP_PROCESSING(SSobj, src)
src.visible_message(span_warning("The cape desintegrates as the light contacts it's surface!"))
-/datum/action/bloodsucker/cloak/shadow/ActivatePower()
+/datum/action/cooldown/bloodsucker/cloak/shadow/ActivatePower()
. = ..()
var/turf/T = get_turf(owner)
var/light_amount = T.get_lumcount()
- if(light_amount <= 0.2)
+ if(light_amount <= LIGHTING_TILE_IS_DARK)
if(!owner.get_item_by_slot(SLOT_NECK))
owner.equip_to_slot_or_del( new /obj/item/clothing/neck/yogs/sith_cloak/cloak(null), SLOT_NECK)
-/datum/action/bloodsucker/cloak/shadow/DeactivatePower()
+/datum/action/cooldown/bloodsucker/cloak/shadow/DeactivatePower()
. = ..()
var/obj/item/I = owner.get_item_by_slot(SLOT_NECK)
if(istype(I, /obj/item/clothing/neck/yogs/sith_cloak/cloak))
diff --git a/code/modules/antagonists/bloodsuckers/powers/distress.dm b/code/modules/antagonists/bloodsuckers/powers/distress.dm
index 6a9d3e081434..230dae54d067 100644
--- a/code/modules/antagonists/bloodsuckers/powers/distress.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/distress.dm
@@ -1,16 +1,16 @@
-/datum/action/bloodsucker/distress
+/datum/action/cooldown/bloodsucker/distress
name = "Distress"
desc = "Injure yourself, allowing you to make a desperate call for help to your Master."
button_icon_state = "power_distress"
- power_explanation = "Distress:\n\
+ power_explanation = "Distress:\n\
Use this Power from anywhere and your Master Bloodsucker will instnatly be alerted of your location."
power_flags = NONE
check_flags = NONE
purchase_flags = NONE
bloodcost = 10
- cooldown = 10 SECONDS
+ cooldown_time = 10 SECONDS
-/datum/action/bloodsucker/distress/ActivatePower()
+/datum/action/cooldown/bloodsucker/distress/ActivatePower()
. = ..()
var/turf/open/floor/target_area = get_area(owner)
var/datum/antagonist/vassal/vassaldatum = owner.mind.has_antag_datum(/datum/antagonist/vassal)
diff --git a/code/modules/antagonists/bloodsuckers/powers/feed.dm b/code/modules/antagonists/bloodsuckers/powers/feed.dm
index 123116cad0bc..9b8a54274c16 100644
--- a/code/modules/antagonists/bloodsuckers/powers/feed.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/feed.dm
@@ -1,240 +1,139 @@
-/datum/action/bloodsucker/feed
+#define FEED_NOTICE_RANGE 2
+#define FEED_DEFAULT_TIMER (10 SECONDS)
+
+/datum/action/cooldown/bloodsucker/feed
name = "Feed"
- desc = "Draw the heartsblood of living victims in your grasp. You will break the Masquerade if seen feeding."
+ desc = "Feed blood off of a living creature."
button_icon_state = "power_feed"
- power_explanation = "Feed:\n\
+ power_explanation = "Feed:\n\
+ Feed can't be used until you reach your first Bloodsucker level.\n\
Activate Feed while next to someone and you will begin to feed blood off of them.\n\
- If passively grabbed, you will feed faster than default.\n\
- If aggressively grabbed, along with drinking even faster, your victim will additionally be put to sleep.\n\
- You cannot talk while feeding, as your mouth is full of blood.\n\
- Feeding off of someone until they die will cause you to lose Humanity.\n\
- If you are seen feeding off of someone (2 tiles) while your target is grabbed, you will break the Masquerade.\n\
- Higher levels will increase the feeding's speed."
+ The time needed before you start feeding speeds up the higher level you are.\n\
+ Feeding off of someone while you have them aggressively grabbed will put them to sleep.\n\
+ While feeding, you can't speak, as your mouth is covered.\n\
+ Feeding while nearby (2 tiles away from) a mortal who is unaware of Bloodsuckers' existence, will cause a Masquerade Infraction\n\
+ If you get too many Masquerade Infractions, you will break the Masquerade.\n\
+ If you are in desperate need of blood, mice can be fed off of, at a cost."
power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_STAKED|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
- purchase_flags = BLOODSUCKER_CAN_BUY
+ purchase_flags = BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER
bloodcost = 0
- cooldown = 3 SECONDS
-
- ///Amount of times we were seen Feeding. If seen 3 times, we broke the Masquerade.
- var/feeds_noticed = 0
- ///Distance before silent feeding is noticed.
- var/notice_range = 2
- ///Check if we were noticed Feeding.
- var/was_noticed = FALSE
- ///So we can validate more than just the guy we're grappling.
- var/mob/living/feed_target
- ///If you started grappled, then ending it will end your Feed.
- var/target_grappled = FALSE
- ///Am I Silent?
- var/amSilent = FALSE
- ///How much Blood did I drink? This is used for logs
- var/amount_taken = 0
- ///The initial wait before you start drinking blood.
- var/feed_time
- ///Quantity to take per tick, based on Silent/frenzied or not.
- var/blood_take_mult
- /// CHECKS - To prevent spam.
- var/warning_target_inhuman = FALSE
- var/warning_target_dead = FALSE
- var/warning_full = FALSE
- var/warning_target_bloodvol = 99999
- var/was_alive = FALSE
-
-/datum/action/bloodsucker/feed/CheckCanUse(mob/living/carbon/user)
+ cooldown_time = 15 SECONDS
+ ///Amount of blood taken, reset after each Feed. Used for logging.
+ var/blood_taken = 0
+ ///The amount of Blood a target has since our last feed, this loops and lets us not spam alerts of low blood.
+ var/warning_target_bloodvol = BLOOD_VOLUME_MAX_LETHAL
+ ///Reference to the target we've fed off of
+ var/datum/weakref/target_ref
+ ///Are we feeding with passive grab or not?
+ var/silent_feed = TRUE
+
+/datum/action/cooldown/bloodsucker/feed/CanUse(mob/living/carbon/user)
. = ..()
if(!.)
return FALSE
-
- // Wearing mask
+ if(target_ref) //already sucking blood.
+ return FALSE
+ if(!level_current)
+ owner.balloon_alert(owner, "too weak!")
+ to_chat(owner, span_warning("You can't use [src] until you level up."))
+ return FALSE
if(user.is_mouth_covered())
- to_chat(owner, span_warning("Your mouth is covered!"))
+ owner.balloon_alert(owner, "mouth covered!")
return FALSE
- // Find my Target!
+ //Find target, it will alert what the problem is, if any.
if(!find_target())
return FALSE
- // DONE!
return TRUE
-/// Called twice: validating a subtle victim, or validating your grapple victim.
-/datum/action/bloodsucker/feed/proc/ValidateTarget(mob/living/target)
- // Must have Target.
- if(!target)//|| !ismob(target)
- to_chat(owner, span_warning("You must be next to or grabbing a victim to feed from them."))
- return FALSE
- // Not even living!
- if(!isliving(target) || issilicon(target))
- to_chat(owner, span_warning("You may only feed from living beings."))
- return FALSE
- // Check for other animals (Supposed to be after Mouse so Mouse can skip over it)
- else if(!iscarbon(target))
- to_chat(owner, span_warning("Such simple beings cannot be fed off of."))
- return FALSE
- // Has no blood to take!
- else if(target.blood_volume <= 0)
- to_chat(owner, span_warning("Your victim has no blood to take."))
+/datum/action/cooldown/bloodsucker/feed/ContinueActive(mob/living/user, mob/living/target)
+ if(!target)
return FALSE
- // Bloodsuckers can be fed off of if they are grabbed more than Passively.
- if(IS_BLOODSUCKER(target) && target == owner.pulling && owner.grab_state <= GRAB_PASSIVE)
- to_chat(owner, span_warning("Other Bloodsuckers will not fall for your subtle approach."))
+ if(!user.Adjacent(target))
return FALSE
- if(ishuman(target))
- var/mob/living/carbon/human/target_user = target
- if(!target_user.can_inject(owner, BODY_ZONE_HEAD, 1) && target == owner.pulling && owner.grab_state < GRAB_AGGRESSIVE)
- to_chat(owner, span_warning("Their suit is too thick to feed through."))
- return FALSE
- if(NOBLOOD in target_user.dna.species.species_traits)// || owner.get_blood_id() != target.get_blood_id())
- to_chat(owner, span_warning("Your victim's blood is not suitable for you to take."))
- return FALSE
return TRUE
-/// If I'm not grabbing someone, find me someone nearby.
-/datum/action/bloodsucker/feed/proc/find_target()
- // Default
- feed_target = null
- target_grappled = FALSE
- // If you are pulling a mob, that's your target. If you don't like it, then release them.
- if(owner.pulling && ismob(owner.pulling))
- // Check grapple target Valid
- if(!ValidateTarget(owner.pulling)) // Grabbed targets display error.
- return FALSE
- target_grappled = TRUE
- feed_target = owner.pulling
- return TRUE
- // Find Targets
- var/list/mob/living/seen_mobs = list()
- for(var/mob/living/watchers in view(1, owner) - owner)
- if(!isliving(watchers))
- continue
- seen_mobs |= watchers
- // None Seen!
- if(!seen_mobs.len)
- to_chat(owner, span_warning("You must be next to or grabbing a victim to feed from them."))
- return FALSE
- // Check Valids...
- var/list/targets_valid = list()
- var/list/targets_dead = list()
- for(var/mob/living/watchers in seen_mobs)
- // Check adjecent Valid target
- if(watchers != owner && ValidateTarget(watchers)) // Do NOT display errors. We'll be doing this again in CheckCanUse(), which will rule out grabbed targets.
- // Prioritize living, but remember dead as backup
- if(watchers.stat < DEAD)
- targets_valid |= watchers
- else
- targets_dead |= watchers
- // No Living? Try dead.
- if(!targets_valid.len && targets_dead.len)
- targets_valid = targets_dead
- // No Targets
- if(!targets_valid.len)
- // Did I see targets? Then display at least one error
- if(seen_mobs.len > 1)
- to_chat(owner, span_warning("None of these are valid targets to feed from subtly."))
- else
- ValidateTarget(seen_mobs[1])
- return FALSE
- else
- feed_target = pick(targets_valid)
- return TRUE
-
-/datum/action/bloodsucker/feed/ActivatePower()
- . = ..()
+/datum/action/cooldown/bloodsucker/feed/DeactivatePower()
var/mob/living/user = owner
- // Checks: Step 1 - Am I SECRET or LOUD?
- if(!bloodsuckerdatum_power.frenzied && (!target_grappled || owner.grab_state <= GRAB_PASSIVE)) // && iscarbon(target) // Non-carbons (animals) not passive. They go straight into aggressive.
- amSilent = TRUE
-
- // Checks: Step 2 - Is it a Mouse?
- if(istype(feed_target, /mob/living/simple_animal/mouse))
- if(bloodsuckerdatum_power.my_clan == CLAN_TOREADOR)
- to_chat(user, span_danger("I am not going to drink blood of a lesser lifeform."))
- DeactivatePower()
- return
- var/mob/living/simple_animal/mouse_target = feed_target
+ if(target_ref)
+ var/mob/living/feed_target = target_ref.resolve()
+ log_combat(user, feed_target, "fed on blood", addition="(and took [blood_taken] blood)")
+ user.balloon_alert(owner, "feed stopped")
+ to_chat(user, span_notice("You slowly release [feed_target]."))
+ if(feed_target.stat == DEAD)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankkilled", /datum/mood_event/drankkilled)
+ bloodsuckerdatum_power.AddHumanityLost(10)
+
+ target_ref = null
+ warning_target_bloodvol = BLOOD_VOLUME_MAX_LETHAL
+ blood_taken = 0
+ REMOVE_TRAIT(user, TRAIT_IMMOBILIZED, FEED_TRAIT)
+ REMOVE_TRAIT(user, TRAIT_MUTE, FEED_TRAIT)
+ return ..()
+
+/datum/action/cooldown/bloodsucker/feed/ActivatePower()
+ var/mob/living/feed_target = target_ref.resolve()
+ if(istype(feed_target, /mob/living/simple_animal/mouse || istype(feed_target, /mob/living/simple_animal/hostile/rat)))
+ to_chat(owner, span_notice("You recoil at the taste of a lesser lifeform."))
+ if(bloodsuckerdatum_power.my_clan.blood_drink_type != BLOODSUCKER_DRINK_INHUMANELY)
+ SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad)
+ bloodsuckerdatum_power.AddHumanityLost(1)
bloodsuckerdatum_power.AddBloodVolume(25)
- to_chat(user, span_notice("You recoil at the taste of a lesser lifeform."))
- SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad)
- bloodsuckerdatum_power.AddHumanityLost(1)
DeactivatePower()
- mouse_target.adjustBruteLoss(20)
+ feed_target.death()
return
- // Checks: Step 3 - How fast should I be and how much should I drink?
- var/feed_time_multiplier
+ var/feed_timer = clamp(round(FEED_DEFAULT_TIMER / (1.25 * level_current)), 1 SECONDS, FEED_DEFAULT_TIMER)
if(bloodsuckerdatum_power.frenzied)
- blood_take_mult = 2
- feed_time_multiplier = 8
- else if(!amSilent)
- blood_take_mult = 1
- feed_time_multiplier = 25 - (2.5 * level_current)
- else
- blood_take_mult = 0.3
- feed_time_multiplier = 45 - (2.5 * level_current)
- feed_time = max(8, feed_time_multiplier)
- // Let's check if our target is alive
- was_alive = feed_target.stat < DEAD && ishuman(feed_target)
-
- // Send pre-pull message
- if(amSilent)
- to_chat(owner, span_notice("You quietly lean towards [feed_target]"))
- else
- to_chat(owner, span_notice("You pull [feed_target] close to you!"))
+ feed_timer = 2 SECONDS
- // Start the countdown
- if(!do_mob(user, feed_target, feed_time, NONE, TRUE))
+ owner.balloon_alert(owner, "feeding off [feed_target]...")
+ if(!do_after(owner, feed_timer, feed_target, NONE, TRUE))
DeactivatePower()
- to_chat(owner, span_danger("Your feeding was interrupted!"))
return
-
- // Give them the effects (Depending on if we are silent or not)
- if(!amSilent)
- // Sleep & paralysis.
- ApplyVictimEffects(feed_target, first_hit = TRUE)
- // Pull target to you if they don't take up space.
+ if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE)
+ if(!IS_BLOODSUCKER(feed_target) && !IS_VASSAL(feed_target) && !IS_MONSTERHUNTER(feed_target))
+ feed_target.Unconscious((5 + level_current) SECONDS)
if(!feed_target.density)
- feed_target.Move(user.loc)
- user.visible_message(
- span_warning("[user] closes [user.p_their()] mouth around [feed_target]'s neck!"),
- span_warning("You sink your fangs into [feed_target]'s neck."),
- )
- if(amSilent)
- var/deadmessage = feed_target.stat == DEAD ? "" : " [feed_target.p_they(TRUE)] looks dazed, and will not remember this."
- user.visible_message(
- span_notice("[user] puts [feed_target]'s wrist up to [user.p_their()] mouth."), \
- span_notice("You slip your fangs into [feed_target]'s wrist.[deadmessage]"), \
- vision_distance = notice_range, ignored_mobs = feed_target) // Only people who AREN'T the target will notice this action.
-
- // Check if we have anyone watching - If there is one, we broke the Masquerade.
- for(var/mob/living/watchers in viewers(notice_range, owner) - owner - feed_target)
- // Are they someone who will actually report our behavior?
- if(watchers.client \
- && !watchers.has_unlimited_silicon_privilege \
- && watchers.stat != DEAD \
- && watchers.eye_blind == 0 \
- && watchers.eye_blurry == 0 \
- && !IS_BLOODSUCKER(watchers) \
- && !IS_VASSAL(watchers) \
- && !HAS_TRAIT(watchers, TRAIT_BLOODSUCKER_HUNTER))
- was_noticed = TRUE
- break
- if(was_noticed && !target_grappled)
- feeds_noticed++
- to_chat(owner, span_danger("Someone may have noticed..."))
- if(!bloodsuckerdatum_power.broke_masquerade)
- to_chat(user, span_cultbold("You broke the Masquerade [feeds_noticed] time(s), if you break it 3 times, you become a criminal to the Bloodsucker's Cause!"))
+ feed_target.Move(owner.loc)
+ silent_feed = FALSE //no more mr nice guy
+ owner.visible_message(
+ span_warning("[owner] closes [owner.p_their()] mouth around [feed_target]'s neck!"),
+ span_warning("You sink your fangs into [feed_target]'s neck."))
else
- to_chat(owner, span_notice("You think no one saw you..."))
+ // Only people who AREN'T the target will notice this action.
+ var/dead_message = feed_target.stat != DEAD ? " [feed_target.p_they(TRUE)] looks dazed, and will not remember this." : ""
+ owner.visible_message(
+ span_notice("[owner] puts [feed_target]'s wrist up to [owner.p_their()] mouth."), \
+ span_notice("You slip your fangs into [feed_target]'s wrist.[dead_message]"), \
+ vision_distance = FEED_NOTICE_RANGE, ignored_mobs = feed_target)
+
+ //check if we were seen
+ for(var/mob/living/watchers in oviewers(FEED_NOTICE_RANGE) - feed_target)
+ if(!watchers.client)
+ continue
+ if(watchers.has_unlimited_silicon_privilege)
+ continue
+ if(watchers.stat >= DEAD)
+ continue
+ if(HAS_TRAIT(watchers, TRAIT_BLIND) || HAS_TRAIT(watchers, TRAIT_NEARSIGHT))
+ continue
+ if(IS_BLOODSUCKER(watchers) || IS_VASSAL(watchers) || HAS_TRAIT(watchers.mind, TRAIT_BLOODSUCKER_HUNTER))
+ continue
+ owner.balloon_alert(owner, "feed noticed!")
+ bloodsuckerdatum_power.give_masquerade_infraction()
+ break
- // FEEEEEEEEED!! //
- ADD_TRAIT(user, TRAIT_MUTE, BLOODSUCKER_TRAIT) // My mouth is full!
- user.Immobilize(10 SECONDS) // Prevents spilling blood accidentally.
+ ADD_TRAIT(owner, TRAIT_MUTE, FEED_TRAIT)
+ ADD_TRAIT(owner, TRAIT_IMMOBILIZED, FEED_TRAIT)
+ return ..()
-/datum/action/bloodsucker/feed/UsePower(mob/living/user)
+/datum/action/cooldown/bloodsucker/feed/process()
+ if(!active) //If we aren't active (running on SSfastprocess)
+ return ..() //Manage our cooldown timers
+ var/mob/living/user = owner
+ var/mob/living/feed_target = target_ref.resolve()
if(!ContinueActive(user, feed_target))
- if(amSilent)
- to_chat(user, span_warning("Your feeding has been interrupted... but [feed_target.p_they()] didn't seem to notice you."))
- DeactivatePower()
- else
- to_chat(user, span_warning("Your feeding has been interrupted!"))
+ if(!silent_feed)
user.visible_message(
span_warning("[user] is ripped from [feed_target]'s throat. [feed_target.p_their(TRUE)] blood sprays everywhere!"),
span_warning("Your teeth are ripped from [feed_target]'s throat. [feed_target.p_their(TRUE)] blood sprays everywhere!"))
@@ -242,7 +141,7 @@
if(iscarbon(feed_target))
var/mob/living/carbon/carbon_target = feed_target
carbon_target.bleed(15)
- playsound(get_turf(feed_target), 'sound/effects/splat.ogg', 40, 1)
+ playsound(get_turf(feed_target), 'sound/effects/splat.ogg', 40, TRUE)
if(ishuman(feed_target))
var/mob/living/carbon/human/target_user = feed_target
var/obj/item/bodypart/head_part = target_user.get_bodypart(BODY_ZONE_HEAD)
@@ -253,103 +152,94 @@
feed_target.add_mob_blood(feed_target)
feed_target.apply_damage(10, BRUTE, BODY_ZONE_HEAD, wound_bonus = CANT_WOUND)
INVOKE_ASYNC(feed_target, TYPE_PROC_REF(/mob, emote), "scream")
- DeactivatePower()
+ DeactivatePower()
return
-
- ///////////////////////////////////////////////////////////
- // Handle Feeding! User & Victim Effects (per tick)
- bloodsuckerdatum_power.HandleFeeding(feed_target, blood_take_mult, level_current)
- amount_taken += amSilent ? 0.3 : 1
- if(!amSilent)
- ApplyVictimEffects(feed_target)
-
- ///////////////////////////////////////////////////////////
- // MOOD EFFECTS //
- // Drank good blood? - GOOD
- if(amount_taken > 5 && feed_target.stat < DEAD && ishuman(feed_target))
+ var/feed_strength_mult = 0
+ if(bloodsuckerdatum_power.frenzied)
+ feed_strength_mult = 2
+ else if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE)
+ feed_strength_mult = 1
+ else
+ feed_strength_mult = 0.3
+ blood_taken += bloodsuckerdatum_power.handle_feeding(feed_target, feed_strength_mult, level_current)
+ if(feed_strength_mult > 5 && feed_target.stat < DEAD)
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood)
- // Dead Blood? - BAD
+ if(bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY && !feed_target.mind)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad)
if(feed_target.stat >= DEAD)
- if(ishuman(feed_target))
- SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_dead)
- if(!warning_target_dead)
- to_chat(user, span_notice("Your victim is dead. [feed_target.p_their(TRUE)] blood barely nourishes you."))
- warning_target_dead = TRUE
-
- // Blood Remaining? (Carbons/Humans only)
- else if(!IS_BLOODSUCKER(feed_target))
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_dead)
+ if(!IS_BLOODSUCKER(feed_target))
if(feed_target.blood_volume <= BLOOD_VOLUME_BAD(feed_target) && warning_target_bloodvol > BLOOD_VOLUME_BAD(feed_target))
- to_chat(owner, span_danger("Your victim's blood is fatally low!"))
+ owner.balloon_alert(owner, "your victim's blood is fatally low!")
else if(feed_target.blood_volume <= BLOOD_VOLUME_OKAY(feed_target) && warning_target_bloodvol > BLOOD_VOLUME_OKAY(feed_target))
- to_chat(owner, span_danger("Your victim's blood is dangerously low."))
+ owner.balloon_alert(owner, "your victim's blood is dangerously low.")
else if(feed_target.blood_volume <= BLOOD_VOLUME_SAFE(feed_target) && warning_target_bloodvol > BLOOD_VOLUME_SAFE(feed_target))
- to_chat(owner, span_danger("Your victim's blood is at an unsafe level."))
- warning_target_bloodvol = feed_target.blood_volume // If we had a warning to give, it's been given by now.
- // Full?
- if(user.blood_volume >= bloodsuckerdatum_power.max_blood_volume && !warning_full)
- to_chat(owner, span_notice("You are full, further blood will be wasted."))
- warning_full = TRUE
- // Done?
+ owner.balloon_alert(owner, "your victim's blood is at an unsafe level.")
+ warning_target_bloodvol = feed_target.blood_volume
+
+ if(user.blood_volume >= bloodsuckerdatum_power.max_blood_volume)
+ DeactivatePower()
+ return
if(feed_target.blood_volume <= 0)
DeactivatePower()
- to_chat(owner, span_notice("You have bled your victim dry..."))
return
-
- // Blood Gulp Sound
owner.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
- if(!amSilent)
+ //play sound to target to show they're dying.
+ if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE)
feed_target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
-/// Check if we killed our target
-/datum/action/bloodsucker/feed/proc/CheckKilledTarget(mob/living/target)
- if(target && target.stat >= DEAD && ishuman(target))
- SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "drankkilled", /datum/mood_event/drankkilled)
- bloodsuckerdatum_power.AddHumanityLost(10)
-
-/// NOTE: We only care about pulling if target started off that way. Mostly only important for Aggressive feed.
-/datum/action/bloodsucker/feed/ContinueActive(mob/living/user, mob/living/target)
- if(!target)
+/datum/action/cooldown/bloodsucker/feed/proc/find_target()
+ if(owner.pulling && isliving(owner.pulling))
+ if(!can_feed_from(owner.pulling, give_warnings = TRUE))
+ return FALSE
+ target_ref = WEAKREF(owner.pulling)
+ return TRUE
+ var/list/close_living_mobs = list()
+ var/list/close_dead_mobs = list()
+ for(var/mob/living/near_targets in oview(1, owner))
+ if(!owner.Adjacent(near_targets))
+ continue
+ if(near_targets.stat)
+ close_living_mobs |= near_targets
+ else
+ close_dead_mobs |= near_targets
+ //Check living first
+ for(var/mob/living/suckers in close_living_mobs)
+ if(can_feed_from(suckers))
+ target_ref = WEAKREF(suckers)
+ return TRUE
+ //If not, check dead
+ for(var/mob/living/suckers in close_dead_mobs)
+ if(can_feed_from(suckers))
+ target_ref = WEAKREF(suckers)
+ return TRUE
+ //No one to suck blood from.
+ return FALSE
+
+/datum/action/cooldown/bloodsucker/feed/proc/can_feed_from(mob/living/target, give_warnings = FALSE)
+ if(istype(target, /mob/living/simple_animal/mouse) || istype(target, /mob/living/simple_animal/hostile/rat))
+ if(bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY)
+ if(give_warnings)
+ owner.balloon_alert(owner, "too disgusting!")
+ return FALSE
+ return TRUE
+ //Mice check done, only humans are otherwise allowed
+ if(!ishuman(target))
return FALSE
- if(!user.Adjacent(target))
+ var/mob/living/carbon/human/target_user = target
+ if(!(target_user.dna?.species) || !(target_user.mob_biotypes & MOB_ORGANIC))
+ if(give_warnings)
+ owner.balloon_alert(owner, "no blood!")
return FALSE
- if(target_grappled && !user.pulling)
+ if(!target_user.can_inject(owner, BODY_ZONE_HEAD))
+ if(give_warnings)
+ owner.balloon_alert(owner, "suit too thick!")
+ return FALSE
+ if((bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY) && !target_user.mind && !bloodsuckerdatum_power.frenzied)
+ if(give_warnings)
+ owner.balloon_alert(owner, "cant drink from mindless!")
return FALSE
return TRUE
-/// Bloodsuckers not affected by "the Kiss" of another vampire
-/datum/action/bloodsucker/feed/proc/ApplyVictimEffects(mob/living/target, first_hit = FALSE)
- if(IS_BLOODSUCKER(target) || IS_VASSAL(target))
- return
- if(first_hit)
- target.Unconscious(5 SECONDS,0)
- target.Paralyze(40 + 5 * level_current)
-
-/datum/action/bloodsucker/feed/DeactivatePower()
- . = ..() // activate = FALSE
-
- if(feed_target) // Check: Otherwise it runtimes if you fail to feed on someone.
- if(amSilent)
- to_chat(owner, span_notice("You slowly release [feed_target]'s wrist." + (feed_target.stat == 0 ? " [feed_target.p_their(TRUE)] face lacks expression, like you've already been forgotten." : "")))
- else
- owner.visible_message(
- span_warning("[owner] unclenches their teeth from [feed_target]'s neck."),
- span_warning("You retract your fangs and release [feed_target] from your bite."))
- log_combat(owner, feed_target, "fed on blood", addition="(and took [amount_taken] blood)")
- // Did we kill our target?
- if(was_alive)
- CheckKilledTarget(feed_target)
- // Only break it once we've broken it 3 times, not more.
- if(feeds_noticed == 3)
- bloodsuckerdatum_power.break_masquerade()
- // Reset ALL checks for next time the Power is used.
- amSilent = FALSE
- was_noticed = FALSE
- warning_target_inhuman = FALSE
- warning_target_dead = FALSE
- warning_full = FALSE
- feed_target = null
- warning_target_bloodvol = 99999
- // My mouth is no longer full
- var/mob/living/O = owner
- O.SetImmobilized(0)
- REMOVE_TRAIT(owner, TRAIT_MUTE, BLOODSUCKER_TRAIT)
+#undef FEED_NOTICE_RANGE
+#undef FEED_DEFAULT_TIMER
diff --git a/code/modules/antagonists/bloodsuckers/powers/fortitude.dm b/code/modules/antagonists/bloodsuckers/powers/fortitude.dm
index a19682ab496a..375eb17d7599 100644
--- a/code/modules/antagonists/bloodsuckers/powers/fortitude.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/fortitude.dm
@@ -1,8 +1,8 @@
-/datum/action/bloodsucker/fortitude
+/datum/action/cooldown/bloodsucker/fortitude
name = "Fortitude"
desc = "Withstand egregious physical wounds and walk away from attacks that would stun, pierce, and dismember lesser beings."
button_icon_state = "power_fortitude"
- power_explanation = "Fortitude:\n\
+ power_explanation = "Fortitude:\n\
Activating Fortitude will provide pierce, stun and dismember immunity.\n\
You will additionally gain resistance to Brute and Stamina damge, scaling with level.\n\
While using Fortitude, attempting to run will crush you.\n\
@@ -12,13 +12,14 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_AM_COSTLESS_UNCONSCIOUS
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 30
- cooldown = 8 SECONDS
+ cooldown_time = 8 SECONDS
constant_bloodcost = 0.2
var/was_running
var/fortitude_resist // So we can raise and lower your brute resist based on what your level_current WAS.
-/datum/action/bloodsucker/fortitude/ActivatePower()
+/datum/action/cooldown/bloodsucker/fortitude/ActivatePower()
. = ..()
+ owner.balloon_alert(owner, "fortitude turned on.")
to_chat(owner, span_notice("Your flesh, skin, and muscles become as steel."))
// Traits & Effects
ADD_TRAIT(owner, TRAIT_PIERCEIMMUNE, BLOODSUCKER_TRAIT)
@@ -40,21 +41,24 @@
if(was_running)
bloodsucker_user.toggle_move_intent()
-/datum/action/bloodsucker/fortitude/UsePower(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/fortitude/process()
// Checks that we can keep using this.
. = ..()
if(!.)
return
+ if(!active)
+ return
+ var/mob/living/carbon/user = owner
/// Prevents running while on Fortitude
if(user.m_intent != MOVE_INTENT_WALK)
user.toggle_move_intent()
- to_chat(user, span_warning("You attempt to run, crushing yourself."))
+ user.balloon_alert(user, "you attempt to run, crushing yourself.")
user.adjustBruteLoss(rand(5,15))
/// We don't want people using fortitude being able to use vehicles
if(user.buckled && istype(user.buckled, /obj/vehicle))
user.buckled.unbuckle_mob(src, force=TRUE)
-/datum/action/bloodsucker/fortitude/DeactivatePower()
+/datum/action/cooldown/bloodsucker/fortitude/DeactivatePower()
if(!ishuman(owner))
return
var/mob/living/carbon/human/bloodsucker_user = owner
@@ -73,58 +77,54 @@
if(was_running && bloodsucker_user.m_intent == MOVE_INTENT_WALK)
bloodsucker_user.toggle_move_intent()
+ owner.balloon_alert(owner, "fortitude turned off.")
return ..()
/// Monster Hunter version
-/datum/action/bloodsucker/fortitude/hunter
+/datum/action/cooldown/bloodsucker/fortitude/hunter
name = "Flow"
desc = "Use the arts to Flow, giving shove and stun immunity, as well as brute, burn, dismember and pierce resistance. You cannot run while this is active."
purchase_flags = HUNTER_CAN_BUY
-/datum/action/bloodsucker/fortitude/shadow
+/datum/action/cooldown/bloodsucker/fortitude/shadow
name = "Shadow Armor"
desc = "Empowered to the abyss, fortitude will now grant you a shadow armor, making your grip harder to escape and reduce projectile damage while in darkness."
+ background_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ active_background_icon_state = "lasombra_power_on"
+ base_background_icon_state = "lasombra_power_off"
button_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
- background_icon_state_on = "lasombra_power_on"
- background_icon_state_off = "lasombra_power_off"
- icon_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
button_icon_state = "power_armor"
additional_text = "Additionally gives you extra damage while fortitude'd and agro grab while in darkness."
purchase_flags = LASOMBRA_CAN_BUY
constant_bloodcost = 0.3
- var/mutable_appearance/armor_overlay
-/datum/action/bloodsucker/fortitude/shadow/ActivatePower()
+/datum/action/cooldown/bloodsucker/fortitude/shadow/ActivatePower()
. = ..()
var/mob/living/carbon/human/user = owner
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- armor_overlay = mutable_appearance('icons/obj/vamp_obj.dmi', "fortarmor")
var/turf/T = get_turf(owner)
var/light_amount = T.get_lumcount()
- if(light_amount <= 0.2)
- owner.add_overlay(armor_overlay)
+ if(light_amount <= LIGHTING_TILE_IS_DARK)
bloodsuckerdatum.frenzygrab.teach(user, TRUE)
to_chat(user, span_notice("Shadow tentacles form and attach themselves to your body, you feel as if your muscles have merged with the shadows!"))
user.physiology.punchdamagehigh_bonus += 0.5 * level_current
user.physiology.punchdamagelow_bonus += 0.5 * level_current
user.physiology.punchstunthreshold_bonus += 0.5 * level_current //So we dont give them stun baton hands
-/datum/action/bloodsucker/fortitude/shadow/UsePower()
+/datum/action/cooldown/bloodsucker/fortitude/shadow/process()
. = ..()
var/turf/T = get_turf(owner)
var/light_amount = T.get_lumcount()
var/mob/living/carbon/user = owner
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(light_amount > 0.2)
- owner.cut_overlay(armor_overlay)
+ if(light_amount > LIGHTING_TILE_IS_DARK)
bloodsuckerdatum.frenzygrab.remove(user)
to_chat(user, span_warning("As you enter in contact with the light, the tentacles dissipate!"))
-/datum/action/bloodsucker/fortitude/shadow/DeactivatePower()
+/datum/action/cooldown/bloodsucker/fortitude/shadow/DeactivatePower()
. = ..()
var/mob/living/carbon/human/user = owner
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- owner.cut_overlay(armor_overlay)
bloodsuckerdatum.frenzygrab.remove(user)
user.physiology.punchdamagehigh_bonus -= 0.5 * level_current
user.physiology.punchdamagelow_bonus -= 0.5 * level_current
diff --git a/code/modules/antagonists/bloodsuckers/powers/gangrel.dm b/code/modules/antagonists/bloodsuckers/powers/gangrel.dm
index 53355935dfdd..1739828d8618 100644
--- a/code/modules/antagonists/bloodsuckers/powers/gangrel.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/gangrel.dm
@@ -1,19 +1,19 @@
-/datum/action/bloodsucker/gangrel
+/datum/action/cooldown/bloodsucker/gangrel
+ background_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
button_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
- icon_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
background_icon_state = "gangrel_power_off"
- background_icon_state_on = "gangrel_power_on"
- background_icon_state_off = "gangrel_power_off"
+ active_background_icon_state = "gangrel_power_on"
+ base_background_icon_state = "gangrel_power_off"
purchase_flags = GANGREL_CAN_BUY
power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN
check_flags = BP_AM_COSTLESS_UNCONSCIOUS
- cooldown = 10 SECONDS
+ cooldown_time = 10 SECONDS
-/datum/action/bloodsucker/gangrel/transform
+/datum/action/cooldown/bloodsucker/gangrel/transform
name = "Transform"
desc = "Allows you to unleash your inner form and turn into something greater."
button_icon_state = "power_gangrel"
- power_explanation = "Transform:\n\
+ power_explanation = "Transform:\n\
A gangrel only power, will turn you into a feral being depending on your blood sucked.\n\
May have unforseen consequences if used on low blood sucked, upgrades every 500 units.\n\
Some forms have special abilites to them depending on what abilites you have.\n\
@@ -21,51 +21,72 @@
power_flags = BP_AM_SINGLEUSE|BP_AM_STATIC_COOLDOWN
bloodcost = 100
-/datum/action/bloodsucker/gangrel/transform/ActivatePower()
+/datum/action/cooldown/bloodsucker/gangrel/transform/ActivatePower()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/mob/living/carbon/human/user = owner
- var/datum/species/user_species = user.dna.species
- var/minortransformdone = FALSE
- var/mediumtransformdone = FALSE
- user.Immobilize(10 SECONDS)
if(!do_mob(user, user, 10 SECONDS, 1))
return
- switch(bloodsuckerdatum.total_blood_drank)
- if(0 to 500)
- if(!minortransformdone)
- if(iscatperson(user))
- user.set_species(/datum/species/lizard)
- playsound(user.loc, 'sound/voice/lizard/hiss.ogg', 50)
- else
- user.set_species(/datum/species/human/felinid)
- playsound(user.loc, 'sound/voice/feline/meow1.ogg', 50)
- if(DIGITIGRADE in user_species.species_traits)
- user_species.species_traits -= DIGITIGRADE
- minortransformdone = TRUE
- user.dna.species.punchdamagelow += 5.0
- user.dna.species.punchdamagehigh += 5.0 //stronk
- user.dna.species.punchstunthreshold += 5.0
- user.dna.species.armor += 30
- to_chat(user, span_notice("You aren't strong enough to morph into something stronger! But you do certainly feel more feral and stronger than before."))
+ var/list/radial_display = list()
+ //get our options, switches are kinda weird here cause wwe ant to stack them
+ if(bloodsuckerdatum.total_blood_drank) //makes the icons for the options
+ var/datum/radial_menu_choice/option = new
+ user.setDir(SOUTH)
+ var/icon/icon_to_mix = getFlatIcon(user)
+ icon_to_mix.Blend(icon('icons/mob/mutant_bodyparts.dmi', "m_ears_cat_FRONT"), ICON_OVERLAY)
+ option.image = icon_to_mix
+ option.info = "[iscatperson(user) ? "Lizard" : "Felinid"]: Increased agility and speed, but decreased defense."
+ radial_display["Lizard/Felinid"] = option //haha yeah
+ if(bloodsuckerdatum.total_blood_drank >= 250)
+ var/datum/radial_menu_choice/option = new
+ var/icon/body = icon('yogstation/icons/mob/human_parts.dmi', "gorilla_r_leg") //procedurally generated icons? don't mind if i do
+ body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_l_leg"), ICON_OVERLAY) //it may seem kinda big but it's worth it ngl
+ body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_r_arm"), ICON_OVERLAY)
+ body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_l_arm"), ICON_OVERLAY)
+ body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_r_hand"), ICON_OVERLAY)
+ body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_l_hand"), ICON_OVERLAY)
+ body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_chest_m"), ICON_OVERLAY)
+ body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_head_m"), ICON_OVERLAY)
+ option.image = body
+ option.info = "Gorilla: Increased durability and strength, but less speed."
+ radial_display["Gorilla"] = option
+ if(bloodsuckerdatum.total_blood_drank >= 750)
+ var/datum/radial_menu_choice/option = new
+ option.image = icon('icons/mob/bloodsucker_mobs.dmi', "batform")
+ option.info = "Turn into a giant bat simple mob with unique abilities."
+ radial_display["Bat"] = option
+ var/chosen_transform = show_radial_menu(user, user, radial_display)
+ transform(chosen_transform) //actually transform
+ . = ..()
+
+/datum/action/cooldown/bloodsucker/gangrel/transform/proc/transform(chosen_transform)
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ var/mob/living/carbon/human/user = owner
+ var/datum/species/user_species = user.dna.species
+ switch(chosen_transform)
+ if("Lizard/Felinid")
+ if(iscatperson(user))
+ user.set_species(/datum/species/lizard)
+ playsound(user.loc, 'sound/voice/lizard/hiss.ogg', 50)
else
- to_chat(user, span_notice("You still haven't evolved your ability yet."))
+ user.set_species(/datum/species/human/felinid)
+ playsound(user.loc, 'sound/voice/feline/meow1.ogg', 50)
+ if(!LAZYFIND(user_species.species_traits, DIGITIGRADE))
+ user_species.species_traits += DIGITIGRADE
+ user.dna.species.armor -= 20 //careful
+ user.dna.species.speedmod = -0.5
+ user.dna.species.action_speed_coefficient = 0.7
bloodsuckerdatum.AddBloodVolume(75)
- if(500 to 1000)
- if(!mediumtransformdone)
- user.set_species(/datum/species/gorilla)
- playsound(user.loc, 'sound/creatures/gorilla.ogg', 50)
- if(DIGITIGRADE in user_species.species_traits)
- user_species.species_traits -= DIGITIGRADE
- mediumtransformdone = TRUE
- user.dna.species.punchdamagelow += 7.5
- user.dna.species.punchdamagehigh += 7.5 //very stronk
- user.dna.species.punchstunthreshold += 7.5
- user.dna.species.armor += 35
- to_chat(owner, span_notice("You transform into a gorrila-ey beast, you feel stronger!"))
- else
- to_chat(owner, span_notice("You still haven't evolved your ability yet."))
- bloodsuckerdatum.AddBloodVolume(50)
- if(1500 to INFINITY)
+ if("Gorilla")
+ user.set_species(/datum/species/gorilla)
+ playsound(user.loc, 'sound/creatures/gorilla.ogg', 50)
+ if(DIGITIGRADE in user_species.species_traits)
+ user_species.species_traits -= DIGITIGRADE
+ user.dna.species.punchdamagelow += 10
+ user.dna.species.punchdamagehigh += 10 //very stronk
+ user.dna.species.punchstunthreshold += 10
+ user.dna.species.armor += 40
+ bloodsuckerdatum.AddBloodVolume(50)
+ if("Bat")
var/mob/living/simple_animal/hostile/bloodsucker/giantbat/gb
if(!gb || gb.stat == DEAD)
gb = new /mob/living/simple_animal/hostile/bloodsucker/giantbat(user.loc)
@@ -73,38 +94,35 @@
gb.bloodsucker = user
user.status_flags |= GODMODE //sad!
user.mind.transfer_to(gb)
- var/list/bat_powers = list(new /datum/action/bloodsucker/gangrel/transform_back,)
- for(var/datum/action/bloodsucker/power in bloodsuckerdatum.powers)
- if(istype(power, /datum/action/bloodsucker/targeted/haste))
- bat_powers += new /datum/action/bloodsucker/targeted/haste/batdash
- if(istype(power, /datum/action/bloodsucker/targeted/mesmerize))
- bat_powers += new /datum/action/bloodsucker/targeted/bloodbolt
- if(istype(power, /datum/action/bloodsucker/targeted/brawn))
- bat_powers += new /datum/action/bloodsucker/gangrel/wingslam
- for(var/datum/action/bloodsucker/power in bat_powers)
+ var/list/bat_powers = list(new /datum/action/cooldown/bloodsucker/gangrel/transform_back, )
+ for(var/datum/action/cooldown/bloodsucker/power in bloodsuckerdatum.powers)
+ if(istype(power, /datum/action/cooldown/bloodsucker/targeted/haste))
+ bat_powers += new /datum/action/cooldown/bloodsucker/targeted/haste/batdash
+ if(istype(power, /datum/action/cooldown/bloodsucker/targeted/mesmerize))
+ bat_powers += new /datum/action/cooldown/bloodsucker/targeted/bloodbolt
+ if(istype(power, /datum/action/cooldown/bloodsucker/targeted/brawn))
+ bat_powers += new /datum/action/cooldown/bloodsucker/gangrel/wingslam
+ for(var/datum/action/cooldown/bloodsucker/power in bat_powers)
power.Grant(gb)
- QDEL_IN(gb, 2 MINUTES)
playsound(gb.loc, 'sound/items/toysqueak1.ogg', 50, TRUE)
- to_chat(owner, span_notice("You transform into a fatty beast!"))
- return ..() //early to not mess with vampire organs proc
- bloodsuckerdatum.HealVampireOrgans() //regives you the stuff
- . = ..()
+ return //early to not mess with vampire organs proc
+
+ bloodsuckerdatum.heal_vampire_organs() //regives you the stuff
-/datum/action/bloodsucker/gangrel/transform_back
+/datum/action/cooldown/bloodsucker/gangrel/transform_back
name = "Transform"
desc = "Regress back into a human."
button_icon_state = "power_gangrel"
- power_explanation = "Transform:\n\
+ power_explanation = "Transform:\n\
Regress back to your humanoid form early, requires you to stand still.\n\
Beware you will not be able to transform again until the night passes!"
-/datum/action/bloodsucker/gangrel/transform_back/ActivatePower()
- var/mob/living/user = owner
- if(!do_mob(user, user, 10 SECONDS))
+/datum/action/cooldown/bloodsucker/gangrel/transform_back/ActivatePower()
+ if(!do_mob(owner, owner, 10 SECONDS))
return
var/mob/living/simple_animal/hostile/bloodsucker/bs
- qdel(owner)
- qdel(bs)
+ if(istype(owner, bs))
+ qdel(bs)
. = ..()
/*
////////////////||\\\\\\\\\\\\\\\\
@@ -112,15 +130,15 @@
// Powers \\
\\\\\\\\\\\\\\\\||////////////////
*/
-/datum/action/bloodsucker/targeted/haste/batdash
+/datum/action/cooldown/bloodsucker/targeted/haste/batdash
name = "Flying Haste"
desc = "Propulse yourself into a position of advantage."
+ background_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
button_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
- icon_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
button_icon_state = "power_baste"
- background_icon_state_on = "bat_power_on"
- background_icon_state_off = "bat_power_off"
- power_explanation = "Flying Haste:\n\
+ active_background_icon_state = "bat_power_on"
+ base_background_icon_state = "bat_power_off"
+ power_explanation = "Flying Haste<:\n\
Makes you dash into the air, creating a smoke cloud at the end.\n\
Helpful in situations where you either need to run away or engage in a crowd of people, works over tables.\n\
Created from your Immortal Haste ability."
@@ -128,27 +146,27 @@
check_flags = NONE
purchase_flags = GANGREL_CAN_BUY
bloodcost = 0
- cooldown = 15 SECONDS
+ cooldown_time = 15 SECONDS
-/datum/action/bloodsucker/targeted/haste/batdash/CheckCanUse(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/targeted/haste/batdash/CanUse(mob/living/carbon/user)
var/mob/living/L = user
if(L.stat == DEAD)
return FALSE
return TRUE
-/datum/action/bloodsucker/targeted/haste/batdash/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/haste/batdash/FireTargetedPower(atom/target_atom)
. = ..()
do_smoke(2, owner.loc, smoke_type = /obj/effect/particle_effect/fluid/smoke/transparent) //so you can attack people after hasting
-/datum/action/bloodsucker/targeted/bloodbolt
+/datum/action/cooldown/bloodsucker/targeted/bloodbolt
name = "Blood Bolt"
desc = "Shoot a blood bolt to damage your foes."
+ background_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
button_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
- icon_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
button_icon_state = "power_bolt"
- background_icon_state_on = "bat_power_on"
- background_icon_state_off = "bat_power_off"
- power_explanation = "Blood Bolt:\n\
+ active_background_icon_state = "bat_power_on"
+ base_background_icon_state = "bat_power_off"
+ power_explanation = "Blood Bolt<:\n\
Shoots a blood bolt that does moderate damage to your foes.\n\
Helpful in situations where you get outranged or just extra damage.\n\
Created from your Mesmerize ability."
@@ -156,15 +174,15 @@
check_flags = NONE
purchase_flags = GANGREL_CAN_BUY
bloodcost = 0
- cooldown = 12.5 SECONDS
+ cooldown_time = 12.5 SECONDS
-/datum/action/bloodsucker/targeted/bloodbolt/CheckCanUse(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/targeted/bloodbolt/CanUse(mob/living/carbon/user)
var/mob/living/L = user
if(L.stat == DEAD)
return FALSE
return TRUE
-/datum/action/bloodsucker/targeted/bloodbolt/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/bloodbolt/FireTargetedPower(atom/target_atom)
. = ..()
var/mob/living/user = owner
to_chat(user, span_warning("You fire a blood bolt!"))
@@ -177,14 +195,14 @@
magic_9ball.preparePixelProjectile(target_atom, user)
INVOKE_ASYNC(magic_9ball, TYPE_PROC_REF(/obj/item/projectile, fire))
playsound(user, 'sound/magic/wand_teleport.ogg', 60, TRUE)
- PowerActivatedSuccessfully()
+ power_activated_sucessfully()
/obj/item/projectile/magic/arcane_barrage/bloodsucker
name = "blood bolt"
icon_state = "bloodbolt"
damage_type = BURN
damage = 30
- var/datum/action/bloodsucker/targeted/bloodbolt/bloodsucker_power
+ var/datum/action/cooldown/bloodsucker/targeted/bloodbolt/bloodsucker_power
/obj/item/projectile/magic/arcane_barrage/bloodsucker/on_hit(target)
if(ismob(target))
@@ -195,20 +213,20 @@
return BULLET_ACT_HIT
. = ..()
-/datum/action/bloodsucker/gangrel/wingslam
+/datum/action/cooldown/bloodsucker/gangrel/wingslam
name = "Wing Slam"
desc = "Slams all foes next to you."
button_icon_state = "power_wingslam"
- background_icon_state_on = "bat_power_on"
- background_icon_state_off = "bat_power_off"
- power_explanation = "Wing Slam:\n\
+ active_background_icon_state = "bat_power_on"
+ base_background_icon_state = "bat_power_off"
+ power_explanation = "Wing Slam:\n\
Knocksback and immobilizes people adjacent to you.\n\
Has a low recharge time and may be helpful in meelee situations!\n\
Created from your Brawn ability."
check_flags = NONE
bloodcost = 0
-/datum/action/bloodsucker/gangrel/wingslam/ActivatePower()
+/datum/action/cooldown/bloodsucker/gangrel/wingslam/ActivatePower()
var/mob/living/user = owner
var/list/choices = list()
for(var/mob/living/carbon/C in view(1, user))
@@ -227,9 +245,9 @@
span_userdanger("You were sent flying by the flap of [user]'s wings!"),
)
to_chat(user, span_warning("You flap your wings, sending [M] flying!"))
- playsound(user.loc, 'sound/weapons/punch4.ogg', 60, 1, -1)
+ playsound(user.loc, 'sound/weapons/punch4.ogg', 60, TRUE, -1)
M.adjustBruteLoss(10)
- M.Knockdown(40)
+ M.Knockdown(4 SECONDS)
user.do_attack_animation(M, ATTACK_EFFECT_SMASH)
var/send_dir = get_dir(user, M)
var/turf/turf_thrown_at = get_ranged_target_turf(M, send_dir, 5)
@@ -242,15 +260,15 @@
\\\\\\\\\\\\\\\\||////////////////
*/
-/datum/action/bloodsucker/targeted/feast
+/datum/action/cooldown/bloodsucker/targeted/feast
name = "Feast"
desc = "DEVOUR THE WEAKLINGS, CAUSE THEM HARM. FEED. ME."
+ background_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
button_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
- icon_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
button_icon_state = "power_feast"
- background_icon_state_on = "wolf_power_on"
- background_icon_state_off = "wolf_power_off"
- power_explanation = "Feast:\n\
+ active_background_icon_state = "wolf_power_on"
+ base_background_icon_state = "wolf_power_off"
+ power_explanation = "Feast:\n\
Feasting on a dead person will give you a satiation point and gib them.\n\
Satiation points are essential for overcoming frenzy, after gathering 3 you'll turn back to normal.\n\
Feasting on someone while they are alive will bite them and make them bleed.\n\
@@ -260,12 +278,26 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_AM_COSTLESS_UNCONSCIOUS
purchase_flags = GANGREL_CAN_BUY
bloodcost = 0
- cooldown = 10 SECONDS
+ cooldown_time = 10 SECONDS
target_range = 1
power_activates_immediately = TRUE
prefire_message = "WHOM SHALL BE DEVOURED."
-/datum/action/bloodsucker/targeted/feast/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/feast/CheckValidTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ return isliving(target_atom)
+
+/datum/action/cooldown/bloodsucker/targeted/feast/CheckCanTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ // Target Type: Living
+ if(isliving(target_atom))
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/targeted/feast/FireTargetedPower(atom/target_atom)
if(isturf(target_atom))
return
owner.face_atom(target_atom)
@@ -273,7 +305,7 @@
var/mob/living/carbon/human/target = target_atom
if(target.stat == DEAD)
user.devour(target)
- PowerActivatedSuccessfully()
+ power_activated_sucessfully()
return
user.do_attack_animation(target, ATTACK_EFFECT_BITE)
var/affecting = pick(BODY_ZONE_CHEST, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
@@ -281,15 +313,15 @@
target.apply_damage(35, BRUTE, affecting, target.run_armor_check(affecting, MELEE, armour_penetration = 10), sharpness = SHARP_EDGED)
target.visible_message(span_danger("[user] takes a large bite out of [target]!"), \
span_userdanger("[user] takes a large bite out of you!"))
- PowerActivatedSuccessfully()
+ power_activated_sucessfully()
-/datum/action/bloodsucker/gangrel/wolfortitude
+/datum/action/cooldown/bloodsucker/gangrel/wolfortitude
name = "Wolftitude"
- desc = "Withstand egregious physical wounds and walk away from attacks that would stun, pierce, and dismember lesser beings."
+ desc = "WITHSTAND THEIR ATTACKS. DESTROY. THEM. ALL!"
button_icon_state = "power_wort"
- background_icon_state_on = "wolf_power_on"
- background_icon_state_off = "wolf_power_off"
- power_explanation = "Fortitude:\n\
+ active_background_icon_state = "wolf_power_on"
+ base_background_icon_state = "wolf_power_off"
+ power_explanation = "Fortitude:\n\
Activating Wolftitude will provide more attack damage, and more overall health.\n\
It will give you a minor health buff while it stands, but slow you down severely.\n\
It has a decent cooldown time to allow yourself to turn it off and run away for a while.\n\
@@ -298,9 +330,9 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_AM_COSTLESS_UNCONSCIOUS
purchase_flags = GANGREL_CAN_BUY
bloodcost = 0
- cooldown = 8 SECONDS
+ cooldown_time = 8 SECONDS
-/datum/action/bloodsucker/gangrel/wolfortitude/ActivatePower()
+/datum/action/cooldown/bloodsucker/gangrel/wolfortitude/ActivatePower()
. = ..()
to_chat(owner, span_notice("Your fur and claws harden, becoming as hard as steel."))
var/mob/living/simple_animal/hostile/A = owner
@@ -310,7 +342,7 @@
A.melee_damage_lower += 10
A.melee_damage_upper += 10
-/datum/action/bloodsucker/gangrel/wolfortitude/DeactivatePower()
+/datum/action/cooldown/bloodsucker/gangrel/wolfortitude/DeactivatePower()
. = ..()
var/mob/living/simple_animal/hostile/A = owner
A.maxHealth /= 1.2
@@ -319,15 +351,15 @@
A.melee_damage_lower -= 10
A.melee_damage_upper -= 10
-/datum/action/bloodsucker/targeted/pounce
+/datum/action/cooldown/bloodsucker/targeted/pounce
name = "Pounce"
desc = "TACKLE THE LIVING TO THE GROUND. FEAST ON CORPSES."
+ background_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
button_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
- icon_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
button_icon_state = "power_pounce"
- background_icon_state_on = "wolf_power_on"
- background_icon_state_off = "wolf_power_off"
- power_explanation = "Pounce:\n\
+ active_background_icon_state = "wolf_power_on"
+ base_background_icon_state = "wolf_power_off"
+ power_explanation = "Pounce:\n\
Click any player to instantly dash at them, knocking them down and paralyzing them for a short while.\n\
Additionally if they are dead you'll consume their corpse to gain satiation and get closer to leaving frenzy.\n\
Created from your Predatory Lunge ability."
@@ -335,25 +367,25 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = GANGREL_CAN_BUY
bloodcost = 0
- cooldown = 10 SECONDS
+ cooldown_time = 10 SECONDS
target_range = 6
power_activates_immediately = FALSE
-/datum/action/bloodsucker/targeted/pounce/ActivatePower()
+/datum/action/cooldown/bloodsucker/targeted/pounce/ActivatePower()
. = ..()
var/mob/living/simple_animal/hostile/bloodsucker/werewolf/A = owner
A.icon_state = initial(A.icon_state) + "_pounce"
A.icon_living = initial(A.icon_state) + "_pounce"
A.update_body()
-/datum/action/bloodsucker/targeted/pounce/DeactivatePower()
+/datum/action/cooldown/bloodsucker/targeted/pounce/DeactivatePower()
. = ..()
var/mob/living/simple_animal/hostile/bloodsucker/werewolf/A = owner
A.icon_state = initial(A.icon_state)
A.icon_living = initial(A.icon_state)
A.update_body()
-/datum/action/bloodsucker/targeted/pounce/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/pounce/FireTargetedPower(atom/target_atom)
. = ..()
var/mob/living/simple_animal/hostile/bloodsucker/werewolf/user = owner
owner.face_atom(target_atom)
@@ -371,19 +403,19 @@
if(!user.Adjacent(target))
return
user.devour(target)
- PowerActivatedSuccessfully()
+ power_activated_sucessfully()
return
target.Knockdown(6 SECONDS)
target.Paralyze(1 SECONDS)
- PowerActivatedSuccessfully()
+ power_activated_sucessfully()
-/datum/action/bloodsucker/targeted/pounce/CheckValidTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/pounce/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
return isliving(target_atom)
-/datum/action/bloodsucker/targeted/pounce/CheckCanTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/pounce/CheckCanTarget(atom/target_atom)
// DEFAULT CHECKS (Distance)
. = ..()
// Target Type: Living
@@ -391,13 +423,13 @@
return TRUE
return FALSE
-/datum/action/bloodsucker/gangrel/howl
+/datum/action/cooldown/bloodsucker/gangrel/howl
name = "Howl"
desc = "LET THEM KNOW WHAT HUNTS THEM. KNOCKDOWNS AND CONFUSES NEARBY WEAKLINGS."
button_icon_state = "power_howl"
- background_icon_state_on = "wolf_power_on"
- background_icon_state_off = "wolf_power_off"
- power_explanation = "Howl:\n\
+ active_background_icon_state = "wolf_power_on"
+ base_background_icon_state = "wolf_power_off"
+ power_explanation = "Howl:\n\
Activating Howl will start up a 2 and a half second charge up.\n\
After the charge up you'll knockdown anyone adjacent to you.\n\
Additionally, you'll confuse and deafen anyone in a 3 tile range.\n\
@@ -406,9 +438,9 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_AM_COSTLESS_UNCONSCIOUS
purchase_flags = GANGREL_CAN_BUY
bloodcost = 0
- cooldown = 15 SECONDS
+ cooldown_time = 15 SECONDS
-/datum/action/bloodsucker/gangrel/howl/ActivatePower()
+/datum/action/cooldown/bloodsucker/gangrel/howl/ActivatePower()
. = ..()
var/mob/living/simple_animal/hostile/bloodsucker/werewolf/A = owner
A.visible_message(span_danger("[A] inhales a ton of air!"), span_warning("You prepare to howl!"))
@@ -423,20 +455,20 @@
continue
if(iscarbon(target))
var/mob/living/carbon/M = target
- M.confused += 15
+ M.adjust_confusion(15 SECONDS)
M.adjustEarDamage(0, 50)
if(target.Adjacent(A))
M.Knockdown(4 SECONDS)
- M.Paralyze(0.1)
+ M.Paralyze(0.1 SECONDS)
DeactivatePower()
-/datum/action/bloodsucker/gangrel/rabidism
+/datum/action/cooldown/bloodsucker/gangrel/rabidism
name = "Rabidism"
desc = "FLAIL WILLDY, INJURING ALL WHO APPROACH AND SAVAGING STRUCTURES."
button_icon_state = "power_rabid"
- background_icon_state_on = "wolf_power_on"
- background_icon_state_off = "wolf_power_off"
- power_explanation = "Rabidism:\n\
+ active_background_icon_state = "wolf_power_on"
+ base_background_icon_state = "wolf_power_off"
+ power_explanation = "Rabidism:\n\
Rabidism will deal reduced damage to everyone in range including you.\n\
During Rabidism's ten second rage you'll deal alot more damage to structures.\n\
Be aware of it's long cooldown time.\n\
@@ -445,41 +477,41 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_AM_COSTLESS_UNCONSCIOUS
purchase_flags = GANGREL_CAN_BUY
bloodcost = 0
- cooldown = 20 SECONDS
+ cooldown_time = 20 SECONDS
-/datum/action/bloodsucker/gangrel/rabidism/ActivatePower()
+/datum/action/cooldown/bloodsucker/gangrel/rabidism/ActivatePower()
. = ..()
var/mob/living/simple_animal/hostile/bloodsucker/werewolf/A = owner
A.environment_smash = ENVIRONMENT_SMASH_RWALLS
A.obj_damage *= 3
addtimer(CALLBACK(src, PROC_REF(DeactivatePower)), 10 SECONDS)
-/datum/action/bloodsucker/gangrel/rabidism/ContinueActive()
+/datum/action/cooldown/bloodsucker/gangrel/rabidism/ContinueActive()
return TRUE
-/datum/action/bloodsucker/gangrel/rabidism/UsePower(mob/living/user)
+/datum/action/cooldown/bloodsucker/gangrel/rabidism/process()
. = ..()
- var/mob/living/simple_animal/hostile/bloodsucker/werewolf/A = user
+ var/mob/living/simple_animal/hostile/bloodsucker/werewolf/A = owner
for(var/mob/living/all_targets in dview(1, get_turf(A)))
if(all_targets == A || all_targets == A.bloodsucker)
continue
A.UnarmedAttack(all_targets) //byongcontrol
-/datum/action/bloodsucker/gangrel/rabidism/DeactivatePower()
+/datum/action/cooldown/bloodsucker/gangrel/rabidism/DeactivatePower()
. = ..()
var/mob/living/simple_animal/hostile/bloodsucker/werewolf/A = owner
A.environment_smash = initial(A.environment_smash)
A.obj_damage = initial(A.obj_damage)
-/datum/action/bloodsucker/targeted/tear
+/datum/action/cooldown/bloodsucker/targeted/tear
name = "Tear"
desc = "Ruthlessly tear into an enemy, dealing massive damage to them if successful."
button_icon_state = "power_tear"
+ background_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
button_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
- icon_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
- background_icon_state_on = "gangrel_power_on"
- background_icon_state_off = "gangrel_power_off"
- power_explanation = "Tear:\n\
+ active_background_icon_state = "gangrel_power_on"
+ base_background_icon_state = "gangrel_power_off"
+ power_explanation = "Tear:\n\
Tear will make your first attack start up a bleeding process.\n\
Bleeding process will only work if the target stands still.\n\
When it's done it will damage the target severely."
@@ -487,10 +519,10 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_AM_COSTLESS_UNCONSCIOUS
purchase_flags = GANGREL_CAN_BUY
bloodcost = 10
- cooldown = 7 SECONDS
+ cooldown_time = 7 SECONDS
var/mob/living/mauled
-/datum/action/bloodsucker/targeted/tear/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/tear/FireTargetedPower(atom/target_atom)
. = ..()
var/mob/living/carbon/human/user = owner
var/mob/living/target = target_atom
@@ -503,8 +535,12 @@
mauled = target
Mawl(target)
-/datum/action/bloodsucker/targeted/tear/proc/Mawl(mob/living/target)
+/datum/action/cooldown/bloodsucker/targeted/tear/proc/Mawl(mob/living/target)
var/mob/living/carbon/user = owner
+
+ if(!do_mob(user, target, 1 SECONDS))
+ return
+
var/datum/status_effect/saw_bleed/B = target.has_status_effect(STATUS_EFFECT_SAWBLEED)
while(!target.stat)
if(!target.Adjacent(user) || !do_mob(user, target, 0.8 SECONDS))
@@ -519,13 +555,13 @@
else
B.add_bleed(B.bleed_buildup)
-/datum/action/bloodsucker/targeted/tear/CheckValidTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/tear/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
return isliving(target_atom)
-/datum/action/bloodsucker/targeted/tear/CheckCanTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/tear/CheckCanTarget(atom/target_atom)
// DEFAULT CHECKS (Distance)
. = ..()
// Target Type: Living
@@ -564,7 +600,7 @@
mob_overlay_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi'
body_parts_covered = ARMS|HANDS
flags_inv = HIDEJUMPSUIT
- var/datum/action/bloodsucker/targeted/tear/tearaction = new
+ var/datum/action/cooldown/bloodsucker/targeted/tear/tearaction = new
/obj/item/clothing/shoes/wolflegs
name = "Wolf Legs"
diff --git a/code/modules/antagonists/bloodsuckers/powers/gohome.dm b/code/modules/antagonists/bloodsuckers/powers/gohome.dm
index 494cdbd53ac4..7d65fceea0f1 100644
--- a/code/modules/antagonists/bloodsuckers/powers/gohome.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/gohome.dm
@@ -3,65 +3,63 @@
#define GOHOME_FLICKER_TWO 4
#define GOHOME_TELEPORT 6
-/datum/action/bloodsucker/gohome
+/datum/action/cooldown/bloodsucker/gohome
name = "Vanishing Act"
desc = "As dawn aproaches, disperse into mist and return directly to your Lair.
WARNING: You will drop ALL of your possessions if observed by mortals."
button_icon_state = "power_gohome"
- background_icon_state_on = "vamp_power_off_oneshot"
- background_icon_state_off = "vamp_power_off_oneshot"
- power_explanation = "Vanishing Act: \n\
+ active_background_icon_state = "vamp_power_off_oneshot"
+ base_background_icon_state = "vamp_power_off_oneshot"
+ power_explanation = "Vanishing Act: \n\
Activating Vanishing Act will, after a short delay, teleport the user to their Claimed Coffin. \n\
The power will cancel out if the Claimed Coffin is somehow destroyed. \n\
Immediately after activating, lights around the user will begin to flicker. \n\
Once the user teleports to their coffin, in their place will be a Rat or Bat."
power_flags = BP_AM_TOGGLE|BP_AM_SINGLEUSE|BP_AM_STATIC_COOLDOWN
- check_flags = BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_STAKED|BP_CANT_USE_WHILE_INCAPACITATED
+ check_flags = BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_STAKED
// You only get this once you've claimed a lair and Sol is near.
purchase_flags = NONE
constant_bloodcost = 2
bloodcost = 100
- cooldown = 100 SECONDS
+ cooldown_time = 100 SECONDS
///What stage of the teleportation are we in
var/teleporting_stage = GOHOME_START
- var/list/spawning_mobs = list(
+ ///The types of mobs that will drop post-teleportation.
+ var/static/list/spawning_mobs = list(
/mob/living/simple_animal/mouse = 3,
/mob/living/simple_animal/hostile/retaliate/bat = 1,
)
-/datum/action/bloodsucker/gohome/CheckCanUse(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/gohome/CanUse(mob/living/carbon/user)
. = ..()
if(!.)
return FALSE
/// Have No Lair (NOTE: You only got this power if you had a lair, so this means it's destroyed)
if(!istype(bloodsuckerdatum_power) || !bloodsuckerdatum_power.coffin)
- to_chat(owner, span_warning("Your coffin has been destroyed!"))
+ owner.balloon_alert(owner, "coffin was destroyed!")
return FALSE
return TRUE
-/datum/action/bloodsucker/gohome/ActivatePower()
+/datum/action/cooldown/bloodsucker/gohome/ActivatePower()
. = ..()
- to_chat(owner, span_notice("You focus on separating your consciousness from your physical form..."))
+ owner.balloon_alert(owner, "preparing to teleport...")
-/datum/action/bloodsucker/gohome/UsePower(mob/living/user)
+/datum/action/cooldown/bloodsucker/gohome/process()
. = ..()
if(!.)
return FALSE
- if(!bloodsuckerdatum_power.coffin)
- to_chat(owner, span_warning("Your coffin has been destroyed! You no longer have a destination."))
- return FALSE
+
switch(teleporting_stage)
if(GOHOME_START)
- INVOKE_ASYNC(src, PROC_REF(flicker_lights), 3, 20)
+ INVOKE_ASYNC(src, PROC_REF(flicker_lights), 3, 2 SECONDS)
if(GOHOME_FLICKER_ONE)
- INVOKE_ASYNC(src, PROC_REF(flicker_lights), 4, 40)
+ INVOKE_ASYNC(src, PROC_REF(flicker_lights), 4, 4 SECONDS)
if(GOHOME_FLICKER_TWO)
- INVOKE_ASYNC(src, PROC_REF(flicker_lights), 4, 60)
+ INVOKE_ASYNC(src, PROC_REF(flicker_lights), 4, 6 SECONDS)
if(GOHOME_TELEPORT)
- do_mob(user, user, 1 SECONDS, TRUE)
- INVOKE_ASYNC(src, PROC_REF(teleport_to_coffin), user)
+ INVOKE_ASYNC(src, PROC_REF(teleport_to_coffin), owner)
teleporting_stage++
-/datum/action/bloodsucker/gohome/ContinueActive(mob/living/user, mob/living/target)
+/datum/action/cooldown/bloodsucker/gohome/ContinueActive(mob/living/user, mob/living/target)
. = ..()
if(!.)
return FALSE
@@ -72,12 +70,12 @@
return FALSE
return TRUE
-/datum/action/bloodsucker/gohome/proc/flicker_lights(flicker_range, beat_volume)
+/datum/action/cooldown/bloodsucker/gohome/proc/flicker_lights(flicker_range, beat_volume)
for(var/obj/machinery/light/nearby_lights in view(flicker_range, get_turf(owner)))
nearby_lights.flicker(5)
playsound(get_turf(owner), 'sound/effects/singlebeat.ogg', beat_volume, TRUE)
-/datum/action/bloodsucker/gohome/proc/teleport_to_coffin(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/gohome/proc/teleport_to_coffin(mob/living/carbon/user)
var/drop_item = FALSE
var/turf/current_turf = get_turf(user)
// If we aren't in the dark, anyone watching us will cause us to drop out stuff
@@ -114,12 +112,9 @@
/// TELEPORT: Move to Coffin & Close it!
user.set_resting(TRUE, TRUE, FALSE)
do_teleport(user, bloodsuckerdatum_power.coffin, no_effects = TRUE, forced = TRUE, channel = TELEPORT_CHANNEL_QUANTUM)
- user.Stun(3 SECONDS, TRUE)
- // Puts me inside.
- if(!bloodsuckerdatum_power.coffin.close(user))
- // CLOSE LID: If fail, force me in.
- bloodsuckerdatum_power.coffin.insert(user)
- playsound(bloodsuckerdatum_power.coffin.loc, bloodsuckerdatum_power.coffin.close_sound, 15, TRUE, -3)
+ bloodsuckerdatum_power.coffin.close(owner)
+ bloodsuckerdatum_power.coffin.take_contents()
+ playsound(bloodsuckerdatum_power.coffin.loc, bloodsuckerdatum_power.coffin.close_sound, 15, TRUE, -3)
DeactivatePower()
diff --git a/code/modules/antagonists/bloodsuckers/powers/masquerade.dm b/code/modules/antagonists/bloodsuckers/powers/masquerade.dm
index a564836bdfbd..74a3bda077ed 100644
--- a/code/modules/antagonists/bloodsuckers/powers/masquerade.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/masquerade.dm
@@ -10,11 +10,11 @@
* - Normal body temp -- remove Cold Blooded (return on deactivate)
*/
-/datum/action/bloodsucker/masquerade
+/datum/action/cooldown/bloodsucker/masquerade
name = "Masquerade"
desc = "Feign the vital signs of a mortal, and escape both casual and medical notice as the monster you truly are."
button_icon_state = "power_human"
- power_explanation = "Masquerade:\n\
+ power_explanation = "Masquerade:\n\
Activating Masquerade will forge your identity to be practically identical to that of a human;\n\
- You lose nearly all Bloodsucker benefits, including healing, sleep, radiation, crit, virus and cold immunity.\n\
- Your eyes turn to that of a regular human as your heart begins to beat.\n\
@@ -23,40 +23,41 @@
At the end of a Masquerade, you will re-gain your Vampiric abilities, as well as lose any Disease & Gene you might have."
power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN
check_flags = BP_CANT_USE_IN_FRENZY|BP_AM_COSTLESS_UNCONSCIOUS
- purchase_flags = BLOODSUCKER_CAN_BUY
+ purchase_flags = BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER
bloodcost = 10
- cooldown = 5 SECONDS
+ cooldown_time = 5 SECONDS
constant_bloodcost = 0.1
var/list/theqdeld = list()
-/datum/action/bloodsucker/masquerade/ActivatePower()
+/datum/action/cooldown/bloodsucker/masquerade/ActivatePower()
. = ..()
var/mob/living/carbon/user = owner
to_chat(user, span_notice("Your heart beats falsely within your lifeless chest. You may yet pass for a mortal."))
to_chat(user, span_warning("Your vampiric healing is halted while imitating life."))
// Remove Clan-specific stuff
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- switch(bloodsuckerdatum.my_clan)
- if(CLAN_TZIMISCE)
- set_antag_hud(user, "bloodsucker")
- if(CLAN_GANGREL)
- if(bloodsuckerdatum.clanprogress >= 1) // change this if we get more stuff to include other clans
- var/obj/item/clothing/neck/neckdrip = user.get_item_by_slot(SLOT_NECK)
- if(istype(neckdrip, /obj/item/clothing/neck/wolfcollar))
- theqdeld += neckdrip
- if(bloodsuckerdatum.clanprogress >= 2)
- var/obj/item/earsdrip = user.get_item_by_slot(SLOT_EARS)
- if(istype(earsdrip, /obj/item/radio/headset/wolfears))
- theqdeld += earsdrip
- if(bloodsuckerdatum.clanprogress >= 3)
- var/obj/item/clothing/gloves/glovesdrip = user.get_item_by_slot(SLOT_GLOVES)
- if(istype(glovesdrip, /obj/item/clothing/gloves/wolfclaws))
- theqdeld += glovesdrip
- if(bloodsuckerdatum.clanprogress >= 4)
- var/obj/item/clothing/shoes/shoesdrip = user.get_item_by_slot(SLOT_SHOES)
- if(istype(shoesdrip , /obj/item/clothing/shoes/wolflegs))
- theqdeld += shoesdrip
- QDEL_LIST(theqdeld)
+ if(bloodsuckerdatum.my_clan)
+ switch(bloodsuckerdatum.my_clan.get_clan())
+ if(CLAN_TZIMISCE)
+ bloodsuckerdatum.antag_hud_name = "bloodsucker"
+ if(CLAN_GANGREL)
+ if(bloodsuckerdatum.clanprogress >= 1) // change this if we get more stuff to include other clans
+ var/obj/item/clothing/neck/neckdrip = user.get_item_by_slot(SLOT_NECK)
+ if(istype(neckdrip, /obj/item/clothing/neck/wolfcollar))
+ theqdeld += neckdrip
+ if(bloodsuckerdatum.clanprogress >= 2)
+ var/obj/item/earsdrip = user.get_item_by_slot(SLOT_EARS)
+ if(istype(earsdrip, /obj/item/radio/headset/wolfears))
+ theqdeld += earsdrip
+ if(bloodsuckerdatum.clanprogress >= 3)
+ var/obj/item/clothing/gloves/glovesdrip = user.get_item_by_slot(SLOT_GLOVES)
+ if(istype(glovesdrip, /obj/item/clothing/gloves/wolfclaws))
+ theqdeld += glovesdrip
+ if(bloodsuckerdatum.clanprogress >= 4)
+ var/obj/item/clothing/shoes/shoesdrip = user.get_item_by_slot(SLOT_SHOES)
+ if(istype(shoesdrip , /obj/item/clothing/shoes/wolflegs))
+ theqdeld += shoesdrip
+ QDEL_LIST(theqdeld)
// Remove Bloodsucker traits
REMOVE_TRAIT(user, TRAIT_NOHARDCRIT, BLOODSUCKER_TRAIT)
REMOVE_TRAIT(user, TRAIT_NOSOFTCRIT, BLOODSUCKER_TRAIT)
@@ -76,10 +77,10 @@
eyes.flash_protect = initial(eyes.flash_protect)
var/obj/item/organ/heart/vampheart/vampheart = user.getorganslot(ORGAN_SLOT_HEART)
if(istype(vampheart))
- vampheart.FakeStart()
+ vampheart.fake_start_heart()
user.apply_status_effect(STATUS_EFFECT_MASQUERADE)
-/datum/action/bloodsucker/masquerade/DeactivatePower()
+/datum/action/cooldown/bloodsucker/masquerade/DeactivatePower()
. = ..() // activate = FALSE
var/mob/living/carbon/user = owner
user.remove_status_effect(STATUS_EFFECT_MASQUERADE)
@@ -110,26 +111,27 @@
disease.cure()
// Adds Clan-specific stuff
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- switch(bloodsuckerdatum.my_clan)
- if(CLAN_TZIMISCE)
- set_antag_hud(user, "tzimisce")
- if(CLAN_GANGREL)
- if(bloodsuckerdatum.clanprogress >= 1) // change this if we get more stuff to include other clans
- var/obj/item/clothing/neck/previousdrip = user.get_item_by_slot(SLOT_NECK)
- user.dropItemToGround(previousdrip)
- user.equip_to_slot_or_del(new /obj/item/clothing/neck/wolfcollar(user), SLOT_NECK)
- if(bloodsuckerdatum.clanprogress >= 2)
- var/obj/item/clothing/ears/previousdrip = user.get_item_by_slot(SLOT_EARS)
- user.dropItemToGround(previousdrip)
- user.equip_to_slot_or_del(new /obj/item/radio/headset/wolfears(user), SLOT_EARS)
- if(bloodsuckerdatum.clanprogress >= 3)
- var/obj/item/clothing/gloves/previousdrip = user.get_item_by_slot(SLOT_GLOVES)
- user.dropItemToGround(previousdrip)
- user.equip_to_slot_or_del(new /obj/item/clothing/gloves/wolfclaws(user), SLOT_GLOVES)
- if(bloodsuckerdatum.clanprogress >= 4)
- var/obj/item/clothing/shoes/previousdrip = user.get_item_by_slot(SLOT_SHOES)
- user.dropItemToGround(previousdrip)
- user.equip_to_slot_or_del(new /obj/item/clothing/shoes/wolflegs(user), SLOT_SHOES)
+ if(bloodsuckerdatum.my_clan)
+ switch(bloodsuckerdatum.my_clan.get_clan())
+ if(CLAN_TZIMISCE)
+ bloodsuckerdatum.antag_hud_name = "tzimisce"
+ if(CLAN_GANGREL)
+ if(bloodsuckerdatum.clanprogress >= 1) // change this if we get more stuff to include other clans
+ var/obj/item/clothing/neck/previousdrip = user.get_item_by_slot(SLOT_NECK)
+ user.dropItemToGround(previousdrip)
+ user.equip_to_slot_or_del(new /obj/item/clothing/neck/wolfcollar(user), SLOT_NECK)
+ if(bloodsuckerdatum.clanprogress >= 2)
+ var/obj/item/clothing/ears/previousdrip = user.get_item_by_slot(SLOT_EARS)
+ user.dropItemToGround(previousdrip)
+ user.equip_to_slot_or_del(new /obj/item/radio/headset/wolfears(user), SLOT_EARS)
+ if(bloodsuckerdatum.clanprogress >= 3)
+ var/obj/item/clothing/gloves/previousdrip = user.get_item_by_slot(SLOT_GLOVES)
+ user.dropItemToGround(previousdrip)
+ user.equip_to_slot_or_del(new /obj/item/clothing/gloves/wolfclaws(user), SLOT_GLOVES)
+ if(bloodsuckerdatum.clanprogress >= 4)
+ var/obj/item/clothing/shoes/previousdrip = user.get_item_by_slot(SLOT_SHOES)
+ user.dropItemToGround(previousdrip)
+ user.equip_to_slot_or_del(new /obj/item/clothing/shoes/wolflegs(user), SLOT_SHOES)
to_chat(user, span_notice("Your heart beats one final time, while your skin dries out and your icy pallor returns."))
/**
diff --git a/code/modules/antagonists/bloodsuckers/powers/olfaction.dm b/code/modules/antagonists/bloodsuckers/powers/olfaction.dm
index e498e95bd2c3..066a975b56bf 100644
--- a/code/modules/antagonists/bloodsuckers/powers/olfaction.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/olfaction.dm
@@ -1,21 +1,21 @@
#define TRACKING_SCENT (1<<0)
#define TRACKING_BLOOD (1<<1)
-/datum/action/bloodsucker/olfaction
+/datum/action/cooldown/bloodsucker/olfaction
name = "Sanguine Olfaction"
desc = "Smells blood."
button_icon_state = "power_olfac"
- power_explanation = "Olfaction:\n\
+ power_explanation = "Olfaction:\n\
Here's the sniffer.\n\
You shouldn't see this text, please scream and cry in the discord chat or ooc about it after the round if you do.\n\
Or make a bug report."
power_flags = BP_AM_STATIC_COOLDOWN
- cooldown = 20 SECONDS
+ cooldown_time = 20 SECONDS
-/datum/action/bloodsucker/olfaction/acquire_scent
+/datum/action/cooldown/bloodsucker/olfaction/acquire_scent
name = "Sanguine Olfaction"
desc = "Acquire a scent from the environment immediately around you to track."
- power_explanation = "Sanguine Olfaction:\n\
+ power_explanation = "Sanguine Olfaction:\n\
Activating this power will search around you in a 1-tile radius for bloodied objects and floors.\n\
If these objects are covered in blood from another humanoid being, you will see the option to track one of those scents.\n\
Selecting one of those scents will grant you a new ability to follow that scent. This will create a visible trail to follow to the owner of that blood.\n\
@@ -31,9 +31,9 @@
///tracking blood or scents. blood for bloodsuckers, scent for genetics olfaction
var/tracking_flags = TRACKING_SCENT
- var/datum/action/bloodsucker/olfaction/follow_scent/follow = new /datum/action/bloodsucker/olfaction/follow_scent()
+ var/datum/action/cooldown/bloodsucker/olfaction/follow_scent/follow = new /datum/action/cooldown/bloodsucker/olfaction/follow_scent()
-/datum/action/bloodsucker/olfaction/acquire_scent/Grant(mob/user, tracking)
+/datum/action/cooldown/bloodsucker/olfaction/acquire_scent/Grant(mob/user, tracking)
. = ..()
if(!iscarbon(user))
to_chat(owner, span_warning("Your olfactory senses aren't developed enough to utilize this ability!"))
@@ -42,7 +42,7 @@
if(tracking)
tracking_flags = tracking
-/datum/action/bloodsucker/olfaction/acquire_scent/Trigger()
+/datum/action/cooldown/bloodsucker/olfaction/acquire_scent/Trigger()
if(!..())
return FALSE
DeactivatePower()
@@ -78,7 +78,7 @@
if(!length(possible))
to_chat(owner,span_warning("Despite your best efforts, there are no scents to be found here"))
return
- tracking_target = input(owner, "Choose a scent to focus in on.", "Scent Tracking") as null|anything in possible
+ tracking_target = tgui_input_list(owner, "Choose a scent to focus in on.", "Scent Tracking", possible)
if(tracking_flags & TRACKING_SCENT)
tracking_target = possible[tracking_target]
if(!tracking_target)
@@ -105,49 +105,49 @@
return TRUE
-/datum/action/bloodsucker/olfaction/acquire_scent/Remove(mob/M)
+/datum/action/cooldown/bloodsucker/olfaction/acquire_scent/Remove(mob/M)
follow = locate(follow) in owner.actions
if(follow)
follow.Remove(owner)
return ..()
-/datum/action/bloodsucker/olfaction/acquire_scent/sanguine
+/datum/action/cooldown/bloodsucker/olfaction/acquire_scent/sanguine
sensitive = FALSE
tracking_flags = TRACKING_BLOOD
purchase_flags = BLOODSUCKER_CAN_BUY
- follow = new /datum/action/bloodsucker/olfaction/follow_scent/sanguine()
+ follow = new /datum/action/cooldown/bloodsucker/olfaction/follow_scent/sanguine()
-/datum/action/bloodsucker/olfaction/acquire_scent/lesser
+/datum/action/cooldown/bloodsucker/olfaction/acquire_scent/lesser
name = "Transcendent Olfaction"
- button_icon = 'icons/mob/actions/backgrounds.dmi'
+ background_icon = 'icons/mob/actions/backgrounds.dmi'
background_icon_state = "bg_spell"
- background_icon_state_on = "bg_spell"
- background_icon_state_off = "bg_spell"
+ active_background_icon_state = "bg_spell"
+ base_background_icon_state = "bg_spell"
- icon_icon = 'icons/mob/actions/actions_spells.dmi'
+ button_icon = 'icons/mob/actions/actions_spells.dmi'
button_icon_state = "nose"
buttontooltipstyle = ""
sensitive = TRUE
tracking_flags = TRACKING_SCENT
- power_explanation = "Transcendent Olfaction:\n\
+ power_explanation = "Transcendent Olfaction:\n\
Activating this power will search all objects and items in a 1-tile radius around you for scents.\n\
If these objects or items have been used by humanoid beings and still have their scent, you will see the option to track one of those scents.\n\
Selecting one of those scents will grant you a new ability to follow that scent. This will create a visible trail to follow to the owner of that scent.\n\
WARNING: it will be difficult to see around you while following a scent trail due to the the color being drained from all your vision except for the trail and your target."
- cooldown = 60 SECONDS
+ cooldown_time = 60 SECONDS
- follow = new /datum/action/bloodsucker/olfaction/follow_scent/lesser()
+ follow = new /datum/action/cooldown/bloodsucker/olfaction/follow_scent/lesser()
-/datum/action/bloodsucker/olfaction/follow_scent
+/datum/action/cooldown/bloodsucker/olfaction/follow_scent
name = "Follow the Scent"
desc = "Begin following the scent of your target."
button_icon_state = "power_olfac"
- power_explanation = "Follow the Scent:\n\
+ power_explanation = "Follow the Scent:\n\
Activating this power will create a trail you can follow to the target scent you selected with Olfaction.\n\
During this time, the target you're tracking will also leave a trail behind them as they move.\n\
WARNING: The path created for you to follow may bring you to doors you can't open and areas you don't have access to. If this happens, try again later or from a new area. \n\
@@ -159,7 +159,7 @@
///which status effect is being applied on use
var/status_effect = STATUS_EFFECT_BLOOD_HUNTER
-/datum/action/bloodsucker/olfaction/follow_scent/Grant(mob/user, tracking)
+/datum/action/cooldown/bloodsucker/olfaction/follow_scent/Grant(mob/user, tracking)
. = ..()
if(!iscarbon(user))
to_chat(owner, span_warning("Your olfactory senses aren't developed enough to utilize this ability!"))
@@ -169,7 +169,7 @@
if(tracking)
tracking_target = tracking
-/datum/action/bloodsucker/olfaction/follow_scent/Trigger()
+/datum/action/cooldown/bloodsucker/olfaction/follow_scent/Trigger()
if(!..())
return FALSE
DeactivatePower()
@@ -249,25 +249,25 @@
new /obj/effect/temp_visual/scent_trail(trail_step, scent_dir, sniffer, invert, scent_color)
sleep(0.1 SECONDS)
-/datum/action/bloodsucker/olfaction/follow_scent/sanguine
+/datum/action/cooldown/bloodsucker/olfaction/follow_scent/sanguine
name = "Follow the Scent"
desc = "Begin following the scent of your target."
status_effect = STATUS_EFFECT_BLOOD_HUNTER
-/datum/action/bloodsucker/olfaction/follow_scent/lesser
+/datum/action/cooldown/bloodsucker/olfaction/follow_scent/lesser
name = "Follow the Scent"
desc = "Begin following the scent of your target."
- button_icon = 'icons/mob/actions/backgrounds.dmi'
+ background_icon = 'icons/mob/actions/backgrounds.dmi'
background_icon_state = "bg_spell"
- background_icon_state_on = "bg_spell"
- background_icon_state_off = "bg_spell"
+ active_background_icon_state = "bg_spell"
+ base_background_icon_state = "bg_spell"
buttontooltipstyle = ""
- icon_icon = 'icons/mob/actions/actions_spells.dmi'
+ button_icon = 'icons/mob/actions/actions_spells.dmi'
button_icon_state = "nose"
status_effect = STATUS_EFFECT_SCENT_HUNTER
- cooldown = 60 SECONDS
+ cooldown_time = 60 SECONDS
diff --git a/code/modules/antagonists/bloodsuckers/powers/recuperate.dm b/code/modules/antagonists/bloodsuckers/powers/recuperate.dm
index 51a7a0bc0d4b..08cb9a819ef1 100644
--- a/code/modules/antagonists/bloodsuckers/powers/recuperate.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/recuperate.dm
@@ -1,9 +1,9 @@
/// Used by Vassals
-/datum/action/bloodsucker/recuperate
+/datum/action/cooldown/bloodsucker/recuperate
name = "Sanguine Recuperation"
desc = "Slowly heals you overtime using your master's blood, in exchange for some of your own blood and effort."
button_icon_state = "power_recup"
- power_explanation = "Recuperate:\n\
+ power_explanation = "Recuperate:\n\
Activating this Power will begin to heal your wounds.\n\
You will heal Brute and Toxin damage, at the cost of Stamina damage, and blood from both you and your Master.\n\
If you aren't a bloodless race, you will additionally heal Burn damage.\n\
@@ -12,9 +12,9 @@
check_flags = BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = NONE
bloodcost = 1.5
- cooldown = 10 SECONDS
+ cooldown_time = 10 SECONDS
-/datum/action/bloodsucker/recuperate/CheckCanUse(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/recuperate/CanUse(mob/living/carbon/user)
. = ..()
if(!.)
return
@@ -23,23 +23,26 @@
return FALSE
return TRUE
-/datum/action/bloodsucker/recuperate/ActivatePower()
+/datum/action/cooldown/bloodsucker/recuperate/ActivatePower()
. = ..()
to_chat(owner, span_notice("Your muscles clench as your master's immortal blood mixes with your own, knitting your wounds."))
-/datum/action/bloodsucker/recuperate/UsePower(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/recuperate/process()
. = ..()
if(!.)
return
+ if(!active)
+ return
+ var/mob/living/carbon/user = owner
var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(user)
vassaldatum.master.AddBloodVolume(-1)
- user.Jitter(5)
+ user.adjust_jitter(5 SECONDS)
user.adjustStaminaLoss(bloodcost * 1.1)
user.adjustBruteLoss(-2.5)
user.adjustToxLoss(-2, forced = TRUE)
// Plasmamen won't lose blood, they don't have any, so they don't heal from Burn.
- if(!(NOBLOOD in user.dna.species.species_traits))
+ if(!LAZYFIND(user.dna.species.species_traits, NOBLOOD))
user.blood_volume -= bloodcost
user.adjustFireLoss(-1.5)
// Stop Bleeding
@@ -47,12 +50,12 @@
for(var/obj/item/bodypart/part in user.bodyparts)
part.generic_bleedstacks--
-/datum/action/bloodsucker/recuperate/ContinueActive(mob/living/user, mob/living/target)
+/datum/action/cooldown/bloodsucker/recuperate/ContinueActive(mob/living/user, mob/living/target)
if(user.stat >= DEAD)
return FALSE
if(user.incapacitated())
return FALSE
return TRUE
-/datum/action/bloodsucker/recuperate/DeactivatePower()
+/datum/action/cooldown/bloodsucker/recuperate/DeactivatePower()
. = ..()
diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/_powers_targeted.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/_powers_targeted.dm
index 6057734f8e33..e73ae5fbfd33 100644
--- a/code/modules/antagonists/bloodsuckers/powers/targeted/_powers_targeted.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/targeted/_powers_targeted.dm
@@ -1,98 +1,92 @@
// NOTE: All Targeted spells are Toggles! We just don't bother checking here.
-/datum/action/bloodsucker/targeted
+/datum/action/cooldown/bloodsucker/targeted
power_flags = BP_AM_TOGGLE
- var/obj/effect/proc_holder/bloodsucker/bs_proc_holder
- var/target_range = 99
- var/prefire_message = ""
+ ///If set, how far the target has to be for the power to work.
+ var/target_range
+ ///Message sent to chat when clicking on the power, before you use it.
+ var/prefire_message
///Most powers happen the moment you click. Some, like Mesmerize, require time and shouldn't cost you if they fail.
var/power_activates_immediately = TRUE
///Is this power LOCKED due to being used?
var/power_in_use = FALSE
/// Modify description to add notice that this is aimed.
-/datum/action/bloodsucker/targeted/New(Target)
+/datum/action/cooldown/bloodsucker/targeted/New(Target)
desc += "
\[Targeted Power\]"
+ return ..()
+
+/datum/action/cooldown/bloodsucker/targeted/Remove(mob/living/remove_from)
. = ..()
- // Create Proc Holder for intercepting clicks
- bs_proc_holder = new()
- bs_proc_holder.linked_power = src
+ if(remove_from.click_intercept == src)
+ unset_click_ability(remove_from)
-/datum/action/bloodsucker/targeted/Trigger(trigger_flags)
- if(active && CheckCanDeactivate())
+/datum/action/cooldown/bloodsucker/targeted/Trigger(trigger_flags, atom/target)
+ if(active && can_deactivate())
DeactivatePower()
return FALSE
- if(!CheckCanPayCost(owner) || !CheckCanUse(owner))
+ if(!can_pay_cost(owner) || !CanUse(owner, trigger_flags))
return FALSE
- ActivatePower()
- UpdateButtonIcon()
- // Create & Link Targeting Proc
- var/mob/living/user = owner
- if(user.ranged_ability)
- user.ranged_ability.remove_ranged_ability()
- bs_proc_holder.add_ranged_ability(user)
- if(prefire_message != "")
+ if(prefire_message)
to_chat(owner, span_announce("[prefire_message]"))
- return TRUE
-/datum/action/bloodsucker/targeted/DeactivatePower()
+ ActivatePower(trigger_flags)
+ if(target)
+ return InterceptClickOn(owner, null, target)
+
+ return set_click_ability(owner)
+
+/datum/action/cooldown/bloodsucker/targeted/DeactivatePower()
if(power_flags & BP_AM_TOGGLE)
- UnregisterSignal(owner, COMSIG_LIVING_BIOLOGICAL_LIFE)
+ STOP_PROCESSING(SSprocessing, src)
active = FALSE
- DeactivateRangedAbility()
- UpdateButtonIcon()
+ build_all_button_icons()
+ unset_click_ability(owner)
// ..() // we don't want to pay cost here
-/// Only Turned off when CLICK is disabled...aka, when you successfully clicked
-/datum/action/bloodsucker/targeted/proc/DeactivateRangedAbility()
- bs_proc_holder.remove_ranged_ability()
-
/// Check if target is VALID (wall, turf, or character?)
-/datum/action/bloodsucker/targeted/proc/CheckValidTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/proc/CheckValidTarget(atom/target_atom)
if(target_atom == owner)
return FALSE
return TRUE
/// Check if valid target meets conditions
-/datum/action/bloodsucker/targeted/proc/CheckCanTarget(atom/target_atom)
- // Out of Range
- if(!(target_atom in view(target_range, owner)))
- if(target_range > 1) // Only warn for range if it's greater than 1. Brawn doesn't need to announce itself.
- to_chat(owner, "Target out of range.")
- return FALSE
+/datum/action/cooldown/bloodsucker/targeted/proc/CheckCanTarget(atom/target_atom)
+ if(target_range)
+ // Out of Range
+ if(!(target_atom in view(target_range, owner)))
+ if(target_range > 1) // Only warn for range if it's greater than 1. Brawn doesn't need to announce itself.
+ owner.balloon_alert(owner, "out of range.")
+ return FALSE
return istype(target_atom)
/// Click Target
-/datum/action/bloodsucker/targeted/proc/ClickWithPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/proc/click_with_power(atom/target_atom)
// CANCEL RANGED TARGET check
if(power_in_use || !CheckValidTarget(target_atom))
return FALSE
// Valid? (return true means DON'T cancel power!)
- if(!CheckCanPayCost() || !CheckCanUse(owner) || !CheckCanTarget(target_atom))
+ if(!can_pay_cost() || !CanUse(owner) || !CheckCanTarget(target_atom))
return TRUE
power_in_use = TRUE // Lock us into this ability until it successfully fires off. Otherwise, we pay the blood even if we fail.
- FireTargetedPower(target_atom) // We use this instead of ActivatePower(), which has no input
+ FireTargetedPower(target_atom) // We use this instead of ActivatePower(trigger_flags), which has no input
// Skip this part so we can return TRUE right away.
if(power_activates_immediately)
- PowerActivatedSuccessfully() // Mesmerize pays only after success.
+ power_activated_sucessfully() // Mesmerize pays only after success.
power_in_use = FALSE
return TRUE
/// Like ActivatePower, but specific to Targeted (and takes an atom input). We don't use ActivatePower for targeted.
-/datum/action/bloodsucker/targeted/proc/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/proc/FireTargetedPower(atom/target_atom)
log_combat(owner, target_atom, "used [name] on")
/// The power went off! We now pay the cost of the power.
-/datum/action/bloodsucker/targeted/proc/PowerActivatedSuccessfully()
- PayCost()
+/datum/action/cooldown/bloodsucker/targeted/proc/power_activated_sucessfully()
+ unset_click_ability(owner)
+ pay_cost()
+ StartCooldown()
DeactivatePower()
- StartCooldown() // Do AFTER UpdateIcon() inside of DeactivatePower. Otherwise icon just gets wiped.
-
-/// Target Proc Holder
-/obj/effect/proc_holder/bloodsucker
- ///The linked Bloodsucker power
- var/datum/action/bloodsucker/targeted/linked_power
-/obj/effect/proc_holder/bloodsucker/InterceptClickOn(mob/living/caller, params, atom/targeted_atom)
- return linked_power.ClickWithPower(targeted_atom)
+/datum/action/cooldown/bloodsucker/targeted/InterceptClickOn(mob/living/caller, params, atom/target)
+ click_with_power(target)
diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm
index 68f89d069f46..07bdc5473261 100644
--- a/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm
@@ -1,8 +1,8 @@
-/datum/action/bloodsucker/targeted/brawn
+/datum/action/cooldown/bloodsucker/targeted/brawn
name = "Brawn"
desc = "Snap restraints, break lockers and doors, or deal terrible damage with your bare hands."
button_icon_state = "power_strength"
- power_explanation = "Brawn:\n\
+ power_explanation = "Brawn:\n\
Click any person to bash into them, break restraints you have or knocking a grabber down. Only one of these can be done per use.\n\
Punching a Cyborg will heavily EMP them in addition to deal damage.\n\
At level 3, you get the ability to break closets open, additionally can both break restraints AND knock a grabber down in the same use.\n\
@@ -12,29 +12,25 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 8
- cooldown = 9 SECONDS
+ cooldown_time = 9 SECONDS
target_range = 1
power_activates_immediately = TRUE
prefire_message = "Select a target."
-/datum/action/bloodsucker/targeted/brawn/CheckCanUse(mob/living/carbon/user)
- . = ..()
- if(!.) // Default checks
- return FALSE
-
+/datum/action/cooldown/bloodsucker/targeted/brawn/ActivatePower(trigger_flags)
// Did we break out of our handcuffs?
- if(CheckBreakRestraints())
- PowerActivatedSuccessfully()
+ if(break_restraints())
+ power_activated_sucessfully()
return FALSE
// Did we knock a grabber down? We can only do this while not also breaking restraints if strong enough.
- if(level_current >= 3 && CheckEscapePuller())
- PowerActivatedSuccessfully()
+ if(level_current >= 3 && escape_puller())
+ power_activated_sucessfully()
return FALSE
// Did neither, now we can PUNCH.
- return TRUE
+ return ..()
// Look at 'biodegrade.dm' for reference
-/datum/action/bloodsucker/targeted/brawn/proc/CheckBreakRestraints()
+/datum/action/cooldown/bloodsucker/targeted/brawn/proc/break_restraints()
var/mob/living/carbon/human/user = owner
///Only one form of shackles removed per use
var/used = FALSE
@@ -49,7 +45,7 @@
span_warning("[closet] tears apart as you bash it open from within!"),
)
to_chat(user, span_warning("We bash [closet] wide open!"))
- addtimer(CALLBACK(src, PROC_REF(break_closet), user, closet), 1)
+ addtimer(CALLBACK(src, PROC_REF(break_closet), user, closet), 0.1 SECONDS)
used = TRUE
// Remove both Handcuffs & Legcuffs
@@ -60,8 +56,8 @@
span_warning("[user] discards their restraints like it's nothing!"),
span_warning("We break through our restraints!"),
)
- user.clear_cuffs(cuffs, TRUE)
- user.clear_cuffs(legcuffs, TRUE)
+ user.clear_cuffs(cuffs, TRUE, TRUE)
+ user.clear_cuffs(legcuffs, TRUE, TRUE)
used = TRUE
// Remove Straightjackets
@@ -84,14 +80,14 @@
return used
// This is its own proc because its done twice, to repeat code copypaste.
-/datum/action/bloodsucker/targeted/brawn/proc/break_closet(mob/living/carbon/human/user, obj/structure/closet/closet)
+/datum/action/cooldown/bloodsucker/targeted/brawn/proc/break_closet(mob/living/carbon/human/user, obj/structure/closet/closet)
if(closet)
closet.welded = FALSE
closet.locked = FALSE
closet.broken = TRUE
closet.open()
-/datum/action/bloodsucker/targeted/brawn/proc/CheckEscapePuller()
+/datum/action/cooldown/bloodsucker/targeted/brawn/proc/escape_puller()
if(!owner.pulledby) // || owner.pulledby.grab_state <= GRAB_PASSIVE)
return FALSE
var/mob/pulled_mob = owner.pulledby
@@ -115,14 +111,14 @@
owner.pulledby = null // It's already done, but JUST IN CASE.
return TRUE
-/datum/action/bloodsucker/targeted/brawn/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/brawn/FireTargetedPower(atom/target_atom)
. = ..()
var/mob/living/user = owner
// Target Type: Mob
if(isliving(target_atom))
var/mob/living/target = target_atom
var/mob/living/carbon/carbonuser = user
- var/hitStrength = carbonuser.dna.species.punchdamagehigh * 1.25 + 2
+ var/hitStrength = carbonuser.dna.species.punchdamagehigh * 1.25 + level_current
// Knockdown!
var/powerlevel = min(5, 1 + level_current)
if(rand(5 + powerlevel) >= 5)
@@ -169,48 +165,48 @@
playsound(get_turf(target_airlock), 'sound/effects/bang.ogg', 30, TRUE, -1)
target_airlock.open(2) // open(2) is like a crowbar or jaws of life.
-/datum/action/bloodsucker/targeted/brawn/CheckValidTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/brawn/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
return isliving(target_atom) || istype(target_atom, /obj/machinery/door) || istype(target_atom, /obj/structure/closet)
-/datum/action/bloodsucker/targeted/brawn/CheckCanTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/brawn/CheckCanTarget(atom/target_atom)
// DEFAULT CHECKS (Distance)
. = ..()
if(!.) // Disable range notice for Brawn.
return FALSE
- // Must outside Closet to target anyone!
+ // Must be outside Closet to target anyone!
if(!isturf(owner.loc))
return FALSE
// Target Type: Living
if(isliving(target_atom))
return TRUE
// Target Type: Door
- else if(istype(target_atom, /obj/machinery/door))
+ if(istype(target_atom, /obj/machinery/door))
if(level_current < 4)
- to_chat(owner, span_warning("You need [4 - level_current] more levels to be able to break open the [target_atom]!"))
+ owner.balloon_alert(owner, "you need [4 - level_current] more levels!")
return FALSE
return TRUE
// Target Type: Locker
- else if(istype(target_atom, /obj/structure/closet))
+ if(istype(target_atom, /obj/structure/closet))
if(level_current < 3)
- to_chat(owner, span_warning("You need [3 - level_current] more levels to be able to break open the [target_atom]!"))
+ owner.balloon_alert(owner, "you need [3 - level_current] more levels!")
return FALSE
return TRUE
return FALSE
-/datum/action/bloodsucker/targeted/brawn/shadow
+/datum/action/cooldown/bloodsucker/targeted/brawn/shadow
name = "Obliterate"
+ background_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ active_background_icon_state = "lasombra_power_on"
+ base_background_icon_state = "lasombra_power_off"
button_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
- background_icon_state_on = "lasombra_power_on"
- background_icon_state_off = "lasombra_power_off"
- icon_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
button_icon_state = "power_obliterate"
additional_text = "Additionally afflicts the target with a shadow curse while in darkness and disables any lights they may possess."
purchase_flags = LASOMBRA_CAN_BUY
-/datum/action/bloodsucker/targeted/brawn/shadow/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/brawn/shadow/FireTargetedPower(atom/target_atom)
var/mob/living/carbon/human/H = target_atom
H.apply_status_effect(STATUS_EFFECT_SHADOWAFFLICTED)
var/turf/T = get_turf(H)
diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/haste.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/haste.dm
index ff15040acfde..778f9f6d59d8 100644
--- a/code/modules/antagonists/bloodsuckers/powers/targeted/haste.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/targeted/haste.dm
@@ -3,11 +3,11 @@
* Level 3: Stun People Passed
*/
-/datum/action/bloodsucker/targeted/haste
+/datum/action/cooldown/bloodsucker/targeted/haste
name = "Immortal Haste"
desc = "Dash somewhere with supernatural speed. Those nearby may be knocked away, stunned, or left empty-handed."
button_icon_state = "power_speed"
- power_explanation = "Immortal Haste:\n\
+ power_explanation = "Immortal Haste:\n\
Click anywhere to immediately dash towards that location.\n\
The Power will not work if you are lying down, in no gravity, or are aggressively grabbed.\n\
Anyone in your way during your Haste will be knocked down and Payalyzed, moreso if they are using Flow.\n\
@@ -16,14 +16,15 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 6
- cooldown = 12 SECONDS
+ cooldown_time = 12 SECONDS
target_range = 15
power_activates_immediately = TRUE
- var/list/hit //current hit, set while power is in use as we can't pass the list as an extra calling argument in registersignal.
+ /// Current hit, set while power is in use as we can't pass the list as an extra calling argument in registersignal.
+ var/list/hit = list()
/// If set, uses this speed in deciseconds instead of world.tick_lag
var/speed_override
-/datum/action/bloodsucker/targeted/haste/CheckCanUse(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/targeted/haste/CanUse(mob/living/carbon/user)
. = ..()
if(!.)
return FALSE
@@ -40,14 +41,14 @@
return TRUE
/// Anything will do, if it's not me or my square
-/datum/action/bloodsucker/targeted/haste/CheckValidTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/haste/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
return target_atom.loc != owner.loc
/// This is a non-async proc to make sure the power is "locked" until this finishes.
-/datum/action/bloodsucker/targeted/haste/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/haste/FireTargetedPower(atom/target_atom)
. = ..()
hit = list()
RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
@@ -80,7 +81,7 @@
UnregisterSignal(owner, COMSIG_MOVABLE_MOVED)
hit = null
-/datum/action/bloodsucker/targeted/haste/proc/on_move()
+/datum/action/cooldown/bloodsucker/targeted/haste/proc/on_move()
for(var/mob/living/all_targets in dview(1, get_turf(owner)))
if(!hit[all_targets] && (all_targets != owner))
hit[all_targets] = TRUE
@@ -90,25 +91,25 @@
all_targets.spin(10, 1)
if(IS_MONSTERHUNTER(all_targets) && HAS_TRAIT(all_targets, TRAIT_STUNIMMUNE))
to_chat(all_targets, "Knocked down!")
- for(var/datum/action/bloodsucker/power in all_targets.actions)
+ for(var/datum/action/cooldown/bloodsucker/power in all_targets.actions)
if(power.active)
power.DeactivatePower()
- all_targets.Jitter(20)
- all_targets.confused = max(8, all_targets.confused)
- all_targets.stuttering = max(8, all_targets.stuttering)
+ all_targets.set_timed_status_effect(8 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE)
+ all_targets.set_timed_status_effect(8 SECONDS, /datum/status_effect/confusion, only_if_higher = TRUE)
+ all_targets.adjust_timed_status_effect(8 SECONDS, /datum/status_effect/speech/stutter)
all_targets.Knockdown(10 + level_current * 5) // Re-knock them down, the first one didn't work due to stunimmunity
-/datum/action/bloodsucker/targeted/haste/shadow
+/datum/action/cooldown/bloodsucker/targeted/haste/shadow
name = "Blow"
+ background_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ active_background_icon_state = "lasombra_power_on"
+ base_background_icon_state = "lasombra_power_off"
button_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
- background_icon_state_on = "lasombra_power_on"
- background_icon_state_off = "lasombra_power_off"
- icon_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
button_icon_state = "power_bomb"
additional_text = "Additionally disables lightframes in range and confuses nearby mortals."
purchase_flags = LASOMBRA_CAN_BUY
-/datum/action/bloodsucker/targeted/haste/shadow/on_move()
+/datum/action/cooldown/bloodsucker/targeted/haste/shadow/on_move()
. = ..()
var/mob/living/carbon/human/user = owner
for(var/obj/machinery/light/L in range(5, user))
@@ -123,5 +124,5 @@
if(iscarbon(target))
var/mob/living/carbon/M = target
to_chat(M, span_danger("As a figure passes by, you feel your head spike up!"))
- M.confused += 4
- M.adjustEarDamage(0, 15)
\ No newline at end of file
+ M.adjust_confusion(4 SECONDS)
+ M.adjustEarDamage(0, 15)
diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/lasombra.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/lasombra.dm
new file mode 100644
index 000000000000..450049b3ed12
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/powers/targeted/lasombra.dm
@@ -0,0 +1,103 @@
+/datum/action/cooldown/bloodsucker/targeted/lasombra
+ name = "Shadow Control"
+ desc = "Submit shadows to your bidding, making darkness much scarier than before."
+ background_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ button_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ active_background_icon_state = "lasombra_power_on"
+ base_background_icon_state = "lasombra_power_off"
+ button_icon_state = "power_shadow"
+ power_explanation = "Shadow Control:\n\
+ Shadow Control allows you to do different things based on level:\n\
+ Level 1 - Click lights to instantly break them;\n\
+ Level 2 - Click a person near darkness to shut off any lights they have.\n\
+ Level 3 - Click doors to bolt them down for a while, scales with level;\n\
+ Level 4 - Click tiles to make a temporary fence on them that blocks movement, scales with level."
+ power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN
+ check_flags = BP_CANT_USE_IN_FRENZY
+ purchase_flags = LASOMBRA_CAN_BUY
+ bloodcost = 20
+ cooldown_time = 15 SECONDS
+
+/datum/action/cooldown/bloodsucker/targeted/lasombra/CheckValidTarget(atom/target_atom)
+ . = ..()
+ if(!.)
+ return FALSE
+ return isliving(target_atom) || istype(target_atom, /obj/machinery/door) || istype(target_atom, /obj/machinery/light) || isopenturf(target_atom)
+
+/datum/action/cooldown/bloodsucker/targeted/lasombra/CheckCanTarget(atom/target_atom)
+ . = ..()
+ if(isopenturf(target_atom))
+ if(level_current < 4)
+ owner.balloon_alert(owner, "you need [4 - level_current] more levels!")
+ return FALSE
+ return TRUE
+ if(istype(target_atom, /obj/machinery/door))
+ if(level_current < 3)
+ owner.balloon_alert(owner, "you need [3 - level_current] more levels!")
+ return FALSE
+ return TRUE
+ if(isliving(target_atom))
+ if(level_current < 2)
+ owner.balloon_alert(owner, "you need 1 more level!")
+ return FALSE
+ return TRUE
+ if(istype(target_atom, /obj/machinery/light))
+ return TRUE
+ return FALSE
+
+/datum/action/cooldown/bloodsucker/targeted/lasombra/FireTargetedPower(atom/target_atom)
+ . = ..()
+ if(istype(target_atom, /obj/machinery/light))
+ for(var/obj/machinery/light/light_bulbs in range(5, target_atom)) //break nearby lights
+ light_bulbs.on = TRUE
+ light_bulbs.break_light_tube()
+
+ if(isliving(target_atom))
+ var/mob/living/L = target_atom
+ if(isethereal(L))
+ L.emp_act(EMP_LIGHT)
+ for(var/obj/item/O in L.get_all_contents())
+ if(O.light_range && O.light_power)
+ disintegrate(O)
+ if(L.pulling && L.pulling.light_range && isitem(L.pulling))
+ disintegrate(L.pulling)
+
+ if(istype(target_atom, /obj/machinery/door))
+ var/obj/structure/window/shadow/full/friend = new /obj/structure/window/shadow(get_turf(target_atom))
+ QDEL_IN(friend, (2 + level_current) SECONDS)
+ target_atom.visible_message(span_warning("Shadows leap at the door, blocking it!"))
+
+ if(isopenturf(target_atom))
+ var/set_direction = get_dir(owner, target_atom)
+ var/obj/structure/window/shadow/friend = new /obj/structure/window/shadow(get_turf(target_atom))
+ friend.dir = set_direction
+ QDEL_IN(friend, (3 + level_current) SECONDS)
+ target_atom.visible_message(span_warning("Shadows harden into a translucent wall, blocking passage!"))
+
+/datum/action/cooldown/bloodsucker/targeted/lasombra/proc/disintegrate(obj/item/O)
+ if(istype(O, /obj/item/pda))
+ var/obj/item/pda/PDA = O
+ PDA.set_light_on(FALSE)
+ PDA.update_icon()
+ O.visible_message(span_danger("The light in [PDA] shorts out!"))
+ else
+ O.visible_message(span_danger("[O] is disintegrated by [src]!"))
+ O.burn()
+ playsound(src, 'sound/items/welder.ogg', 50, 1)
+
+/obj/structure/window/shadow
+ name = "shadow barrier"
+ desc = "Basic shadow fence meant to stop idiots like you from passing."
+ icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ icon_state = "shadowfence"
+ can_be_unanchored = FALSE
+
+/obj/structure/window/shadow/can_be_rotated()
+ return FALSE
+
+/obj/structure/window/shadow/full
+ name = "shadow barrier" //original
+ desc = "Not a window."
+ icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ icon_state = "shadowlock"
+ fulltile = TRUE
diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/lunge.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/lunge.dm
index e149a05eab46..49647ab98870 100644
--- a/code/modules/antagonists/bloodsuckers/powers/targeted/lunge.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/targeted/lunge.dm
@@ -1,22 +1,28 @@
-/datum/action/bloodsucker/targeted/lunge
+/datum/action/cooldown/bloodsucker/targeted/lunge
name = "Predatory Lunge"
- desc = "Spring at a humanoid to grapple them without warning, or tear the dead's heart out. Attacks from concealment or the rear may even knock them down if strong enough."
+ desc = "Spring at your target to grapple them without warning, or tear the dead's heart out. Attacks from concealment or the rear may even knock them down if strong enough."
button_icon_state = "power_lunge"
- power_explanation = "Predatory Lunge:\n\
- Click any player to instantly dash at them if at level 2 or more, aggressively grabbing them.\n\
- If below level 2, you will have to charge your lunge for a while. During this time you'll have to stand still for lunge to work.\n\
- You cannot use the Power if you are aggressively grabbed.\n\
- If the target is wearing riot gear or is a Monster Hunter, you will merely passively grab them.\n\
- If grabbed from behind or from the darkness (Cloak of Darkness counts) with a power level at or above 4, will additionally knock the target down.\n\
- Higher levels will increase the knockdown dealt to enemies."
- power_flags = BP_AM_TOGGLE
+ power_explanation = "Predatory Lunge:\n\
+ Click any player to start spinning wildly and, after a short delay, dash at them.\n\
+ When lunging at someone, you will grab them, immediately starting off at aggressive.\n\
+ Riot gear and Monster Hunters are protected and will only be passively grabbed.\n\
+ You cannot use the Power if you are already grabbing someone, or are being grabbed.\n\
+ If you grab from behind, or from darkness (Cloak of Darkness works), you will knock the target down.\n\
+ If used on a dead body, will tear their heart out.\n\
+ Higher levels increase the knockdown dealt to enemies.\n\
+ At level 4, you will no longer spin, but you will be limited to tackling from only 6 tiles away."
+ power_flags = NONE
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 10
- cooldown = 10 SECONDS
- target_range = 6
+ cooldown_time = 10 SECONDS
power_activates_immediately = FALSE
- var/casting = FALSE //yogs - special snowflake!!!
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/upgrade_power()
+ . = ..()
+ //range is lowered when you get stronger.
+ if(level_current > 3)
+ target_range = 6
/*
* Level 1: Grapple level 2
@@ -24,24 +30,27 @@
* Level 3: Grapple 3 from Shadows
*/
-/datum/action/bloodsucker/targeted/lunge/CheckCanUse(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/targeted/lunge/CanUse(mob/living/carbon/user)
. = ..()
if(!.)
return FALSE
- /// Are we being grabbed?
+ // Are we being grabbed?
if(user.pulledby && user.pulledby.grab_state >= GRAB_AGGRESSIVE)
- to_chat(user, span_warning("You're being grabbed!"))
+ owner.balloon_alert(user, "grabbed!")
+ return FALSE
+ if(user.pulling)
+ owner.balloon_alert(user, "grabbing someone!")
return FALSE
return TRUE
/// Check: Are we lunging at a person?
-/datum/action/bloodsucker/targeted/lunge/CheckValidTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/lunge/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
return isliving(target_atom)
-/datum/action/bloodsucker/targeted/lunge/CheckCanTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/lunge/CheckCanTarget(atom/target_atom)
// Default Checks
. = ..()
if(!.)
@@ -57,80 +66,91 @@
return FALSE
return TRUE
-/datum/action/bloodsucker/targeted/lunge/FireTargetedPower(atom/target_atom)
- . = ..()
- var/mob/living/user = owner
- var/mob/living/carbon/target = target_atom
- var/turf/targeted_turf = get_turf(target)
+/datum/action/cooldown/bloodsucker/targeted/lunge/can_deactivate()
+ return !(datum_flags & DF_ISPROCESSING) //only if you aren't lunging
+/datum/action/cooldown/bloodsucker/targeted/lunge/FireTargetedPower(atom/target_atom)
+ . = ..()
owner.face_atom(target_atom)
- if(level_current < 2 && !prepare_target_lunge(target_atom))
- PowerActivatedSuccessfully()
+ if(level_current > 3)
+ do_lunge(target_atom)
return
- user.Immobilize(10 SECONDS)
- var/safety = get_dist(user, targeted_turf) * 3 + 1
- var/consequetive_failures = 0
- while(--safety && !target.Adjacent(user))
- if(!step_to(user, targeted_turf))
- consequetive_failures++
- if(consequetive_failures >= 3) // If 3 steps don't work, just stop.
- break
- lunge_end(target)
- PowerActivatedSuccessfully()
-/datum/action/bloodsucker/targeted/lunge/proc/prepare_target_lunge(atom/target_atom)
- casting = TRUE;
- to_chat(owner, span_notice("You prepare to lunge!"))
+ prepare_target_lunge(target_atom)
+ return TRUE
+
+///Starts processing the power and prepares the lunge by spinning, calls lunge at the end of it.
+/datum/action/cooldown/bloodsucker/targeted/lunge/proc/prepare_target_lunge(atom/target_atom)
+ START_PROCESSING(SSprocessing, src)
+ owner.balloon_alert(owner, "lunge started!")
//animate them shake
- var/base_x = owner.pixel_x
- var/base_y = owner.pixel_y
+ var/base_x = owner.base_pixel_x
+ var/base_y = owner.base_pixel_y
animate(owner, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS, loop = -1)
for(var/i in 1 to 25)
var/x_offset = base_x + rand(-3, 3)
var/y_offset = base_y + rand(-3, 3)
animate(pixel_x = x_offset, pixel_y = y_offset, time = 0.1 SECONDS)
- if(!do_after(owner, 4 SECONDS, target_atom, extra_checks = CALLBACK(src, PROC_REF(CheckCanTarget), target_atom)))
- animate(owner, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS)
- casting = FALSE
+ if(!do_after(owner, 4 SECONDS, stayStill = FALSE, extra_checks = CALLBACK(src, PROC_REF(CheckCanTarget), target_atom)))
+ end_target_lunge(base_x, base_y)
+
return FALSE
- animate(owner, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS)
- casting = FALSE
+
+ end_target_lunge()
+ do_lunge(target_atom)
return TRUE
-/datum/action/bloodsucker/targeted/lunge/process()
- ..()
- if(!casting) //snowflake code for cooldowns
- return
+///When preparing to lunge ends, this clears it up.
+/datum/action/cooldown/bloodsucker/targeted/lunge/proc/end_target_lunge(base_x, base_y)
+ animate(owner, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS)
+ STOP_PROCESSING(SSprocessing, src)
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/process()
+ if(!active) //If running SSfasprocess (on cooldown)
+ return ..() //Manage our cooldown timers
if(prob(75))
owner.spin(8, 1)
- owner.visible_message(
- span_warning("[owner] spins wildly!"),
- span_notice("You spin!"),
- )
+ owner.balloon_alert_to_viewers("spins wildly!", "you spin!")
return
do_smoke(0, owner.loc, smoke_type = /obj/effect/particle_effect/fluid/smoke/transparent)
-/datum/action/bloodsucker/targeted/lunge/proc/lunge_end(atom/hit_atom)
+///Actually lunges the target, then calls lunge end.
+/datum/action/cooldown/bloodsucker/targeted/lunge/proc/do_lunge(atom/hit_atom)
+ var/turf/targeted_turf = get_turf(hit_atom)
+
+ var/safety = get_dist(owner, targeted_turf) * 3 + 1
+ var/consequetive_failures = 0
+ while(--safety && !hit_atom.Adjacent(owner))
+ if(!step_to(owner, targeted_turf))
+ consequetive_failures++
+ if(consequetive_failures >= 3) // If 3 steps don't work, just stop.
+ break
+
+ lunge_end(hit_atom, targeted_turf)
+
+/datum/action/cooldown/bloodsucker/targeted/lunge/proc/lunge_end(atom/hit_atom, turf/target_turf)
+ power_activated_sucessfully()
+ // Am I next to my target to start giving the effects?
+ if(!owner.Adjacent(hit_atom))
+ return
+
var/mob/living/user = owner
var/mob/living/carbon/target = hit_atom
- var/turf/target_turf = get_turf(target)
-// Am I next to my target to start giving the effects?
- if(!user.Adjacent(target))
- return
+
// Did I slip or get knocked unconscious?
- if(!(user.mobility_flags & MOBILITY_STAND))
+ if(!(user.mobility_flags & MOBILITY_STAND) || user.incapacitated())
var/send_dir = get_dir(user, target_turf)
new /datum/forced_movement(user, get_ranged_target_turf(user, send_dir, 1), 1, FALSE)
user.spin(10)
return
// Is my target a Monster hunter?
var/mob/living/carbon/human/H = target
- if(IS_MONSTERHUNTER(target) || H.is_shove_knockdown_blocked())
- to_chat(owner, span_danger("You get pushed away!"))
- H.grabbedby(owner)
+ if(IS_MONSTERHUNTER(target) || H?.is_shove_knockdown_blocked())
+ owner.balloon_alert(owner, "you get pushed away!")
+ target.grabbedby(owner)
return
- to_chat(owner, span_danger("You lunge at [target]!"))
+ owner.balloon_alert(owner, "you lunge at [target]!")
if(target.stat == DEAD)
var/obj/item/bodypart/chest = target.get_bodypart(BODY_ZONE_CHEST)
var/datum/wound/slash/moderate/crit_wound = new
@@ -138,36 +158,36 @@
owner.visible_message(
span_warning("[owner] tears into [target]'s chest!"),
span_warning("You tear into [target]'s chest!"))
+
var/obj/item/organ/heart/myheart_now = locate() in target.internal_organs
if(myheart_now)
myheart_now.Remove(target)
user.put_in_hands(myheart_now)
- return
- //Grab now
- target.grabbedby(owner)
- target.grippedby(owner, instant = TRUE)
- // Did we knock them down?
- if(level_current >= 4 && (!is_A_facing_B(target, owner) || owner.alpha <= 40))
- target.Knockdown(10 + level_current * 5)
- target.Paralyze(0.1)
+ else
+ target.grabbedby(owner)
+ target.grippedby(owner, instant = TRUE)
+ // Did we knock them down?
+ if(!is_A_facing_B(target, owner) || owner.alpha <= 40)
+ target.Knockdown(10 + level_current * 5)
+ target.Paralyze(0.1)
-/datum/action/bloodsucker/targeted/lunge/DeactivatePower()
+/datum/action/cooldown/bloodsucker/targeted/lunge/DeactivatePower()
var/mob/living/O = owner
O.SetImmobilized(0)
return ..()
-/datum/action/bloodsucker/targeted/lunge/shadow
+/datum/action/cooldown/bloodsucker/targeted/lunge/shadow
name = "Dark Embrace"
+ background_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ active_background_icon_state = "lasombra_power_on"
+ base_background_icon_state = "lasombra_power_off"
button_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
- background_icon_state_on = "lasombra_power_on"
- background_icon_state_off = "lasombra_power_off"
- icon_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
button_icon_state = "power_embrace"
additional_text = "Additionally makes the target walk."
purchase_flags = LASOMBRA_CAN_BUY
-/datum/action/bloodsucker/targeted/lunge/shadow/lunge_end(atom/hit_atom)
+/datum/action/cooldown/bloodsucker/targeted/lunge/shadow/lunge_end(atom/hit_atom)
. = ..()
var/mob/living/carbon/target = hit_atom
if(target.m_intent != MOVE_INTENT_WALK)
diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/mesmerize.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/mesmerize.dm
index c6d414f09a69..94042bea06aa 100644
--- a/code/modules/antagonists/bloodsuckers/powers/targeted/mesmerize.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/targeted/mesmerize.dm
@@ -7,11 +7,11 @@
* Level 5: Doesn't need to be facing you anymore
*/
-/datum/action/bloodsucker/targeted/mesmerize
+/datum/action/cooldown/bloodsucker/targeted/mesmerize
name = "Mesmerize"
desc = "Dominate the mind of a mortal who can see your eyes."
button_icon_state = "power_mez"
- power_explanation = "Mesmerize:\n\
+ power_explanation = "Mesmerize:\n\
Click any player to attempt to mesmerize them. This process takes 5 seconds and will be interrupted on movement.\n\
You cannot wear anything covering your face, and both parties must be facing eachother. Obviously, both parties need to not be blind. \n\
If your target is already mesmerized or a Monster Hunter, the Power will fail.\n\
@@ -24,13 +24,13 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 30
- cooldown = 20 SECONDS
+ cooldown_time = 20 SECONDS
target_range = 8
power_activates_immediately = FALSE
prefire_message = "Whom will you subvert to your will?"
var/mesmerizingtime = 5 SECONDS
-/datum/action/bloodsucker/targeted/mesmerize/CheckCanUse(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/CanUse(mob/living/carbon/user)
. = ..()
if(!.) // Default checks
return FALSE
@@ -43,13 +43,13 @@
return FALSE
return TRUE
-/datum/action/bloodsucker/targeted/mesmerize/CheckValidTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
return isliving(target_atom)
-/datum/action/bloodsucker/targeted/mesmerize/CheckCanTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/CheckCanTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
@@ -84,7 +84,7 @@
return FALSE
return TRUE
-/datum/action/bloodsucker/targeted/mesmerize/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/FireTargetedPower(atom/target_atom)
. = ..()
var/mob/living/target = target_atom
var/mob/living/user = owner
@@ -93,7 +93,7 @@
if(!do_mob(user, target, mesmerizingtime, NONE, TRUE))
return
- PowerActivatedSuccessfully() // PAY COST! BEGIN COOLDOWN!
+ power_activated_sucessfully() // PAY COST! BEGIN COOLDOWN!
var/power_time = 90 + level_current * 15
if(IS_MONSTERHUNTER(target))
to_chat(target, span_warning("You feel your eyes burn for a while, but it passes."))
@@ -116,24 +116,24 @@
mesmerized.emp_act(EMP_HEAVY)
DeactivatePower()
-/datum/action/bloodsucker/targeted/mesmerize/proc/end_mesmerize(mob/living/user, mob/living/target)
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/proc/end_mesmerize(mob/living/user, mob/living/target)
target.notransform = FALSE
REMOVE_TRAIT(target, TRAIT_MUTE, BLOODSUCKER_TRAIT)
// They Woke Up! (Notice if within view)
if(istype(user) && target.stat == CONSCIOUS && (target in view(6, get_turf(user))))
to_chat(owner, span_warning("[target] snapped out of their trance."))
-/datum/action/bloodsucker/targeted/mesmerize/shadow
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/shadow
name = "Glare"
+ background_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ active_background_icon_state = "lasombra_power_on"
+ base_background_icon_state = "lasombra_power_off"
button_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
- background_icon_state_on = "lasombra_power_on"
- background_icon_state_off = "lasombra_power_off"
- icon_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
button_icon_state = "power_glare"
additional_text = "Additionally makes the stun downtime based on distance, being instant when adjacent."
purchase_flags = LASOMBRA_CAN_BUY
-/datum/action/bloodsucker/targeted/mesmerize/shadow/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/mesmerize/shadow/FireTargetedPower(atom/target_atom)
var/mob/living/target = target_atom
var/mob/living/user = owner
if(target.Adjacent(user))
@@ -141,4 +141,4 @@
else
mesmerizingtime = initial(mesmerizingtime) - ((-get_dist(target, user) + 8 )/2) SECONDS //won't screw you up that bad if you miss it barely
power_activates_immediately = FALSE
- . = ..()
+ return ..()
diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/trespass.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/trespass.dm
index a6b9dffc3182..b974d1701805 100644
--- a/code/modules/antagonists/bloodsuckers/powers/targeted/trespass.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/targeted/trespass.dm
@@ -1,8 +1,8 @@
-/datum/action/bloodsucker/targeted/trespass
+/datum/action/cooldown/bloodsucker/targeted/trespass
name = "Trespass"
desc = "Become mist and advance two tiles in one direction. Useful for skipping past doors and barricades."
button_icon_state = "power_tres"
- power_explanation = "Trespass:\n\
+ power_explanation = "Trespass:\n\
Click anywhere from 1-2 tiles away from you to teleport.\n\
This power goes through all obstacles except Walls.\n\
Higher levels decrease the sound played from using the Power, and increase the speed of the transition."
@@ -10,14 +10,14 @@
check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY
bloodcost = 10
- cooldown = 7 SECONDS
+ cooldown_time = 7 SECONDS
prefire_message = "Select a destination."
//target_range = 2
var/turf/target_turf // We need to decide where we're going based on where we clicked. It's not actually the tile we clicked.
var/wallbound = TRUE
var/soliddelay = 0.1 SECONDS
-/datum/action/bloodsucker/targeted/trespass/CheckCanUse(mob/living/carbon/user)
+/datum/action/cooldown/bloodsucker/targeted/trespass/CanUse(mob/living/carbon/user)
. = ..()
if(!.)
return FALSE
@@ -26,7 +26,7 @@
return TRUE
-/datum/action/bloodsucker/targeted/trespass/CheckValidTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/trespass/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
@@ -36,7 +36,7 @@
return TRUE // All we care about is destination. Anything you click is fine.
-/datum/action/bloodsucker/targeted/trespass/CheckCanTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/trespass/CheckCanTarget(atom/target_atom)
// NOTE: Do NOT use ..()! We don't want to check distance or anything.
// Get clicked tile
@@ -64,7 +64,7 @@
return TRUE
-/datum/action/bloodsucker/targeted/trespass/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/trespass/FireTargetedPower(atom/target_atom)
. = ..()
// Find target turf, at or below Atom
@@ -80,8 +80,7 @@
var/sound_strength = max(60, 70 - level_current * 10)
var/sound_dist = max(-6, -level_current) // world.view is 7, so 7-6 = hearing distance of 1
playsound(get_turf(owner), 'sound/magic/summon_karp.ogg', sound_strength, 1, sound_dist)
- var/datum/effect_system/steam_spread/puff = new /datum/effect_system/steam_spread/()
- puff.effect_type = /obj/effect/particle_effect/fluid/smoke/vampsmoke
+ var/datum/effect_system/steam_spread/bloodsucker/puff = new /datum/effect_system/steam_spread()
puff.set_up(3, 0, my_turf)
puff.start()
@@ -114,12 +113,12 @@
puff.set_up(3, 0, target_turf)
puff.start()
-/datum/action/bloodsucker/targeted/trespass/shadow
+/datum/action/cooldown/bloodsucker/targeted/trespass/shadow
name = "Manifest"
+ background_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
+ active_background_icon_state = "lasombra_power_on"
+ base_background_icon_state = "lasombra_power_off"
button_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
- background_icon_state_on = "lasombra_power_on"
- background_icon_state_off = "lasombra_power_off"
- icon_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi'
button_icon_state = "power_manifest"
additional_text = "Additionally allows you pass through walls, albeit at a slower rate."
purchase_flags = LASOMBRA_CAN_BUY
diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/tzimisce.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/tzimisce.dm
index fce59c5257d2..69516a65538c 100644
--- a/code/modules/antagonists/bloodsuckers/powers/targeted/tzimisce.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/targeted/tzimisce.dm
@@ -2,27 +2,26 @@
#define SIZE_MEDIUM 2
#define SIZE_BIG 4
-/datum/action/bloodsucker/targeted/dice
+/datum/action/cooldown/bloodsucker/targeted/dice
name = "Dice"
desc = "Slice, cut, sever. The Flesh obeys as my fingers lay touch on it."
+ background_icon = 'icons/mob/actions/actions_tzimisce_bloodsucker.dmi'
+ button_icon = 'icons/mob/actions/actions_tzimisce_bloodsucker.dmi'
+ background_icon_state = "tzimisce_power_off"
+ active_background_icon_state = "tzimisce_power_on"
+ base_background_icon_state = "tzimisce_power_off"
button_icon_state = "power_dice"
- power_explanation = "Dice:\n\
+ power_explanation = "Dice:\n\
Use on a dead corpse to extract muscle from it to be able to feed it to a vassalrack.\n\
This won't take long and is your primary source of muscle acquiring, necessary for future endeavours.\n\
This ability takes well to leveling up, higher levels will increase your mastery over a person's flesh while using the ability for it's combat purpose.\n\
You shouldn't use this on your allies.."
power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN
bloodcost = 10
- button_icon = 'icons/mob/actions/actions_tzimisce_bloodsucker.dmi'
- icon_icon = 'icons/mob/actions/actions_tzimisce_bloodsucker.dmi'
- background_icon_state = "tzimisce_power_off"
- background_icon_state_on = "tzimisce_power_on"
- background_icon_state_off = "tzimisce_power_off"
purchase_flags = TZIMISCE_CAN_BUY
- power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN
check_flags = BP_AM_COSTLESS_UNCONSCIOUS
target_range = 1
- cooldown = 10 SECONDS
+ cooldown_time = 10 SECONDS
/obj/item/muscle
name = "muscle"
@@ -54,14 +53,14 @@
/obj/item/muscle/examine(mob/user)
. = ..()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(IS_BLOODSUCKER(user) && bloodsuckerdatum.my_clan == CLAN_TZIMISCE)
- . += span_cult("By looking at it you comprehend that it would yield [size] points for ritual usage.")
+ if(IS_BLOODSUCKER(user) && bloodsuckerdatum.my_clan?.control_type == BLOODSUCKER_CONTROL_FLESH)
+ . += span_cult("It will yield [size] points for ritual usage.")
/obj/item/muscle/attackby(obj/item/I, mob/user, params) // handles muscle crafting
var/newsize = 0
var/quantity = 1
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(!(IS_BLOODSUCKER(user) && bloodsuckerdatum.my_clan == CLAN_TZIMISCE))
+ if(!(IS_BLOODSUCKER(user) && bloodsuckerdatum.my_clan?.control_type == BLOODSUCKER_CONTROL_FLESH))
return
if(istype(I, /obj/item/muscle))
var/obj/item/muscle/muscle2 = I
@@ -99,7 +98,7 @@
new /obj/item/muscle/big(user.drop_location())
qdel(src)
-/datum/action/bloodsucker/targeted/dice/FireTargetedPower(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/dice/FireTargetedPower(atom/target_atom)
var/mob/living/target = target_atom
var/mob/living/carbon/user = owner
user.face_atom(target)
@@ -164,13 +163,13 @@
target.gib()
new /obj/item/muscle/medium(target.loc)
-/datum/action/bloodsucker/targeted/dice/CheckValidTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/dice/CheckValidTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
return isliving(target_atom)
-/datum/action/bloodsucker/targeted/dice/CheckCanTarget(atom/target_atom)
+/datum/action/cooldown/bloodsucker/targeted/dice/CheckCanTarget(atom/target_atom)
. = ..()
if(!.)
return FALSE
@@ -180,4 +179,4 @@
#undef SIZE_SMALL
#undef SIZE_MEDIUM
-#undef SIZE_BIG
\ No newline at end of file
+#undef SIZE_BIG
diff --git a/code/modules/antagonists/bloodsuckers/powers/vassal_fold.dm b/code/modules/antagonists/bloodsuckers/powers/vassal_fold.dm
new file mode 100644
index 000000000000..d71b419def6e
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/powers/vassal_fold.dm
@@ -0,0 +1,91 @@
+/datum/action/cooldown/bloodsucker/vassal_blood
+ name = "Help Vassal"
+ desc = "Bring an ex-Vassal back into the fold."
+ button_icon_state = "power_torpor"
+ power_explanation = "Help Vassal:\n\
+ Use this power while you have an ex-Vassal grabbed to bring them back into the fold. \
+ Right-Click will show the status of all Vassals."
+ power_flags = NONE
+ check_flags = NONE
+ purchase_flags = NONE
+ bloodcost = 10
+ cooldown_time = 10 SECONDS
+
+ ///Bloodbag we have in our hands.
+ var/obj/item/reagent_containers/blood/bloodbag
+ ///Weakref to a target we're bringing into the fold.
+ var/datum/weakref/target_ref
+ ///If we are analyze or helping
+ var/analyzing
+
+/datum/action/cooldown/bloodsucker/vassal_blood/CanUse(mob/living/carbon/user)
+ . = ..()
+ if(!.)
+ return FALSE
+ var/datum/antagonist/vassal/revenge/revenge_vassal = owner.mind.has_antag_datum(/datum/antagonist/ex_vassal)
+ if(revenge_vassal)
+ return FALSE
+
+ if(tgui_alert(owner, "What would you like to do?", "Lost and Found", list("Analyze", "Help")) == "Analyze")
+ if(!revenge_vassal.ex_vassals.len)
+ owner.balloon_alert(owner, "no vassals!")
+ return FALSE
+ analyzing = TRUE
+ return TRUE
+
+ if(owner.pulling && isliving(owner.pulling))
+ var/mob/living/pulled_target = owner.pulling
+ var/datum/antagonist/ex_vassal/former_vassal = pulled_target.mind.has_antag_datum(/datum/antagonist/ex_vassal)
+ if(!former_vassal)
+ owner.balloon_alert(owner, "not a former vassal!")
+ return FALSE
+ target_ref = WEAKREF(owner.pulling)
+ return TRUE
+
+ var/blood_bag = locate(/obj/item/reagent_containers/blood) in user.held_items
+ if(!blood_bag)
+ owner.balloon_alert(owner, "blood bag needed!")
+ return FALSE
+ if(istype(blood_bag, /obj/item/reagent_containers/blood/o_minus/bloodsucker))
+ owner.balloon_alert(owner, "already bloodsucker blood!")
+
+ bloodbag = blood_bag
+ return TRUE
+
+/datum/action/cooldown/bloodsucker/vassal_blood/ActivatePower()
+ . = ..()
+ var/datum/antagonist/vassal/revenge/revenge_vassal = owner.mind.has_antag_datum(/datum/antagonist/vassal/revenge)
+ if(analyzing)
+ for(var/datum/antagonist/ex_vassal/former_vassals as anything in revenge_vassal.ex_vassals)
+ var/information = "[former_vassals.owner.current]"
+ information += " - has [round(COOLDOWN_TIMELEFT(former_vassals, blood_timer) / 600)] minutes left of Blood"
+ var/turf/open/floor/target_area = get_area(owner)
+ if(target_area)
+ information += " - currently at [target_area]."
+ if(former_vassals.owner.current.stat >= DEAD)
+ information += " - DEAD."
+
+ to_chat(owner, "[information]")
+
+ DeactivatePower()
+ return
+
+ if(target_ref)
+ var/mob/living/target = target_ref.resolve()
+ var/datum/antagonist/ex_vassal/former_vassal = target.mind.has_antag_datum(/datum/antagonist/ex_vassal)
+ if(!former_vassal || former_vassal.revenge_vassal)
+ target_ref = null
+ return
+ if(do_after(owner, 5 SECONDS, target))
+ former_vassal.return_to_fold(revenge_vassal)
+ target_ref = null
+ DeactivatePower()
+ return
+
+ if(bloodbag)
+ var/mob/living/living_owner = owner
+ living_owner.blood_volume -= 150
+ QDEL_NULL(bloodbag)
+ var/obj/item/reagent_containers/blood/o_minus/bloodsucker/new_bag = new(owner.loc)
+ owner.put_in_active_hand(new_bag)
+ DeactivatePower()
diff --git a/code/modules/antagonists/bloodsuckers/powers/veil.dm b/code/modules/antagonists/bloodsuckers/powers/veil.dm
index de1b1b0ab183..6339c00ec4c9 100644
--- a/code/modules/antagonists/bloodsuckers/powers/veil.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/veil.dm
@@ -1,17 +1,17 @@
-/datum/action/bloodsucker/veil
+/datum/action/cooldown/bloodsucker/veil
name = "Veil of Many Faces"
desc = "Disguise yourself in the illusion of another identity."
button_icon_state = "power_veil"
- power_explanation = "Veil of Many Faces:\n\
+ power_explanation = "Veil of Many Faces:\n\
Activating Veil of Many Faces will shroud you in smoke and forge you a new identity.\n\
Your name and appearance will be completely randomized, and turning the ability off again will undo it all.\n\
Clothes, gear, and Security/Medical HUD status is kept the same while this power is active."
power_flags = BP_AM_TOGGLE
check_flags = BP_CANT_USE_IN_FRENZY
- purchase_flags = VASSAL_CAN_BUY|BLOODSUCKER_CAN_BUY
+ purchase_flags = VASSAL_CAN_BUY|BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER
bloodcost = 15
constant_bloodcost = 0.1
- cooldown = 10 SECONDS
+ cooldown_time = 10 SECONDS
// Outfit Vars
// var/list/original_items = list()
// Identity Vars
@@ -27,20 +27,21 @@
var/prev_disfigured
var/list/prev_features // For lizards and such
-/datum/action/bloodsucker/veil/ActivatePower()
+/datum/action/cooldown/bloodsucker/veil/ActivatePower()
. = ..()
cast_effect() // POOF
// if(blahblahblah)
// Disguise_Outfit()
veil_user()
+ owner.balloon_alert(owner, "veil turned on.")
/* // Meant to disguise your character's clothing into fake ones.
-/datum/action/bloodsucker/veil/proc/Disguise_Outfit()
+/datum/action/cooldown/bloodsucker/veil/proc/Disguise_Outfit()
return
// Step One: Back up original items
*/
-/datum/action/bloodsucker/veil/proc/veil_user()
+/datum/action/cooldown/bloodsucker/veil/proc/veil_user()
// Change Name/Voice
var/mob/living/carbon/human/user = owner
user.name_override = user.dna.species.random_name(user.gender)
@@ -83,7 +84,7 @@
user.update_hair()
user.update_body_parts()
-/datum/action/bloodsucker/veil/DeactivatePower()
+/datum/action/cooldown/bloodsucker/veil/DeactivatePower()
. = ..()
if(!ishuman(owner))
return
@@ -117,12 +118,13 @@
user.update_body_parts() // Body itself, maybe skin color?
cast_effect() // POOF
+ owner.balloon_alert(owner, "veil turned off.")
// CAST EFFECT // General effect (poof, splat, etc) when you cast. Doesn't happen automatically!
-/datum/action/bloodsucker/veil/proc/cast_effect()
+/datum/action/cooldown/bloodsucker/veil/proc/cast_effect()
// Effect
- playsound(get_turf(owner), 'sound/magic/smoke.ogg', 20, 1)
+ playsound(get_turf(owner), 'sound/magic/smoke.ogg', 20, TRUE)
var/datum/effect_system/steam_spread/puff = new /datum/effect_system/steam_spread/()
puff.effect_type = /obj/effect/particle_effect/fluid/smoke/vampsmoke
puff.set_up(3, 0, get_turf(owner))
diff --git a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_coffin.dm b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_coffin.dm
index af70eb1406cb..466683d15ec6 100644
--- a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_coffin.dm
+++ b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_coffin.dm
@@ -1,4 +1,4 @@
-/datum/antagonist/bloodsucker/proc/ClaimCoffin(obj/structure/closet/crate/claimed)
+/datum/antagonist/bloodsucker/proc/claim_coffin(obj/structure/closet/crate/claimed, area/current_area)
// ALREADY CLAIMED
if(claimed.resident)
if(claimed.resident == owner.current)
@@ -6,6 +6,12 @@
else
to_chat(owner, "This [claimed.name] has already been claimed by another.")
return FALSE
+ if(!LAZYFIND(GLOB.the_station_areas, current_area))
+ claimed.balloon_alert(owner.current, "not part of station!")
+ return
+ // This is my Lair
+ coffin = claimed
+ bloodsucker_lair_area = current_area
if(!(/datum/crafting_recipe/vassalrack in owner?.learned_recipes))
owner.teach_crafting_recipe(/datum/crafting_recipe/vassalrack)
owner.teach_crafting_recipe(/datum/crafting_recipe/candelabrum)
@@ -13,22 +19,13 @@
owner.teach_crafting_recipe(/datum/crafting_recipe/meatcoffin)
owner.teach_crafting_recipe(/datum/crafting_recipe/staketrap)
owner.teach_crafting_recipe(/datum/crafting_recipe/woodenducky)
- if(my_clan != CLAN_TZIMISCE) // better things to do
+ if(my_clan?.get_clan() != CLAN_TZIMISCE) // better things to do
owner.teach_crafting_recipe(/datum/crafting_recipe/bloodaltar)
to_chat(owner, span_danger("You learned new recipes - You can view them in the Structure and Weaponry section of the crafting menu!"))
- // This is my Lair
- coffin = claimed
- lair = get_area(claimed)
- to_chat(owner, span_userdanger("You have claimed the [claimed] as your place of immortal rest! Your lair is now [lair]."))
+ to_chat(owner, span_userdanger("You have claimed the [claimed] as your place of immortal rest! Your lair is now [bloodsucker_lair_area]."))
to_chat(owner, span_announce("Bloodsucker Tip: Find new lair recipes in the structure tab of the Crafting Menu, including the Persuasion Rack for converting crew into Vassals and the Blood Altar which lets you gain two tasks per night to Rank Up."))
return TRUE
-/// From crate.dm
-/obj/structure/closet/crate
- var/mob/living/resident /// This lets bloodsuckers claim any "crate" as a Coffin.
- var/pryLidTimer = 25 SECONDS
- breakout_time = 20 SECONDS
-
/obj/structure/closet/crate/coffin/examine(mob/user)
. = ..()
if(user == resident)
@@ -43,11 +40,11 @@
icon_state = "coffin"
icon = 'icons/obj/vamp_obj.dmi'
breakout_time = 30 SECONDS
- pryLidTimer = 20 SECONDS
+ pry_lid_timer = 20 SECONDS
resistance_flags = NONE
material_drop = /obj/item/stack/sheet/metal
material_drop_amount = 2
- armor = list("melee" = 50, "bullet" = 20, "laser" = 30, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
+ armor = list(MELEE = 50, BULLET = 20, LASER = 30, ENERGY = 0, BOMB = 50, BIO = 0, RAD = 0, FIRE = 70, ACID = 60)
/obj/structure/closet/crate/coffin/securecoffin
name = "secure coffin"
@@ -57,11 +54,11 @@
open_sound = 'sound/effects/coffin_open.ogg'
close_sound = 'sound/effects/coffin_close.ogg'
breakout_time = 35 SECONDS
- pryLidTimer = 35 SECONDS
+ pry_lid_timer = 35 SECONDS
resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF
material_drop = /obj/item/stack/sheet/metal
material_drop_amount = 2
- armor = list("melee" = 35, "bullet" = 20, "laser" = 20, "energy" = 0, "bomb" = 100, "bio" = 0, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list(MELEE = 35, BULLET = 20, LASER = 20, ENERGY = 0, BOMB = 100, BIO = 0, RAD = 100, FIRE = 100, ACID = 100)
/obj/structure/closet/crate/coffin/meatcoffin
name = "meat coffin"
@@ -72,10 +69,10 @@
open_sound = 'sound/effects/footstep/slime1.ogg'
close_sound = 'sound/effects/footstep/slime1.ogg'
breakout_time = 25 SECONDS
- pryLidTimer = 20 SECONDS
+ pry_lid_timer = 20 SECONDS
material_drop = /obj/item/reagent_containers/food/snacks/meat/slab
material_drop_amount = 3
- armor = list("melee" = 70, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
+ armor = list(MELEE = 70, BULLET = 10, LASER = 10, ENERGY = 0, BOMB = 70, BIO = 0, RAD = 0, FIRE = 70, ACID = 60)
/obj/structure/closet/crate/coffin/metalcoffin
name = "metal coffin"
@@ -86,25 +83,31 @@
open_sound = 'sound/effects/pressureplate.ogg'
close_sound = 'sound/effects/pressureplate.ogg'
breakout_time = 25 SECONDS
- pryLidTimer = 30 SECONDS
+ pry_lid_timer = 30 SECONDS
material_drop = /obj/item/stack/sheet/metal
- armor = list("melee" = 40, "bullet" = 15, "laser" = 50, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 50, "fire" = 70, "acid" = 60)
-
+ armor = list(MELEE = 40, BULLET = 15, LASER = 50, ENERGY = 0, BOMB = 10, BIO = 0, RAD = 50, FIRE = 70, ACID = 60)
+
//////////////////////////////////////////////
/// NOTE: This can be any coffin that you are resting AND inside of.
-/obj/structure/closet/crate/coffin/proc/ClaimCoffin(mob/living/claimant)
- // Bloodsucker Claim
+/obj/structure/closet/crate/coffin/proc/claim_coffin(mob/living/claimant, area/current_area)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = claimant.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(bloodsuckerdatum)
- // Successfully claimed?
- if(bloodsuckerdatum.ClaimCoffin(src))
- resident = claimant
- anchored = TRUE
- START_PROCESSING(SSprocessing, src)
+ // Successfully claimed?
+ if(bloodsuckerdatum?.claim_coffin(src, current_area))
+ resident = claimant
+ anchored = TRUE
+ START_PROCESSING(SSprocessing, src)
+
+/obj/structure/closet/crate/coffin/examine(mob/user)
+ . = ..()
+ if(user == resident)
+ . += span_cult("This is your Claimed Coffin.")
+ . += span_cult("Rest in it while injured to enter Torpor. Entering it with unspent Ranks will allow you to spend one.")
+ . += span_cult("Alt-Click while inside the Coffin to Lock/Unlock.")
+ . += span_cult("Alt-Click while outside of your Coffin to Unclaim it, unwrenching it and all your other structures as a result.")
/obj/structure/closet/crate/coffin/Destroy()
- UnclaimCoffin()
+ unclaim_coffin()
STOP_PROCESSING(SSprocessing, src)
return ..()
@@ -113,19 +116,11 @@
if(!.)
return FALSE
if(user in src)
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(!bloodsuckerdatum)
- return FALSE
- if(bloodsuckerdatum.lair != get_area(bloodsuckerdatum.coffin))
- if(bloodsuckerdatum.coffin)
- bloodsuckerdatum.coffin.UnclaimCoffin()
- var/list/turf/area_turfs = get_area_turfs(bloodsuckerdatum.lair)
+ var/list/turf/area_turfs = get_area_turfs(get_area(src))
// Create Dirt etc.
var/turf/T_Dirty = pick(area_turfs)
if(T_Dirty && !T_Dirty.density)
// Default: Dirt
- // CHECK: Cobweb already there?
- //if (!locate(var/obj/effect/decal/cleanable/cobweb) in T_Dirty) // REMOVED! Cleanables don't stack.
// STEP ONE: COBWEBS
// CHECK: Wall to North?
var/turf/check_N = get_step(T_Dirty, NORTH)
@@ -133,37 +128,14 @@
// CHECK: Wall to West?
var/turf/check_W = get_step(T_Dirty, WEST)
if(istype(check_W, /turf/closed/wall))
- new /obj/effect/decal/cleanable/cobweb (T_Dirty)
+ new /obj/effect/decal/cleanable/cobweb(T_Dirty)
// CHECK: Wall to East?
var/turf/check_E = get_step(T_Dirty, EAST)
if(istype(check_E, /turf/closed/wall))
- new /obj/effect/decal/cleanable/cobweb/cobweb2 (T_Dirty)
- // STEP TWO: DIRT
- new /obj/effect/decal/cleanable/dirt (T_Dirty)
- // Find Animals in Area
-/* if(rand(0,2) == 0)
- var/mobCount = 0
- var/mobMax = clamp(area_turfs.len / 25, 1, 4)
- for(var/turf/lair_turfs in area_turfs)
- if(!lair_turfs)
- continue
- var/mob/living/simple_animal/SA = locate() in lair_turfs
- if(SA)
- mobCount++
- if(mobCount >= mobMax) // Already at max
- break
- Spawn One
- if(mobCount < mobMax)
-// Seek Out Location
- while(area_turfs.len > 0)
- var/turf/lair_turfs = pick(area_turfs) // We use while&pick instead of a for/loop so it's random, rather than from the top of the list.
- if(lair_turfs && !lair_turfs.density)
- var/mob/living/simple_animal/selected_simplemob = /mob/living/simple_animal/mouse // pick(/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse, /mob/living/simple_animal/hostile/retaliate/bat) //prob(300) /mob/living/simple_animal/mouse,
- new selected_simplemob(lair_turfs)
- break
- area_turfs -= lair_turfs*/
+ new /obj/effect/decal/cleanable/cobweb/cobweb2(T_Dirty)
+ new /obj/effect/decal/cleanable/dirt(T_Dirty)
-/obj/structure/closet/crate/proc/UnclaimCoffin(manual = FALSE)
+/obj/structure/closet/crate/proc/unclaim_coffin(manual = FALSE)
// Unanchor it (If it hasn't been broken, anyway)
anchored = FALSE
if(!resident || !resident.mind)
@@ -172,7 +144,7 @@
var/datum/antagonist/bloodsucker/bloodsuckerdatum = resident.mind.has_antag_datum(/datum/antagonist/bloodsucker)
if(bloodsuckerdatum && bloodsuckerdatum.coffin == src)
bloodsuckerdatum.coffin = null
- bloodsuckerdatum.lair = null
+ bloodsuckerdatum.bloodsucker_lair_area = null
for(var/obj/structure/bloodsucker/bloodsucker_structure in get_area(src))
if(bloodsucker_structure.owner == resident)
bloodsucker_structure.unbolt()
@@ -205,29 +177,38 @@
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
if(!bloodsuckerdatum)
return FALSE
+ var/area/current_area = get_area(src)
if(!bloodsuckerdatum.coffin && !resident)
- switch(input("Do you wish to claim this as your coffin? [get_area(src)] will be your lair, and you will learn to craft new structures.","Claim Lair") in list("Yes", "No"))
+ switch(tgui_alert(user, "Do you wish to claim this as your coffin? [current_area] will be your lair.", "Claim Lair", list("Yes", "No")))
if("Yes")
- ClaimCoffin(user)
- LockMe(user)
- bloodsuckerdatum.SpendRank()
+ claim_coffin(user, current_area)
+ if("No")
+ return
+ LockMe(user)
+ //Level up if possible.
+ if(!bloodsuckerdatum.my_clan)
+ to_chat(user, span_notice("You must enter a Clan to rank up."))
+ return
+ if(bloodsuckerdatum.my_clan.rank_up_type == BLOODSUCKER_RANK_UP_NORMAL)
+ bloodsuckerdatum.SpendRank()
/// You're in a Coffin, everything else is done, you're likely here to heal. Let's offer them the oppertunity to do so.
- bloodsuckerdatum.Check_Begin_Torpor()
+ bloodsuckerdatum.check_begin_torpor()
return TRUE
/// You cannot weld or deconstruct an owned coffin. Only the owner can destroy their own coffin.
/obj/structure/closet/crate/coffin/attackby(obj/item/item, mob/user, params)
- if(resident)
- if(user != resident)
- if(istype(item, cutting_tool))
- to_chat(user, span_notice("This is a much more complex mechanical structure than you thought. You don't know where to begin cutting [src]."))
- return
- if(anchored && istype(item, /obj/item/wrench))
- to_chat(user, span_danger("The coffin won't come unanchored from the floor.[user == resident ? " You can Alt Click to unclaim and unwrench your Coffin." : ""]"))
+ if(!resident)
+ return ..()
+ if(user != resident)
+ if(istype(item, cutting_tool))
+ to_chat(user, span_notice("This is a much more complex mechanical structure than you thought. You don't know where to begin cutting [src]."))
return
+ if(anchored && (item.tool_behaviour == TOOL_WRENCH))
+ to_chat(user, span_danger("The coffin won't come unanchored from the floor.[user == resident ? " You can Alt-Click to unclaim and unwrench your Coffin." : ""]"))
+ return
- if(locked && istype(item, /obj/item/crowbar))
- var/pry_time = pryLidTimer * item.toolspeed // Pry speed must be affected by the speed of the tool.
+ if(locked && (item.tool_behaviour == TOOL_CROWBAR))
+ var/pry_time = pry_lid_timer * item.toolspeed // Pry speed must be affected by the speed of the tool.
user.visible_message(
span_notice("[user] tries to pry the lid off of [src] with [item]."),
span_notice("You begin prying the lid off of [src] with [item]. This should take about [DisplayTimeText(pry_time)]."))
@@ -248,13 +229,15 @@
return
if(user == resident && user.Adjacent(src))
- switch(input("Do you wish to unclaim your coffin?","Unclaim Lair") in list("Yes", "No"))
+ balloon_alert(user, "unclaim coffin?")
+ var/static/list/unclaim_options = list(
+ "Yes" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_yes"),
+ "No" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_no"))
+ var/unclaim_response = show_radial_menu(user, src, unclaim_options, radius = 36, require_near = TRUE)
+ switch(unclaim_response)
if("Yes")
- UnclaimCoffin(TRUE)
- LockMe(user)
- if("No")
- return
- return TRUE
+ unclaim_coffin(TRUE)
+
/obj/structure/closet/crate/proc/LockMe(mob/user, inLocked = TRUE)
if(user == resident)
if(!broken)
diff --git a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_crypt.dm b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_crypt.dm
index 27964f36ffa9..c2febc3cc6a3 100644
--- a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_crypt.dm
+++ b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_crypt.dm
@@ -43,7 +43,7 @@
/// If a Bloodsucker tries to wrench it in place, yell at them.
if(item.tool_behaviour == TOOL_WRENCH && !anchored && IS_BLOODSUCKER(user))
user.playsound_local(null, 'sound/machines/buzz-sigh.ogg', 40, FALSE, pressure_affected = FALSE)
- to_chat(user, span_announce("* Bloodsucker Tip: Examine the Persuasion Rack to understand how it functions!"))
+ to_chat(user, span_announce("* Bloodsucker Tip: Examine Bloodsucker structures to understand how they function!"))
return
. = ..()
@@ -52,15 +52,22 @@
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
/// Claiming the Rack instead of using it?
if(istype(bloodsuckerdatum) && !owner)
- if(!bloodsuckerdatum.lair)
+ if(!bloodsuckerdatum.bloodsucker_lair_area)
to_chat(user, span_danger("You don't have a lair. Claim a coffin to make that location your lair."))
return FALSE
- if(bloodsuckerdatum.lair != get_area(src))
- to_chat(user, span_danger("You may only activate this structure in your lair: [bloodsuckerdatum.lair]."))
+ if(bloodsuckerdatum.bloodsucker_lair_area != get_area(src))
+ to_chat(user, span_danger("You may only activate this structure in your lair: [bloodsuckerdatum.bloodsucker_lair_area]."))
return FALSE
- /// Menu for securing your Persuasion rack in place.
- switch(input("Do you wish to secure [src] here?") in list("Yes", "No"))
+ /// Radial menu for securing your Persuasion rack in place.
+ to_chat(user, span_notice("Do you wish to secure [src] here?"))
+ var/static/list/secure_options = list(
+ "Yes" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_yes"),
+ "No" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_no"))
+ var/secure_response = show_radial_menu(user, src, secure_options, radius = 36, require_near = TRUE)
+ if(!secure_response)
+ return FALSE
+ switch(secure_response)
if("Yes")
user.playsound_local(null, 'sound/items/ratchet.ogg', 70, FALSE, pressure_affected = FALSE)
bolt(user)
@@ -71,7 +78,13 @@
/obj/structure/bloodsucker/AltClick(mob/user)
. = ..()
if(user == owner && user.Adjacent(src))
- switch(input("Unbolt [src]?") in list("Yes", "No"))
+ balloon_alert(user, "unbolt [src]?")
+ var/static/list/unclaim_options = list(
+ "Yes" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_yes"),
+ "No" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_no")
+ )
+ var/unclaim_response = show_radial_menu(user, src, unclaim_options, radius = 36, require_near = TRUE)
+ switch(unclaim_response)
if("Yes")
unbolt(user)
@@ -87,10 +100,11 @@
climbable = TRUE
pass_flags = LETPASSTHROW
can_buckle = FALSE
- var/task_completed = FALSE
var/sacrifices = 0
var/sacrificialtask = FALSE
var/organ_name = ""
+ var/suckamount = 0
+ var/heartamount = 0
Ghost_desc = "This is a Blood Altar, where bloodsuckers can get two tasks per night to get more ranks."
Vamp_desc = "This is a Blood Altar, which allows you to do two tasks per day to advance your ranks.\n\
Interact with the Altar by clicking on it after it's bolted to get a task.\n\
@@ -113,18 +127,29 @@
/obj/structure/bloodsucker/bloodaltar/attack_hand(mob/user, list/modifiers)
. = ..()
if(!.)
- return FALSE
- if(!IS_BLOODSUCKER(user))
+ return
+ if(!IS_BLOODSUCKER(user)) //not bloodsucker
to_chat(user, span_warning("You can't figure out how this works."))
- return FALSE
+ return
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(bloodsuckerdatum.altar_uses >= ALTAR_RANKS_PER_DAY)
+ if(bloodsuckerdatum.altar_uses >= ALTAR_RANKS_PER_DAY) //used the altar already
to_chat(user, span_notice("You have done all tasks for the night, come back tomorrow for more."))
return
+ if(!check_completion(user) && bloodsuckerdatum.current_task) //not done but has a task? put them on their way
+ to_chat(user, span_warning("You already have a rank up task!"))
+ return
+ var/want_rank = tgui_alert("Do you want to gain a task? This will cost 50 Blood.", "Task Manager", list("Yes", "No"))
+ if(want_rank != "Yes" || QDELETED(src))
+ return
+ generate_task(user) //generate
+
+/obj/structure/bloodsucker/bloodaltar/proc/generate_task(mob/living/user)
var/task //just like amongus
- var/suckamount = bloodsuckerdatum.task_blood_required
- var/heartamount = bloodsuckerdatum.task_heart_required
- if(suckamount == 0 && heartamount == 0) // Generate random amounts if we don't already have them set
+ var/mob/living/carbon/crewmate = user
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = crewmate.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ suckamount = bloodsuckerdatum.task_blood_required
+ heartamount = bloodsuckerdatum.task_heart_required
+ if(!suckamount && !heartamount) // Generate random amounts if we don't already have them set
switch(bloodsuckerdatum.bloodsucker_level + bloodsuckerdatum.bloodsucker_level_unspent)
if(0 to 3)
suckamount = rand(100, 200)
@@ -135,49 +160,42 @@
if(8 to INFINITY)
suckamount = rand(500, 600)
heartamount = rand(5,6)
- if(bloodsuckerdatum.task_blood_drank >= suckamount || sacrifices >= heartamount)
- task_completed = TRUE
- if(task_completed)
- bloodsuckerdatum.task_memory = null
- bloodsuckerdatum.current_task = FALSE
- bloodsuckerdatum.bloodsucker_level_unspent++
- bloodsuckerdatum.altar_uses++
- bloodsuckerdatum.task_blood_drank = 0
- bloodsuckerdatum.task_blood_required = 0
- bloodsuckerdatum.task_heart_required = 0
- sacrifices = 0
- to_chat(user, span_notice("You have sucessfully done a task and gained a rank!"))
- task_completed = FALSE
- sacrificialtask = FALSE
- return
- if(bloodsuckerdatum.current_task)
- to_chat(user, span_warning("You already have a rank up task!"))
+ if(crewmate.blood_volume < 50)
+ to_chat(user, span_danger("You don't have enough blood to gain a task!"))
return
- if(!bloodsuckerdatum.current_task)
- var/want_rank = alert("Do you want to gain a task? This will cost 50 Blood.", "Task Manager", "Yes", "No")
- if(want_rank == "No" || QDELETED(src))
- return
- var/mob/living/carbon/C = user
- if(C.blood_volume < 50)
- to_chat(user, span_danger("You don't have enough blood to gain a task!"))
- return
- C.blood_volume -= 50
- switch(rand(1, 3))
- if(1,2)
- bloodsuckerdatum.task_blood_required = suckamount
- task = "Suck [suckamount] units of pure blood."
- if(3)
- bloodsuckerdatum.task_heart_required = heartamount
- task = "Sacrifice [heartamount] hearts by using them on the altar."
- sacrificialtask = TRUE
- bloodsuckerdatum.task_memory += "Current Rank Up Task: [task]
"
- bloodsuckerdatum.current_task = TRUE
- to_chat(user, span_boldnotice("You have gained a new Task! [task] Remember to collect it by using the blood altar!"))
+ crewmate.blood_volume -= 50
+ switch(rand(1, 3))
+ if(1,2)
+ bloodsuckerdatum.task_blood_required = suckamount
+ task = "Suck [suckamount] units of pure blood."
+ if(3)
+ bloodsuckerdatum.task_heart_required = heartamount
+ task = "Sacrifice [heartamount] hearts by using them on the altar."
+ sacrificialtask = TRUE
+ bloodsuckerdatum.task_memory += "Current Rank Up Task: [task]
"
+ bloodsuckerdatum.current_task = TRUE
+ to_chat(user, span_boldnotice("You have gained a new Task! [task] Remember to collect it by using the blood altar!"))
+
+/obj/structure/bloodsucker/bloodaltar/proc/check_completion(mob/living/user)
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(bloodsuckerdatum.task_blood_drank < suckamount || sacrifices < heartamount)
+ return FALSE
+ bloodsuckerdatum.task_memory = null
+ bloodsuckerdatum.current_task = FALSE
+ bloodsuckerdatum.bloodsucker_level_unspent++
+ bloodsuckerdatum.altar_uses++
+ bloodsuckerdatum.task_blood_drank = 0
+ bloodsuckerdatum.task_blood_required = 0
+ bloodsuckerdatum.task_heart_required = 0
+ sacrifices = 0
+ to_chat(user, span_notice("You have sucessfully done a task and gained a rank!"))
+ sacrificialtask = FALSE
+ return TRUE
/obj/structure/bloodsucker/bloodaltar/examine(mob/user)
. = ..()
if(sacrificialtask)
- if(sacrifices > 0)
+ if(sacrifices)
. += span_boldnotice("It currently contains [sacrifices] [organ_name].")
else
return ..()
@@ -191,7 +209,7 @@
to_chat(usr, span_warning("This type of organ doesn't have blood to sustain the altar!"))
return ..()
organ_name = H.name
- to_chat(usr, span_notice("You feed the heart to the altar!"))
+ balloon_alert(user, "heart fed!")
qdel(H)
sacrifices++
return
@@ -205,9 +223,10 @@
desc = "This seem to hold a bit of significance."
icon_state = "restingplace"
var/awoken = FALSE
+ can_buckle = TRUE
Ghost_desc = "This is a Resting Place, where Lasombra bloodsucker can ascend their powers."
Vamp_desc = "This is a Resting Place, which allows you to ascend your powers by gaining points using your ranks or blood.\n\
- Interact with the Altar by clicking on it after you have fed it a abyssal essence, acquirable through influences.\n\
+ Interact with the Altar by clicking on it after you have fed it a abyssal essence, acquirable through influences or sacrifices done on it.\n\
Remember most ascended powers have benefits if used in the dark.\n\
It only seems to speak to elders of 4 or higher ranks."
Vassal_desc = "This is the resting place, where your master does rituals to ascend their bloodsucking powers.\n\
@@ -215,28 +234,17 @@
Hunter_desc = "This is a blood altar, where monsters ascend their powers to shadowy levels.\n\
They normally need ranks or blood in exchange for power, forcing them to move out of their lair and weakening them."
-/obj/item/abyssal_essence
- name = "abyssal essence"
- desc = "As you glare at the abyssal essence, you feel it glaring back."
- icon = 'icons/obj/vamp_obj.dmi'
- icon_state = "abyssal_essence"
- item_state = "abyssal_essence"
- throwforce = 0
- w_class = WEIGHT_CLASS_TINY
- throw_speed = 3
- throw_range = 7
- pressure_resistance = 10
-
/obj/structure/bloodsucker/bloodaltar/restingplace/deconstruct(disassembled = TRUE)
. = ..()
- new /obj/item/abyssal_essence(src.loc)
+ if(awoken)
+ new /obj/item/bloodsucker/abyssal_essence(loc)
qdel(src)
/obj/structure/bloodsucker/bloodaltar/restingplace/attackby(obj/item/H, mob/user, params)
if(!IS_BLOODSUCKER(user) && !IS_VASSAL(user))
return ..()
if(!awoken)
- if(istype(H, /obj/item/abyssal_essence))
+ if(istype(H, /obj/item/bloodsucker/abyssal_essence))
to_chat(usr, span_notice("As you touch [src] with the [H], you start sensing something different coming from [src]!"))
qdel(H)
awoken = TRUE
@@ -251,11 +259,11 @@
if(INTERACTING_WITH(user, src))
return
if(user.mind in src.siphoners)
- to_chat(user, span_danger("You have already harvested this shard!"))
+ balloon_alert(user, "already harvested!")
return
- to_chat(user, span_danger("You start to harvest the energy of [src]..."))
+ balloon_alert(user, "harvesting...")
if(do_after(user, 10 SECONDS, src))
- user.put_in_hands(new /obj/item/abyssal_essence)
+ user.put_in_hands(new /obj/item/bloodsucker/abyssal_essence)
to_chat(user, span_notice("You finish harvesting the energy of [src]!"))
src.siphoners |= user.mind
@@ -263,40 +271,48 @@
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
if(!IS_BLOODSUCKER(user))
return ..()
- if(bloodsuckerdatum.my_clan == CLAN_LASOMBRA)
- if(bloodsuckerdatum.clanpoints > 0)
+ if(!anchored)
+ return ..()
+ if(LAZYLEN(buckled_mobs))
+ do_sacrifice(buckled_mobs, user)
+ if(bloodsuckerdatum.my_clan?.control_type == BLOODSUCKER_CONTROL_SHADOWS)
+ if(bloodsuckerdatum.clanpoints)
var/list/upgradablepowers = list()
- var/list/unupgradablepowers = list(/datum/action/bloodsucker/feed, /datum/action/bloodsucker/masquerade, /datum/action/bloodsucker/veil)
- for(var/datum/action/bloodsucker/power as anything in bloodsuckerdatum.powers)
+ var/list/unupgradablepowers = list(/datum/action/cooldown/bloodsucker/feed, /datum/action/cooldown/bloodsucker/masquerade, /datum/action/cooldown/bloodsucker/veil)
+ for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.powers)
if(initial(power.purchase_flags) & BLOODSUCKER_CAN_BUY)
upgradablepowers += power
if(is_type_in_list(power, unupgradablepowers))
upgradablepowers -= power
- var/choice = input(usr, "What Power do you wish to ascend? This resets the powers level.", "Darkness Manager") in upgradablepowers
+ var/choice = tgui_input_list(usr, "What Power do you wish to ascend? This resets the powers level.", "Darkness Manager", upgradablepowers)
if(!choice)
return
if((locate(upgradablepowers[choice]) in bloodsuckerdatum.powers))
return
- if(istype(choice, /datum/action/bloodsucker/targeted/brawn))
- bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/brawn/shadow)
- if(istype(choice, /datum/action/bloodsucker/targeted/haste))
- bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/haste/shadow)
- if(istype(choice, /datum/action/bloodsucker/fortitude))
- bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/fortitude/shadow) // i hate this
- if(istype(choice, /datum/action/bloodsucker/targeted/mesmerize))
- bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/mesmerize/shadow)
- if(istype(choice, /datum/action/bloodsucker/targeted/trespass))
- bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/trespass/shadow)
- if(istype(choice, /datum/action/bloodsucker/targeted/lunge))
- bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/lunge/shadow)
- if(istype(choice, /datum/action/bloodsucker/cloak/))
- bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/cloak/shadow)
- bloodsuckerdatum.powers -= choice
+ var/datum/action/cooldown/bloodsucker/granted = null
+ switch(choice)
+ if(/datum/action/cooldown/bloodsucker/targeted/brawn)
+ granted = new /datum/action/cooldown/bloodsucker/targeted/brawn/shadow
+ if(/datum/action/cooldown/bloodsucker/targeted/haste)
+ granted = new /datum/action/cooldown/bloodsucker/targeted/haste/shadow
+ if(/datum/action/cooldown/bloodsucker/fortitude)
+ granted = new /datum/action/cooldown/bloodsucker/fortitude/shadow // i hate this
+ if(/datum/action/cooldown/bloodsucker/targeted/mesmerize)
+ granted = new /datum/action/cooldown/bloodsucker/targeted/mesmerize/shadow
+ if(/datum/action/cooldown/bloodsucker/targeted/trespass)
+ granted = new /datum/action/cooldown/bloodsucker/targeted/trespass/shadow
+ if(/datum/action/cooldown/bloodsucker/targeted/lunge)
+ granted = new /datum/action/cooldown/bloodsucker/targeted/lunge/shadow
+ if(/datum/action/cooldown/bloodsucker/cloak/)
+ granted = new /datum/action/cooldown/bloodsucker/cloak/shadow
+ bloodsuckerdatum.BuyPower(granted)
+ var/datum/action/cooldown/bloodsucker/now_level_it_up = LAZYFIND(bloodsuckerdatum.powers, granted)
+ now_level_it_up.level_current = rand(3, 4)
qdel(choice)
to_chat(user, span_boldnotice("You have ascended [choice]!"))
bloodsuckerdatum.clanpoints--
return
- if(bloodsuckerdatum.bloodsucker_level >= 4 )
+ if(bloodsuckerdatum.bloodsucker_level >= 4)
if(!awoken) //don't want this to affect power upgrading if you make another one
to_chat(user, span_cult("Seems like you need a direct link to the abyss to awaken [src]. Maybe searching a spacial influence would yield something."))
return
@@ -318,11 +334,11 @@
if(8 to INFINITY)
to_chat(user, span_notice("You have evolved all abilities possible."))
return
- var/want_clantask = alert("Do you want to spend a rank to gain a shadowpoint? This will cost [rankspent] ranks.", "Dark Manager", "Yes", "No")
+ var/want_clantask = tgui_alert("Do you want to spend a rank to gain a shadowpoint? This will cost [rankspent] ranks.", "Dark Manager", list("Yes", "No"))
if(want_clantask == "No" || QDELETED(src))
return
if(bloodsuckerdatum.bloodsucker_level_unspent < rankspent)
- var/another_shot = alert("It seems like you don't have enough ranks, spend 550 blood instead?", "Dark Manager", "Yes", "No")
+ var/another_shot = tgui_alert("It seems like you don't have enough ranks, spend 550 blood instead?", "Dark Manager", list("Yes", "No"))
if(another_shot == "No" || QDELETED(src))
return
var/mob/living/carbon/C = user
@@ -337,12 +353,199 @@
return
return ..()
-////////////////////////////////////////////////////
+/obj/structure/bloodsucker/bloodaltar/restingplace/proc/do_sacrifice(list/pig, mob/living/carbon/user)
+ var/mob/living/carbon/sacrifice = pick(pig)
+ if(!sacrifice.mind)
+ balloon_alert(user, "not worthy to sacrifice!")
+ return
+ if(sacrifice.stat == DEAD)
+ balloon_alert(user, "[sacrifice.p_theyre()] already dead...")
+ return
+ balloon_alert(user, "starting sacrifice...")
+ if(!do_after(user, 10 SECONDS, sacrifice))
+ balloon_alert(user, "interrupted!")
+ return
+ playsound(get_turf(sacrifice), 'sound/weapons/slash.ogg', 50, TRUE, -1)
+ sacrifice.adjustBruteLoss(200)
+ balloon_alert(user, "sucess!")
+ new /obj/item/bloodsucker/abyssal_essence(get_turf(src))
+
+#define METALLIMIT 50
+
+/obj/structure/bloodsucker/moldingstone
+ name = "molding stone"
+ desc = "Not made of marble, but will have to do."
+ icon_state = "molding_stone"
+ anchored = FALSE
+ density = TRUE
+ can_buckle = TRUE
+ buckle_lying = 180
+ var/metal = 0
+
+/obj/structure/bloodsucker/moldingstone/examine(mob/user)
+ . = ..()
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(bloodsuckerdatum.my_clan?.control_type >= BLOODSUCKER_CONTROL_METAL)
+ if(metal)
+ . += span_boldnotice("It currently contains [metal] metal to use in sculpting.")
+ else
+ return ..()
+
+/obj/structure/bloodsucker/moldingstone/bolt()
+ . = ..()
+ anchored = TRUE
-/*/obj/structure/bloodsucker/bloodstatue
+/obj/structure/bloodsucker/moldingstone/unbolt()
+ . = ..()
+ anchored = FALSE
+
+/obj/structure/bloodsucker/moldingstone/update_icon()
+ cut_overlays()
+ switch(metal)
+ if(1 to 5)
+ add_overlay("metal")
+ if(6 to 20)
+ add_overlay("metal_2")
+ if(21 to 50)
+ add_overlay("metal_3")
+
+/obj/structure/bloodsucker/moldingstone/attackby(obj/item/I, mob/user, params)
+ if(!anchored)
+ return
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(!bloodsuckerdatum)
+ return ..()
+ if(bloodsuckerdatum.my_clan?.control_type < BLOODSUCKER_CONTROL_METAL)
+ return ..()
+ if(istype(I, /obj/item/stack/sheet/metal))
+ if(metal >= METALLIMIT)
+ balloon_alert(user, "full!")
+ return
+ var/obj/item/stack/sheet/metal/M = I
+ if(metal + M.amount > METALLIMIT)
+ M.use(METALLIMIT - metal)
+ metal = METALLIMIT
+ else
+ metal = M.amount
+ qdel(M)
+ balloon_alert(user, "added [metal] metal")
+ if(istype(I, /obj/item/bloodsucker/chisel))
+ start_sculpiting(user)
+ update_icon()
+
+/obj/structure/bloodsucker/moldingstone/proc/start_sculpiting(mob/living/artist)
+ if(metal < 10)
+ balloon_alert(artist, "not enough metal!")
+ return
+ var/list/possible_statues = list()
+ for(var/statues_available as anything in subtypesof(/obj/structure/bloodsucker/bloodstatue))
+ possible_statues[initial(statues_available)] = statues_available
+ var/obj/structure/bloodsucker/bloodstatue/what_type = tgui_input_list(artist, "What kind of statue would you like to make?", "Artist Manual", possible_statues)
+ if(!do_after(artist, 10 SECONDS, src))
+ artist.balloon_alert(artist, "ruined!")
+ metal -= rand(5, 10)
+ update_icon()
+
+ return
+ artist.balloon_alert(artist, "done, a masterpiece!")
+ new what_type(get_turf(src))
+
+/obj/structure/bloodsucker/moldingstone/CtrlClick(mob/user)
+ if(!anchored)
+ return ..()
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(!bloodsuckerdatum)
+ return ..()
+ if(bloodsuckerdatum.my_clan?.control_type < BLOODSUCKER_CONTROL_METAL)
+ return ..()
+ if(metal)
+ var/count = input("How much metal would you like to retrieve from [src]?","Fine Metal", metal) as null | num
+ if(count > METALLIMIT)
+ count = METALLIMIT
+ if(count > metal)
+ count = metal
+ metal -= count
+ new /obj/item/stack/sheet/metal(get_turf(user), count)
+ else
+ to_chat(user, span_warning("There's no metal to retrieve in [src]."))
+ update_icon()
+#undef METALLIMIT
+
+/obj/structure/bloodsucker/bloodstatue
name = "bloody countenance"
desc = "It looks upsettingly familiar..."
-/obj/structure/bloodsucker/bloodportrait
+ icon_state = "statue"
+
+/obj/structure/bloodsucker/bloodstatue/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/art, 30)
+
+/obj/structure/bloodsucker/bloodstatue/bolt()
+ . = ..()
+ anchored = TRUE
+ START_PROCESSING(SSprocessing, src)
+
+/obj/structure/bloodsucker/bloodstatue/unbolt()
+ . = ..()
+ anchored = FALSE
+ STOP_PROCESSING(SSprocessing, src)
+
+/obj/structure/bloodsucker/bloodstatue/command
+ name = "captain bust"
+ desc = "It fills you with an eerie sense of patriotism."
+ icon_state = "statue_command"
+
+/obj/structure/bloodsucker/bloodstatue/command/attack_hand(mob/user, list/modifiers) //get rid of area requirement
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(istype(bloodsuckerdatum) && !owner)
+ if(!bloodsuckerdatum.bloodsucker_lair_area)
+ to_chat(user, span_danger("You don't have a lair. Claim a coffin to make that location your lair."))
+ return FALSE
+ to_chat(user, span_notice("Do you wish to secure [src] here?"))
+ var/static/list/secure_options = list(
+ "Yes" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_yes"),
+ "No" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_no"))
+ var/secure_response = show_radial_menu(user, src, secure_options, radius = 36, require_near = TRUE)
+ if(!secure_response)
+ return FALSE
+ switch(secure_response)
+ if("Yes")
+ user.playsound_local(null, 'sound/items/ratchet.ogg', 70, FALSE, pressure_affected = FALSE)
+ bolt(user)
+ return FALSE
+ return FALSE
+ return TRUE
+
+/obj/structure/bloodsucker/bloodstatue/command/bolt()
+ . = ..()
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner?.mind?.has_antag_datum(/datum/antagonist/bloodsucker)
+ var/area/current_area = get_area(src)
+ if(current_area == bloodsuckerdatum.bloodsucker_lair_area)
+ return
+ bloodsuckerdatum.bloodsucker_lair_area.contained_turfs += current_area.contained_turfs
+
+/obj/structure/bloodsucker/bloodstatue/command/unbolt()
+ . = ..()
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner?.mind?.has_antag_datum(/datum/antagonist/bloodsucker)
+ var/area/current_area = get_area(src)
+ if(current_area == bloodsuckerdatum.bloodsucker_lair_area)
+ return
+ bloodsuckerdatum.bloodsucker_lair_area.turfs_to_uncontain += current_area.contained_turfs
+
+/obj/structure/bloodsucker/bloodstatue/greytide
+ name = "greytider bust"
+ desc = "Despite its simple attire it looks quite menancing."
+ icon_state = "statue_assist"
+
+/obj/structure/bloodsucker/bloodstatue/greytide/process()
+ for(var/mob/living/carbon/carbon_in_area in get_area(src))
+ if(IS_VASSAL(carbon_in_area) || IS_BLOODSUCKER(carbon_in_area) || carbon_in_area.has_status_effect(STATUS_EFFECT_EXPOSED))
+ continue
+ carbon_in_area.apply_status_effect(STATUS_EFFECT_EXPOSED)
+
+////////////////////////////////////////////////////
+
+/*/obj/structure/bloodsucker/bloodportrait
name = "oil portrait"
desc = "A disturbingly familiar face stares back at you. Those reds don't seem to be painted in oil..."
/obj/structure/bloodsucker/bloodbrazier
@@ -436,18 +639,13 @@
Vamp_desc = "This is the Vassal rack, which allows you to thrall crewmembers into loyal minions in your service.\n\
Simply click and hold on a victim, and then drag their sprite on the vassal rack. Click on help intent on the vassal rack to unbuckle them.\n\
To convert into a Vassal, repeatedly click on the persuasion rack while NOT on help intent. The time required scales with the tool in your off hand. This costs Blood to do.\n\
- Once you have Vassals ready, you are able to select a Favorite Vassal;\n\
- Click the Rack as a Vassal is buckled onto it to turn them into your Favorite. This can only be done once, so choose carefully!\n\
- This process costs 150 Blood to do, and will make your Vassal unable to be deconverted, outside of you reaching Final Death."
+ Vassals can be turned into special ones by continuing to torture them once converted."
Vassal_desc = "This is the vassal rack, which allows your master to thrall crewmembers into their minions.\n\
Aid your master in bringing their victims here and keeping them secure.\n\
You can secure victims to the vassal rack by click dragging the victim onto the rack while it is secured."
Hunter_desc = "This is the vassal rack, which monsters use to brainwash crewmembers into their loyal slaves.\n\
They usually ensure that victims are handcuffed, to prevent them from running away.\n\
Their rituals take time, allowing us to disrupt it."
- /// So we can't spam buckle people onto the rack
- var/use_lock = FALSE
- var/mob/buckled
/// Resets on each new character to be added to the chair. Some effects should lower it...
var/convert_progress = 3
/// Mindshielded and Antagonists willingly have to accept you as their Master.
@@ -471,8 +669,8 @@
/obj/structure/bloodsucker/vassalrack/examine(mob/user)
. = ..()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(bloodsuckerdatum.my_clan == CLAN_TZIMISCE)
- if(meat_amount > 0)
+ if(bloodsuckerdatum.my_clan?.control_type == BLOODSUCKER_CONTROL_FLESH)
+ if(meat_amount)
. += span_boldnotice("It currently contains [meat_points] points to use in rituals.")
. += span_boldnotice("You can add meat points to the rack by using muscle, acquired from Dicing corpses, on it.")
else
@@ -495,19 +693,15 @@
to_chat(user, span_announce("* Bloodsucker Tip: Examine the Persuasion Rack to understand how it functions!"))
return
// Default checks
- if(!isliving(movable_atom) || !living_target.Adjacent(src) || living_target == user || !isliving(user) || use_lock || has_buckled_mobs() || user.incapacitated() || living_target.buckled)
+ if(!isliving(movable_atom) || !living_target.Adjacent(src) || living_target == user || !isliving(user) || has_buckled_mobs() || user.incapacitated() || living_target.buckled)
return
// Don't buckle Silicon to it please.
if(issilicon(living_target))
to_chat(user, span_danger("You realize that Silicon cannot be vassalized, therefore it is useless to buckle them."))
return
- // Good to go - Buckle them!
- use_lock = TRUE
if(do_mob(user, living_target, 5 SECONDS))
attach_victim(living_target, user)
- use_lock = FALSE
-/// Attempt Release (Owner vs Non Owner)
/obj/structure/bloodsucker/vassalrack/proc/attach_victim(mob/living/target, mob/living/user)
// Standard Buckle Check
target.forceMove(get_turf(src))
@@ -518,7 +712,7 @@
span_boldnotice("You secure [target] tightly in place. They won't escape you now."),
)
- playsound(src.loc, 'sound/effects/pop_expl.ogg', 25, 1)
+ playsound(loc, 'sound/effects/pop_expl.ogg', 25, 1)
density = TRUE
update_icon()
@@ -527,80 +721,78 @@
disloyalty_confirm = FALSE
disloyalty_offered = FALSE
-/// Attempt Unbuckle
+/// Attempt Release (Owner vs Non Owner)
/obj/structure/bloodsucker/vassalrack/user_unbuckle_mob(mob/living/buckled_mob, mob/user)
- if(!IS_BLOODSUCKER(user) || !IS_VASSAL(user))
- if(buckled_mob == user)
- buckled_mob.visible_message(
- span_danger("[user] tries to release themself from the rack!"),
- span_danger("You attempt to release yourself from the rack!"),
- span_hear("You hear a squishy wet noise."),
- )
- else
- buckled_mob.visible_message(
- span_danger("[user] tries to pull [buckled_mob] from the rack!"),
- span_danger("[user] tries to pull [buckled_mob] from the rack!"),
- span_hear("You hear a squishy wet noise."),
- )
- // Monster hunters are used to this sort of stuff, they know how they work, which includes breaking others out
- var/breakout_timer = IS_MONSTERHUNTER(user) ? 20 SECONDS : 10 SECONDS
- if(!do_mob(user, buckled_mob, breakout_timer))
+ if(IS_BLOODSUCKER(user) || IS_VASSAL(user))
+ return ..()
+
+ if(buckled_mob == user)
+ user.visible_message(
+ span_danger("[user] tries to release themself from the rack!"),
+ span_danger("You attempt to release yourself from the rack!"),
+ span_hear("You hear a squishy wet noise."))
+ if(!do_after(user, 20 SECONDS, user))
return
- unbuckle_mob(buckled_mob)
- . = ..()
+ else
+ buckled_mob.visible_message(
+ span_danger("[user] tries to pull [buckled_mob] rack!"),
+ span_danger("[user] tries to pull [buckled_mob] rack!"),
+ span_hear("You hear a squishy wet noise."))
+ if(!do_after(user, 10 SECONDS, buckled_mob))
+ return
+
+ return ..()
/obj/structure/bloodsucker/vassalrack/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE)
. = ..()
if(!.)
return FALSE
- src.visible_message(span_danger("[buckled_mob][buckled_mob.stat == DEAD ? "'s corpse" : ""] slides off of the rack."))
+ visible_message(span_danger("[buckled_mob][buckled_mob.stat == DEAD ? "'s corpse" : ""] slides off of the rack."))
density = FALSE
- buckled_mob.Paralyze(3 SECONDS)
+ buckled_mob.Paralyze(2 SECONDS)
update_icon()
- use_lock = FALSE // Failsafe
return TRUE
/obj/structure/bloodsucker/vassalrack/attack_hand(mob/user, list/modifiers)
. = ..()
if(!.)
return FALSE
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
// Is there anyone on the rack & If so, are they being tortured?
- if(use_lock || !has_buckled_mobs())
+ if(!has_buckled_mobs())
return FALSE
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
var/mob/living/carbon/buckled_carbons = pick(buckled_mobs)
if(user.a_intent == INTENT_HELP)
if(istype(bloodsuckerdatum))
unbuckle_mob(buckled_carbons)
return FALSE
- else
- user_unbuckle_mob(buckled_carbons, user)
- return
+ user_unbuckle_mob(buckled_carbons, user)
+ return
+ if(!bloodsuckerdatum.my_clan)
+ to_chat(user, span_warning("You can't vassalize people until you enter a Clan (Through your Antagonist UI button)"))
+ user.balloon_alert(user, "join a clan first!")
+ return
/// If I'm not a Bloodsucker, try to unbuckle them.
var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(buckled_carbons)
// Are they our Vassal, or Dead?
if(buckled_carbons.stat == DEAD)
- if(bloodsuckerdatum.my_clan != CLAN_TZIMISCE)
- to_chat(user, span_warning("[buckled_carbons] is dead!"))
+ if(bloodsuckerdatum.my_clan?.control_type < BLOODSUCKER_CONTROL_FLESH)
+ balloon_alert(user, "[buckled_carbons.p_theyre()] dead!")
return
do_ritual(user, buckled_carbons)
return
- if(istype(vassaldatum) && vassaldatum.master == bloodsuckerdatum)
- // Can we assign a Favorite Vassal?
- if(istype(vassaldatum) && !bloodsuckerdatum.has_favorite_vassal)
- offer_favorite_vassal(user, buckled_carbons)
- else if(bloodsuckerdatum.my_clan == CLAN_TZIMISCE)
- do_ritual(user, buckled_carbons)
- use_lock = FALSE
+ if(vassaldatum && (vassaldatum in bloodsuckerdatum.vassals))
+ SEND_SIGNAL(src, BLOODSUCKER_PRE_MAKE_FAVORITE, bloodsuckerdatum, vassaldatum)
return
// Not our Vassal, but Alive & We're a Bloodsucker, good to torture!
torture_victim(user, buckled_carbons)
#define MEATLIMIT 3
+
/obj/structure/bloodsucker/vassalrack/attackby(obj/item/I, mob/user, params) //Tzimisce stuff
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(bloodsuckerdatum.my_clan != CLAN_TZIMISCE)
+ if(bloodsuckerdatum.my_clan?.control_type < BLOODSUCKER_CONTROL_FLESH)
return ..() // only gamers
if(istype(I, /obj/item/muscle))
if(meat_amount >= MEATLIMIT)
@@ -624,37 +816,37 @@
/obj/structure/bloodsucker/vassalrack/update_icon()
cut_overlays()
- if(bigmeat > 0)
+ if(bigmeat)
add_overlay("bigmeat_[bigmeat]")
- if(intermeat > 0)
+ if(intermeat)
add_overlay("mediummeat_[intermeat]")
add_overlay("smallmeat_[intermeat]")
- if(mediummeat > 0)
+ if(mediummeat)
add_overlay("mediummeat_[mediummeat + intermeat]")
- if(smallmeat > 0)
+ if(smallmeat)
add_overlay("smallmeat_[smallmeat + intermeat]")
/obj/structure/bloodsucker/vassalrack/CtrlClick(mob/user)
if(!anchored)
return ..()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- if(bloodsuckerdatum.my_clan != CLAN_TZIMISCE)
+ if(bloodsuckerdatum.my_clan?.control_type < BLOODSUCKER_CONTROL_FLESH)
return ..()
- if(meat_amount > 0)
- if(smallmeat > 0)
+ if(meat_amount)
+ if(smallmeat)
new /obj/item/muscle/small(user.drop_location())
smallmeat--
meat_points -= 1
- if(mediummeat > 0)
+ if(mediummeat)
new /obj/item/muscle/medium(user.drop_location())
mediummeat--
meat_points -= 2
- if(intermeat > 0)
+ if(intermeat)
new /obj/item/muscle/medium(user.drop_location())
new /obj/item/muscle/small(user.drop_location())
intermeat--
meat_points -= 3
- if(bigmeat > 0)
+ if(bigmeat)
new /obj/item/muscle/big(user.drop_location())
bigmeat--
meat_points -= 4
@@ -670,175 +862,145 @@
*/
/obj/structure/bloodsucker/vassalrack/proc/torture_victim(mob/living/user, mob/living/target)
+ if(INTERACTING_WITH(user, target))
+ balloon_alert(user, "already interacting!")
+ return
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- /// Prep...
- use_lock = TRUE
- /// Conversion Process
- if(convert_progress > 0)
- to_chat(user, span_notice("You spill some blood and prepare to initiate [target] into your service."))
- bloodsuckerdatum.AddBloodVolume(-text2num(TORTURE_BLOOD_COST))
- if(!do_torture(user,target))
- to_chat(user, span_danger("The ritual has been interrupted!"))
+ if(IS_VASSAL(target))
+ var/datum/antagonist/vassal/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal)
+ if(!vassaldatum.master.broke_masquerade)
+ balloon_alert(user, "someone else's vassal!")
+ return FALSE
+
+ var/disloyalty_requires = RequireDisloyalty(user, target)
+ if(disloyalty_requires == VASSALIZATION_BANNED)
+ balloon_alert(user, "can't be vassalized!")
+ return FALSE
+
+ // Conversion Process
+ if(convert_progress)
+ balloon_alert(user, "spilling blood...")
+ bloodsuckerdatum.AddBloodVolume(-TORTURE_BLOOD_HALF_COST)
+ if(!do_torture(user, target))
+ return FALSE
+ bloodsuckerdatum.AddBloodVolume(-TORTURE_BLOOD_HALF_COST)
+ // Prevent them from unbuckling themselves as long as we're torturing.
+ target.Paralyze(1 SECONDS)
+ convert_progress--
+
+ // We're done? Let's see if they can be Vassal.
+ if(convert_progress)
+ balloon_alert(user, "needs more persuasion...")
+ return
+
+ if(disloyalty_requires)
+ balloon_alert(user, "has external loyalties! more persuasion required!")
else
- /// Prevent them from unbuckling themselves as long as we're torturing.
- target.Paralyze(1 SECONDS)
- convert_progress--
- /// We're done? Let's see if they can be Vassal.
- if(convert_progress <= 0)
- if(IS_VASSAL(target))
- var/datum/antagonist/vassal/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal)
- if(!vassaldatum.master.broke_masquerade)
- to_chat(user, span_boldwarning("[target] is under the spell of another Bloodsucker!"))
- return
- if(RequireDisloyalty(user, target))
- to_chat(user, span_boldwarning("[target] has external loyalties! [target.p_they(TRUE)] will require more persuasion to break [target.p_them()] to your will!"))
- else
- to_chat(user, span_notice("[target] looks ready for the Dark Communion."))
- /// Otherwise, we're not done, we need to persuade them some more.
- else
- to_chat(user, span_notice("[target] could use [convert_progress == 1 ? "a little" : "some"] more persuasion."))
- use_lock = FALSE
+ balloon_alert(user, "ready for communion!")
return
- /// Check: Mindshield & Antag
- if(!disloyalty_confirm && RequireDisloyalty(user, target))
- if(!do_disloyalty(user,target))
- to_chat(user, span_danger("The ritual has been interrupted!"))
- else if(!disloyalty_confirm)
- to_chat(user, span_danger("[target] refuses to give into your persuasion. Perhaps a little more?"))
+
+ if(!disloyalty_confirm && disloyalty_requires)
+ if(!do_disloyalty(user, target))
+ return
+ if(!disloyalty_confirm)
+ balloon_alert(user, "refused persuasion!")
else
- to_chat(user, span_notice("[target] looks ready for the Dark Communion."))
- use_lock = FALSE
+ balloon_alert(user, "ready for communion!")
return
- user.visible_message(
- span_notice("[user] marks a bloody smear on [target]'s forehead and puts a wrist up to [target.p_their()] mouth!"),
- span_notice("You paint a bloody marking across [target]'s forehead, place your wrist to [target.p_their()] mouth, and subject [target.p_them()] to the Dark Communion."),
- )
- if(!do_mob(user, src, 5 SECONDS))
- to_chat(user, span_danger("The ritual has been interrupted!"))
- use_lock = FALSE
+
+ user.balloon_alert_to_viewers("smears blood...", "painting bloody marks...")
+ if(!do_after(user, 5 SECONDS, target))
+ balloon_alert(user, "interrupted!")
return
if(HAS_TRAIT(target, TRAIT_MINDSHIELD))
to_chat(user, span_danger("They're mindshielded! Break their mindshield with a candelabrum or surgery before continuing!"))
- return
- /// Convert to Vassal!
- bloodsuckerdatum.AddBloodVolume(-text2num(TORTURE_CONVERSION_COST))
- if(bloodsuckerdatum && bloodsuckerdatum.attempt_turn_vassal(target))
- bloodsuckerdatum.bloodsucker_level_unspent++
- user.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
- target.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
- target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
- target.Jitter(15)
- INVOKE_ASYNC(target, TYPE_PROC_REF(/mob, emote), "laugh")
- //remove_victim(target) // Remove on CLICK ONLY!
- use_lock = FALSE
+ return VASSALIZATION_DISLOYAL
+ // Convert to Vassal!
+ bloodsuckerdatum.AddBloodVolume(-TORTURE_CONVERSION_COST)
+ if(bloodsuckerdatum.make_vassal(target))
+ SEND_SIGNAL(src, BLOODSUCKER_MADE_VASSAL, user, target)
/obj/structure/bloodsucker/vassalrack/proc/do_torture(mob/living/user, mob/living/carbon/target, mult = 1)
- /// Fifteen seconds if you aren't using anything. Shorter with weapons and such.
+ // Fifteen seconds if you aren't using anything. Shorter with weapons and such.
var/torture_time = 15
var/torture_dmg_brute = 2
var/torture_dmg_burn = 0
- /// Get Bodypart
- var/target_string = ""
- var/obj/item/bodypart/selected_bodypart = null
- selected_bodypart = pick(target.bodyparts)
- if(selected_bodypart)
- target_string += selected_bodypart.name
- /// Get Weapon
- var/obj/item/held_item = user.get_active_held_item()
- if(!istype(held_item))
- held_item = user.get_inactive_held_item()
+ var/obj/item/bodypart/selected_bodypart = pick(target.bodyparts)
+ // Get Weapon
+ var/obj/item/held_item = user.get_inactive_held_item()
/// Weapon Bonus
if(held_item)
torture_time -= held_item.force / 4
- torture_dmg_brute += held_item.force / 4
- //torture_dmg_burn += I.
- if(held_item.sharpness == SHARP_EDGED)
- torture_time -= 2
- else if(held_item.sharpness == SHARP_POINTY)
- torture_time -= 3
- /// This will hurt your eyes.
- else if(held_item.tool_behaviour == TOOL_WELDER)
- if(held_item.use_tool(src, user, 0, volume = 5))
- torture_time -= 6
- torture_dmg_burn += 5
- held_item.play_tool_sound(target)
- /// Minimum 5 seconds.
- torture_time = max(5 SECONDS, torture_time SECONDS)
- /// Now run process.
- if(!do_mob(user, target, torture_time * mult))
+ if(!held_item.use_tool(src, user, 0, volume = 5))
+ return
+ switch(held_item.damtype)
+ if(BRUTE)
+ torture_dmg_brute = held_item.force / 4
+ torture_dmg_burn = 0
+ if(BURN)
+ torture_dmg_brute = 0
+ torture_dmg_burn = held_item.force / 4
+ switch(held_item.sharpness)
+ if(SHARP_EDGED)
+ torture_time -= 2
+ if(SHARP_POINTY)
+ torture_time -= 3
+
+ // Minimum 5 seconds.
+ torture_time = max(5 SECONDS, torture_time * 10)
+ // Now run process.
+ if(!do_after(user, (torture_time * mult), target))
return FALSE
- /// Success?
+
if(held_item)
playsound(loc, held_item.hitsound, 30, 1, -1)
held_item.play_tool_sound(target)
target.visible_message(
- span_danger("[user] performs a ritual, spilling some of [target]'s blood from their [target_string] and shaking them up!"),
- span_userdanger("[user] performs a ritual, spilling some blood from your [target_string], shaking you up!"),
- )
+ span_danger("[user] performs a ritual, spilling some of [target]'s blood from their [selected_bodypart.name] and shaking them up!"),
+ span_userdanger("[user] performs a ritual, spilling some blood from your [selected_bodypart.name], shaking you up!"))
+
INVOKE_ASYNC(target, TYPE_PROC_REF(/mob, emote), "scream")
- target.Jitter(5)
- target.apply_damages(brute = torture_dmg_brute, burn = torture_dmg_burn, def_zone = (selected_bodypart ? selected_bodypart.body_zone : null)) // take_overall_damage(6,0)
+ target.adjust_jitter(5 SECONDS)
+ target.apply_damages(brute = torture_dmg_brute, burn = torture_dmg_burn, def_zone = selected_bodypart.body_zone)
return TRUE
/// Offer them the oppertunity to join now.
/obj/structure/bloodsucker/vassalrack/proc/do_disloyalty(mob/living/user, mob/living/target)
- spawn(10)
- /// Are we still torturing? Did we cancel? Are they still here?
- if(use_lock && target && target.client)
- to_chat(user, span_notice("[target] has been given the opportunity for servitude. You await their decision..."))
- var/alert_text = "You are being tortured! Do you want to give in and pledge your undying loyalty to [user]?"
- alert_text += "\n\nYou will not lose your current objectives, but they come second to the will of your new master!"
- to_chat(target, span_cultlarge("THE HORRIBLE PAIN! WHEN WILL IT END?!"))
- var/list/torture_icons = list(
- "Accept" = image(icon = 'icons/mob/actions/actions_bloodsucker.dmi', icon_state = "power_recup"),
- "Refuse" = image(icon = 'icons/obj/weapons/baton.dmi', icon_state = "stunbaton_active")
- )
- var/torture_response = show_radial_menu(target, src, torture_icons, radius = 36, require_near = TRUE)
- switch(torture_response)
- if("Accept")
- disloyalty_accept(target)
- else
- disloyalty_refuse(target)
- if(!do_torture(user,target, 2))
+ if(disloyalty_offered)
return FALSE
- // NOTE: We only remove loyalties when we're CONVERTED!
+ disloyalty_offered = TRUE
+ to_chat(user, span_notice("[target] has been given the opportunity for servitude. You await their decision..."))
+ var/alert_response = tgui_alert(
+ user = target, \
+ message = "You are being tortured! Do you want to give in and pledge your undying loyalty to [user]? \n\
+ You will not lose your current objectives, but they come second to the will of your new master!", \
+ title = "THE HORRIBLE PAIN! WHEN WILL IT END?!",
+ buttons = list("Accept", "Refuse"),
+ timeout = 10 SECONDS, \
+ autofocus = TRUE, \
+ )
+ switch(alert_response)
+ if("Accept")
+ disloyalty_confirm = TRUE
+ else
+ target.balloon_alert_to_viewers("stares defiantly", "refused vassalization!")
+ disloyalty_offered = FALSE
+
return TRUE
/obj/structure/bloodsucker/vassalrack/proc/RequireDisloyalty(mob/living/user, mob/living/target)
+#ifdef BLOODSUCKER_TESTING
+ if(!target || !target.mind)
+#else
+ if(!target || !target.client)
+#endif
+ balloon_alert(user, "target has no mind!")
+ return VASSALIZATION_BANNED
+
var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user)
return bloodsuckerdatum.AmValidAntag(target)
-/obj/structure/bloodsucker/vassalrack/proc/disloyalty_accept(mob/living/target)
- // FAILSAFE: Still on the rack?
- if(!(locate(target) in buckled_mobs))
- return
- // NOTE: You can say YES after torture. It'll apply to next time.
- disloyalty_confirm = TRUE
-
-/obj/structure/bloodsucker/vassalrack/proc/disloyalty_refuse(mob/living/target)
- // FAILSAFE: Still on the rack?
- if(!(locate(target) in buckled_mobs))
- return
- // Failsafe: You already said YES.
- if(disloyalty_confirm)
- return
- to_chat(target, span_notice("You refuse to give in! You will not break!"))
-
-
-/obj/structure/bloodsucker/vassalrack/proc/offer_favorite_vassal(mob/living/carbon/human/user, mob/living/target)
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- var/datum/antagonist/vassal/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal)
-
- switch(input("Would you like to turn this Vassal into your completely loyal Servant? This costs 150 Blood to do. You cannot undo this.") in list("Yes", "No"))
- if("Yes")
- user.blood_volume -= 150
- bloodsuckerdatum.has_favorite_vassal = TRUE
- vassaldatum.make_favorite(user)
- else
- to_chat(user, span_danger("You decide not to turn [target] into your Favorite Vassal."))
- use_lock = FALSE
-
/obj/structure/bloodsucker/vassalrack/proc/do_ritual(mob/living/user, mob/living/target)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
if(!target.mind)
@@ -848,28 +1010,26 @@
var/mob/living/carbon/human/H = target
/// Due to the checks leding up to this, if they fail this, they're dead & Not our vassal.
- if(!IS_VASSAL(target))
+ if(!IS_VASSAL(target)) //remind me to refactor this later
to_chat(user, span_notice("Do you wish to rebuild this body? This will remove any restraints they might have, and will cost 150 Blood!"))
- var/revive_response = alert(usr, "Would you like to revive [target]?", "Ghetto Medbay", "Yes", "No")
- switch(revive_response)
- if("Yes")
- if(!do_mob(user, src, 7 SECONDS))
- to_chat(user, span_danger("The ritual has been interrupted!"))
- return
- if(prob(15) && bloodsuckerdatum.bloodsucker_level <= 2)
- to_chat(user, span_danger("Something has gone terribly wrong! You have accidentally turned [target] into a High-Functioning Zombie!"))
- to_chat(target, span_announce("As Blood drips over your body, your heart fails to beat... But you still wake up."))
- H.set_species(/datum/species/zombie)
- else
- to_chat(user, span_danger("You have brought [target] back from the Dead!"))
- to_chat(target, span_announce("As Blood drips over your body, your heart begins to beat... You live again!"))
- B.blood_volume -= 150
- target.revive(full_heal = TRUE, admin_revive = FALSE)
+ var/revive_response = tgui_alert(usr, "Would you like to revive [target]?", "Ghetto Medbay", list("Yes", "No"))
+ if(revive_response == "Yes")
+ if(!do_mob(user, src, 7 SECONDS))
+ to_chat(user, span_danger("The ritual has been interrupted!"))
return
+ if(prob(70 - bloodsuckerdatum.bloodsucker_level * 7)) //calculation, stops going wrong at level 10
+ to_chat(user, span_danger("Something has gone terribly wrong! You have accidentally turned [target] into a High-Functioning Zombie!"))
+ to_chat(target, span_announce("As Blood drips over your body, your heart fails to beat... But you still wake up."))
+ H.set_species(/datum/species/zombie)
+ else
+ to_chat(user, span_danger("You have brought [target] back from the Dead!"))
+ to_chat(target, span_announce("As Blood drips over your body, your heart begins to beat... You live again!"))
+ B.blood_volume -= 150
+ target.revive(full_heal = TRUE, admin_revive = FALSE)
+ return
to_chat(user, span_danger("You decide not to revive [target]."))
// Unbuckle them now.
unbuckle_mob(B)
- use_lock = FALSE
return
var/list/races = list(HUSK_MONSTER)
switch(bloodsuckerdatum.bloodsucker_level)
@@ -884,7 +1044,7 @@
races += TRIPLECHEST_MONSTER
var/list/options = list()
options = races
- var/answer = input(user, "We have the chance to mutate our Vassal, how should we mutilate their corpse? This will cost us blood.", "What do we do with our Vassal?") in options
+ var/answer = tgui_input_list(user, "We have the chance to mutate our Vassal, how should we mutilate their corpse? This will cost us blood.", "What do we do with our Vassal?", options)
var/meat_cost = 0
var/blood_gained
if(!answer)
@@ -1061,7 +1221,7 @@
/// We dont want Bloodsuckers or Vassals affected by this
if(IS_VASSAL(nearly_people) || IS_BLOODSUCKER(nearly_people))
continue
- nearly_people.hallucination += 5
+ nearly_people.adjust_hallucinations(5 SECONDS)
if(nearly_people.getStaminaLoss() >= 100)
continue
if(nearly_people.getStaminaLoss() >= 60)
@@ -1132,10 +1292,10 @@
/obj/structure/bloodsucker/candelabrum/proc/remove_loyalties(mob/living/target, mob/living/user)
// Find Mindshield implant & destroy, takes a good while.
- if(HAS_TRAIT(target, TRAIT_MINDSHIELD))
- for(var/obj/item/implant/mindshield/L in target)
- if(L)
- qdel(L)
+ for(var/obj/item/implant/all_implants as anything in target.implants)
+ if(all_implants.type == /obj/item/implant/mindshield)
+ all_implants.removed(target, silent = TRUE)
+
/// Attempt Unbuckle
/obj/structure/bloodsucker/candelabrum/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE)
. = ..()
diff --git a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm
index 5aa297842d69..7ab14d530e6f 100644
--- a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm
+++ b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm
@@ -1,10 +1,20 @@
+///How much Blood it costs to live.
+#define BLOODSUCKER_PASSIVE_BLOOD_DRAIN 0.1
+
/// Runs from COMSIG_LIVING_BIOLOGICAL_LIFE, handles Bloodsucker constant proccesses.
-/datum/antagonist/bloodsucker/proc/LifeTick()
+/datum/antagonist/bloodsucker/proc/LifeTick(mob/living/source, seconds_per_tick, times_fired)
+ SIGNAL_HANDLER
+
+ if(isbrain(owner.current))
+ return
if(!owner && !owner.current)
INVOKE_ASYNC(src, PROC_REF(HandleDeath))
return
+ if(HAS_TRAIT(owner.current, TRAIT_NODEATH))
+ INVOKE_ASYNC(src, PROC_REF(check_end_torpor))
+
if(istype(owner.current, /mob/living/simple_animal/hostile/bloodsucker))
return
@@ -13,40 +23,48 @@
// Deduct Blood
if(owner.current.stat == CONSCIOUS && !HAS_TRAIT(owner.current, TRAIT_NODEATH))
- INVOKE_ASYNC(src, PROC_REF(AddBloodVolume), passive_blood_drain) // -.1 currently
- if(HandleHealing(1))
- if((COOLDOWN_FINISHED(src, bloodsucker_spam_healing)) && owner.current.blood_volume > 0)
- to_chat(owner.current, span_notice("The power of your blood begins knitting your wounds..."))
+ INVOKE_ASYNC(src, PROC_REF(AddBloodVolume), -BLOODSUCKER_PASSIVE_BLOOD_DRAIN) // -.1 currently
+ if(INVOKE_ASYNC(src, PROC_REF(HandleHealing)))
+ if((COOLDOWN_FINISHED(src, bloodsucker_spam_healing)) && bloodsucker_blood_volume)
+ INVOKE_ASYNC(src, PROC_REF(to_chat), owner.current, span_notice("The power of your blood begins knitting your wounds..."))
COOLDOWN_START(src, bloodsucker_spam_healing, BLOODSUCKER_SPAM_HEALING)
// Standard Updates
- INVOKE_ASYNC(src, PROC_REF(HandleDeath))
+ SEND_SIGNAL(src, COMSIG_BLOODSUCKER_ON_LIFETICK)
INVOKE_ASYNC(src, PROC_REF(HandleStarving))
- INVOKE_ASYNC(src, PROC_REF(HandleTorpor))
-
- if(my_clan == CLAN_TOREADOR && owner.current.stat != DEAD)
- for(var/datum/antagonist/vassal/vassal in vassals)
- if(vassal.master != src)
- continue
- if(!vassal.owner.current || vassal.owner.current == DEAD)
- continue
- if(get_dist(get_turf(owner.current), get_turf(vassal.owner.current)) > 5)
- continue
- SEND_SIGNAL(vassal.owner.current, COMSIG_ADD_MOOD_EVENT, /datum/mood_event/toreador_vassal)
-
+ INVOKE_ASYNC(src, PROC_REF(update_blood))
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// BLOOD
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ INVOKE_ASYNC(src, PROC_REF(update_hud))
+/datum/antagonist/bloodsucker/proc/on_death(mob/living/source, gibbed)
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, PROC_REF(HandleDeath))
+ RegisterSignal(owner.current, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
+ RegisterSignal(src, COMSIG_BLOODSUCKER_ON_LIFETICK, PROC_REF(HandleDeath))
+
+/datum/antagonist/bloodsucker/proc/on_revive(mob/living/source)
+ UnregisterSignal(owner.current, COMSIG_LIVING_REVIVE)
+ UnregisterSignal(src, COMSIG_BLOODSUCKER_ON_LIFETICK)
+
+/**
+ * ## BLOOD STUFF
+ */
/datum/antagonist/bloodsucker/proc/AddBloodVolume(value)
- owner.current.blood_volume = clamp(owner.current.blood_volume + value, 0, max_blood_volume)
- update_hud()
+ bloodsucker_blood_volume = clamp(bloodsucker_blood_volume + value, 0, max_blood_volume)
+
+/datum/antagonist/bloodsucker/proc/on_examine(datum/source, mob/examiner, examine_text)
+ SIGNAL_HANDLER
+
+ if(!iscarbon(source))
+ return
+ var/mob/living/carbon/carbon_source = source
+ var/vamp_examine = carbon_source.return_vamp_examine(examiner)
+ examine_text += vamp_examine
/datum/antagonist/bloodsucker/proc/AddHumanityLost(value)
if(humanity_lost >= 500)
to_chat(owner.current, span_warning("You hit the maximum amount of lost Humanity, you are far from Human."))
return
- if(my_clan == CLAN_TOREADOR)
+ if(my_clan?.get_clan() == CLAN_TOREADOR)
if(humanity_lost >= TOREADOR_MAX_HUMANITY_LOSS)
to_chat(owner.current, span_warning("Your morals prevent you from becoming more inhuman."))
SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, /datum/mood_event/toreador_inhuman2)
@@ -56,21 +74,15 @@
to_chat(owner.current, span_warning("You feel as if you lost some of your humanity, you will now enter Frenzy at [FRENZY_THRESHOLD_ENTER + humanity_lost * 10] Blood."))
/// mult: SILENT feed is 1/3 the amount
-/datum/antagonist/bloodsucker/proc/HandleFeeding(mob/living/carbon/target, mult=1, power_level)
+/datum/antagonist/bloodsucker/proc/handle_feeding(mob/living/carbon/target, mult=1, power_level)
// Starts at 15 (now 8 since we doubled the Feed time)
var/feed_amount = 15 + (power_level * 2)
var/blood_taken = min(feed_amount, target.blood_volume) * mult
target.blood_volume -= blood_taken
- // Simple Animals lose a LOT of blood, and take damage. This is to keep cats, cows, and so forth from giving you insane amounts of blood.
- if(!ishuman(target))
- target.blood_volume -= (blood_taken / max(target.mob_size, 0.1)) * 3.5 // max() to prevent divide-by-zero
- target.apply_damage_type(blood_taken / 3.5) // Don't do too much damage, or else they die and provide no blood nourishment.
- if(target.blood_volume <= 0)
- target.blood_volume = 0
- target.death(0)
+
///////////
// Shift Body Temp (toward Target's temp, by volume taken)
- owner.current.bodytemperature = ((owner.current.blood_volume * owner.current.bodytemperature) + (blood_taken * target.bodytemperature)) / (owner.current.blood_volume + blood_taken)
+ owner.current.bodytemperature = ((bloodsucker_blood_volume * owner.current.bodytemperature) + (blood_taken * target.bodytemperature)) / (bloodsucker_blood_volume + blood_taken)
// our volume * temp, + their volume * temp, / total volume
///////////
// Reduce Value Quantity
@@ -97,17 +109,15 @@
to_chat(owner, span_warning("[target] is catatonic and won't yield any usable blood for tasks!"))
return blood_taken
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-// HEALING
-
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * ## HEALING
+ */
/// Constantly runs on Bloodsucker's LifeTick, and is increased by being in Torpor/Coffins
/datum/antagonist/bloodsucker/proc/HandleHealing(mult = 1)
var/actual_regen = bloodsucker_regen_rate + additional_regen
// Don't heal if I'm staked or on Masquerade (+ not in a Coffin). Masqueraded Bloodsuckers in a Coffin however, will heal.
- if(owner.current.AmStaked() || (HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && !HAS_TRAIT(owner.current, TRAIT_NODEATH) && my_clan != CLAN_TOREADOR))
+ if(owner.current.am_staked() || (HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && !HAS_TRAIT(owner.current, TRAIT_NODEATH) && my_clan?.get_clan() != CLAN_TOREADOR))
return FALSE
owner.current.adjustCloneLoss(-1 * (actual_regen * 4) * mult, 0)
owner.current.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * (actual_regen * 4) * mult) //adjustBrainLoss(-1 * (actual_regen * 4) * mult, 0)
@@ -120,13 +130,13 @@
/// Checks if you're in a coffin here, additionally checks for Torpor right below it.
var/amInCoffin = istype(user.loc, /obj/structure/closet/crate/coffin)
if(amInCoffin && HAS_TRAIT(user, TRAIT_NODEATH))
- if(HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && my_clan != CLAN_TOREADOR)
+ if(HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && my_clan?.get_clan() != CLAN_TOREADOR)
to_chat(user, span_warning("You will not heal while your Masquerade ability is active."))
return
fireheal = min(user.getFireLoss_nonProsthetic(), actual_regen)
mult *= 8 // Increase multiplier if we're sleeping in a coffin.
costMult *= 0 // No cost if we're sleeping in a coffin.
- user.ExtinguishMob()
+ user.extinguish_mob()
user.remove_all_embedded_objects() // Remove Embedded!
if(check_limbs(costMult))
return TRUE
@@ -145,12 +155,12 @@
var/limb_regen_cost = 50 * -costMult
var/mob/living/carbon/user = owner.current
var/list/missing = user.get_missing_limbs()
- if(missing.len && user.blood_volume < limb_regen_cost + 5)
+ if(missing.len && bloodsucker_blood_volume < limb_regen_cost + 5)
return FALSE
- for(var/targetLimbZone in missing) // 1) Find ONE Limb and regenerate it.
- user.regenerate_limb(targetLimbZone, FALSE) // regenerate_limbs() <--- If you want to EXCLUDE certain parts, do it like this ----> regenerate_limbs(0, list("head"))
- AddBloodVolume(limb_regen_cost)
- var/obj/item/bodypart/missing_bodypart = user.get_bodypart(targetLimbZone) // 2) Limb returns Damaged
+ for(var/missing_limb in missing) //Find ONE Limb and regenerate it.
+ user.regenerate_limb(missing_limb, FALSE)
+ AddBloodVolume(-limb_regen_cost)
+ var/obj/item/bodypart/missing_bodypart = user.get_bodypart(missing_limb) // 2) Limb returns Damaged
missing_bodypart.brute_dam = 60
to_chat(user, span_notice("Your flesh knits as it regrows your [missing_bodypart]!"))
playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE)
@@ -167,7 +177,7 @@
* This is called on Bloodsucker's Assign, and when they end Torpor.
*/
-/datum/antagonist/bloodsucker/proc/HealVampireOrgans()
+/datum/antagonist/bloodsucker/proc/heal_vampire_organs()
var/mob/living/carbon/bloodsuckeruser = owner.current
// Step 1 - Fix basic things, husk and organs.
@@ -175,8 +185,7 @@
bloodsuckeruser.regenerate_organs()
// Step 2 NOTE: Giving passive organ regeneration will cause Torpor to spam /datum/client_colour/monochrome at the Bloodsucker, permanently making them colorblind!
- for(var/all_organs in bloodsuckeruser.internal_organs)
- var/obj/item/organ/organ = all_organs
+ for(var/obj/item/organ/organ as anything in bloodsuckeruser.internal_organs)
organ.setOrganDamage(0)
var/obj/item/organ/heart/current_heart = bloodsuckeruser.getorganslot(ORGAN_SLOT_HEART)
if(!istype(current_heart, /obj/item/organ/heart/vampheart) && !istype(current_heart, /obj/item/organ/heart/demon) && !istype(current_heart, /obj/item/organ/heart/cursed && !istype(current_heart, /obj/item/organ/heart/nightmare)))
@@ -190,24 +199,23 @@
current_eyes.sight_flags = SEE_MOBS
current_eyes.see_in_dark = 8
current_eyes.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
- if(my_clan == CLAN_LASOMBRA && ishuman(bloodsuckeruser))
+ current_eyes.setOrganDamage(0) //making sure
+ if(my_clan?.get_clan() == CLAN_LASOMBRA && ishuman(bloodsuckeruser))
var/mob/living/carbon/human/bloodsucker = bloodsuckeruser
- bloodsucker.eye_color = "f00"
+ bloodsucker.eye_color = BLOODCULT_EYE
bloodsuckeruser.update_body()
bloodsuckeruser.update_sight()
// Step 3
if(bloodsuckeruser.stat == DEAD)
bloodsuckeruser.revive(full_heal = FALSE, admin_revive = FALSE)
- for(var/i in bloodsuckeruser.all_wounds)
- var/datum/wound/iter_wound = i
+ for(var/datum/wound/iter_wound as anything in bloodsuckeruser.all_wounds)
iter_wound.remove_wound()
// From [powers/panacea.dm]
var/list/bad_organs = list(
bloodsuckeruser.getorgan(/obj/item/organ/body_egg),
bloodsuckeruser.getorgan(/obj/item/organ/zombie_infection))
- for(var/tumors in bad_organs)
- var/obj/item/organ/yucky_organs = tumors
+ for(var/obj/item/organ/yucky_organs as anything in bad_organs)
if(!istype(yucky_organs))
continue
yucky_organs.Remove(bloodsuckeruser)
@@ -224,110 +232,117 @@
/// FINAL DEATH
/datum/antagonist/bloodsucker/proc/HandleDeath()
// Not "Alive"?
- if(!owner.current || !get_turf(owner.current))
+ if(!owner.current)
FinalDeath()
return
- // Fire Damage? (above double health)
- if(owner.current.getFireLoss() >= owner.current.maxHealth * 2.5)
+ // Fire Damage and Fledgeling? (above double health)
+ if(owner.current.getFireLoss() >= owner.current.maxHealth * 2 && bloodsucker_level < 4)
FinalDeath()
return
- // Staked while "Temp Death" or Asleep
- if(owner.current.StakeCanKillMe() && owner.current.AmStaked())
+ // Fire Damage and Daytime?
+ if(owner.current.getFireLoss() >= owner.current.maxHealth * 2 && (SSsunlight.sunlight_active || frenzied))
FinalDeath()
return
- // Not organic/living? (Zombie/Skeleton/Plasmaman)
- if(!(owner.current.mob_biotypes & MOB_ORGANIC))
+ // Staked while "Temp Death" or Asleep
+ if(owner.current.StakeCanKillMe() && owner.current.am_staked())
FinalDeath()
return
// Temporary Death? Convert to Torpor.
- if(owner.current.stat == DEAD)
- var/mob/living/carbon/human/dead_bloodsucker = owner.current
- if(!HAS_TRAIT(dead_bloodsucker, TRAIT_NODEATH))
- to_chat(dead_bloodsucker, span_danger("Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor."))
- Check_Begin_Torpor(TRUE)
+ if(HAS_TRAIT(owner.current, TRAIT_NODEATH))
+ return
+ to_chat(owner.current, span_danger("Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor."))
+ check_begin_torpor(TRUE)
+
/datum/antagonist/bloodsucker/proc/HandleStarving() // I am thirsty for blood!
// Nutrition - The amount of blood is how full we are.
- owner.current.set_nutrition(min(owner.current.blood_volume, NUTRITION_LEVEL_FED))
+ owner.current.set_nutrition(min(bloodsucker_blood_volume, NUTRITION_LEVEL_FED))
// BLOOD_VOLUME_GOOD: [336] - Pale
// handled in bloodsucker_integration.dm
// BLOOD_VOLUME_EXIT: [560] - Exit Frenzy (If in one) This is high because we want enough to kill the poor soul they feed off of.
- if(owner.current.blood_volume >= (FRENZY_THRESHOLD_EXIT + humanity_lost * 10) && frenzied)
+ if(bloodsucker_blood_volume >= (FRENZY_THRESHOLD_EXIT + humanity_lost * 10) && frenzied)
owner.current.remove_status_effect(STATUS_EFFECT_FRENZY)
// BLOOD_VOLUME_BAD: [224] - Jitter
- if(owner.current.blood_volume < BLOOD_VOLUME_BAD(owner.current) && prob(0.5) && !HAS_TRAIT(owner.current, TRAIT_NODEATH) && !HAS_TRAIT(owner.current, TRAIT_MASQUERADE))
- owner.current.Jitter(3)
+ if(bloodsucker_blood_volume < BLOOD_VOLUME_BAD(owner.current) && prob(0.5) && !HAS_TRAIT(owner.current, TRAIT_NODEATH) && !HAS_TRAIT(owner.current, TRAIT_MASQUERADE))
+ owner.current.adjust_jitter(3 SECONDS)
// BLOOD_VOLUME_SURVIVE: [122] - Blur Vision
- if(owner.current.blood_volume < BLOOD_VOLUME_SURVIVE(owner.current))
- owner.current.blur_eyes(8 - 8 * (owner.current.blood_volume / BLOOD_VOLUME_BAD(owner.current)))
+ if(bloodsucker_blood_volume < BLOOD_VOLUME_SURVIVE(owner.current))
+ owner.current.blur_eyes((8 - 8 * (bloodsucker_blood_volume / BLOOD_VOLUME_BAD(owner.current)))* 2 SECONDS)
// The more blood, the better the Regeneration, get too low blood, and you enter Frenzy.
- if(owner.current.blood_volume < (FRENZY_THRESHOLD_ENTER + humanity_lost * 10) && !frenzied)
+ if(bloodsucker_blood_volume < (FRENZY_THRESHOLD_ENTER + humanity_lost * 10) && !frenzied)
if(!iscarbon(owner.current))
return
- if(owner.current.stat == DEAD)
- HandleDeath()
- return
- enter_frenzy()
- else if(owner.current.blood_volume < BLOOD_VOLUME_BAD(owner.current))
+ owner.current.apply_status_effect(/datum/status_effect/frenzy)
+ else if(bloodsucker_blood_volume < BLOOD_VOLUME_BAD(owner.current))
additional_regen = 0.1
- else if(owner.current.blood_volume < BLOOD_VOLUME_OKAY(owner.current))
+ else if(bloodsucker_blood_volume < BLOOD_VOLUME_OKAY(owner.current))
additional_regen = 0.2
- else if(owner.current.blood_volume < BLOOD_VOLUME_NORMAL(owner.current))
+ else if(bloodsucker_blood_volume < BLOOD_VOLUME_NORMAL(owner.current))
additional_regen = 0.3
- else if(owner.current.blood_volume < BS_BLOOD_VOLUME_MAX_REGEN)
+ else if(bloodsucker_blood_volume < BS_BLOOD_VOLUME_MAX_REGEN)
additional_regen = 0.4
else
additional_regen = 0.5
-/datum/antagonist/bloodsucker/proc/enter_frenzy()
- if(my_clan == CLAN_GANGREL)
- var/mob/living/carbon/user = owner.current
- switch(frenzies)
- if(0)
- owner.current.apply_status_effect(STATUS_EFFECT_FRENZY)
- return
- if(1)
- to_chat(owner, span_warning("You start feeling hungrier, you feel like a normal frenzy won't satiate it enough anymore."))
- owner.current.apply_status_effect(STATUS_EFFECT_FRENZY)
- return
- if(2 to INFINITY)
- AddBloodVolume(FRENZY_THRESHOLD_EXIT + humanity_lost * 10 - user.blood_volume) //so it doesn't happen multiple times and refills your blood when you get out again
- if(!do_mob(user, user, 2 SECONDS, TRUE))
- return
- playsound(user.loc, 'sound/weapons/slash.ogg', 25, TRUE)
- to_chat(user, span_warning("You skin rips and tears."))
- if(!do_mob(user, user, 1 SECONDS, TRUE))
- return
- playsound(user.loc, 'sound/weapons/slashmiss.ogg', 25, TRUE)
- to_chat(user, span_warning("You heart pumps blackened blood into your veins as your skin turns into fur."))
- if(!do_mob(user, user, 1 SECONDS, TRUE))
- return
- playsound(user.loc, 'sound/weapons/slice.ogg', 25, TRUE)
- to_chat(user, span_boldnotice("YOU HAVE AWOKEN."))
- var/mob/living/simple_animal/hostile/bloodsucker/werewolf/ww
- if(!ww || ww.stat == DEAD)
- ww = new /mob/living/simple_animal/hostile/bloodsucker/werewolf(user.loc)
- user.forceMove(ww)
- ww.bloodsucker = user
- user.status_flags |= GODMODE
- user.mind.transfer_to(ww)
- var/list/wolf_powers = list(new /datum/action/bloodsucker/targeted/feast,)
- for(var/datum/action/bloodsucker/power in powers)
- if(istype(power, /datum/action/bloodsucker/fortitude))
- wolf_powers += new /datum/action/bloodsucker/gangrel/wolfortitude
- if(istype(power, /datum/action/bloodsucker/targeted/lunge))
- wolf_powers += new /datum/action/bloodsucker/targeted/pounce
- if(istype(power, /datum/action/bloodsucker/cloak))
- wolf_powers += new /datum/action/bloodsucker/gangrel/howl
- if(istype(power, /datum/action/bloodsucker/targeted/trespass))
- wolf_powers += new /datum/action/bloodsucker/gangrel/rabidism
- for(var/datum/action/bloodsucker/power in wolf_powers)
- power.Grant(ww)
- frenzies++
- else
- owner.current.apply_status_effect(STATUS_EFFECT_FRENZY)
+/// Cycle through all vamp antags and check if they're inside a closet.
+/datum/antagonist/bloodsucker/proc/handle_sol()
+ SIGNAL_HANDLER
+ if(!owner)
+ return
+
+ if(!istype(owner.current.loc, /obj/structure))
+ if(COOLDOWN_FINISHED(src, bloodsucker_spam_sol_burn))
+ if(bloodsucker_level > 0)
+ to_chat(owner, span_userdanger("The solar flare sets your skin ablaze!"))
+ else
+ to_chat(owner, span_userdanger("The solar flare scalds your neophyte skin!"))
+ COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol
+
+ if(owner.current.fire_stacks <= 0)
+ owner.current.fire_stacks = 0
+ if(bloodsucker_level > 0)
+ owner.current.adjust_fire_stacks(0.2 + bloodsucker_level / 10)
+ owner.current.ignite_mob()
+ owner.current.adjustFireLoss(2 + (bloodsucker_level / 2))
+ owner.current.updatehealth()
+ SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_2)
+ return
+
+ if(istype(owner.current.loc, /obj/structure/closet/crate/coffin)) // Coffins offer the BEST protection
+ if(owner.current.am_staked() && COOLDOWN_FINISHED(src, bloodsucker_spam_sol_burn))
+ to_chat(owner.current, span_userdanger("You are staked! Remove the offending weapon from your heart before sleeping."))
+ COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol
+ if(!HAS_TRAIT(owner.current, TRAIT_NODEATH))
+ check_begin_torpor(TRUE)
+ SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/coffinsleep)
+ return
+
+ if(COOLDOWN_FINISHED(src, bloodsucker_spam_sol_burn)) // Closets offer SOME protection
+ to_chat(owner, span_warning("Your skin sizzles. [owner.current.loc] doesn't protect well against UV bombardment."))
+ COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol
+ owner.current.adjustFireLoss(0.5 + (bloodsucker_level / 4))
+ owner.current.updatehealth()
+ SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_1)
+
+/datum/antagonist/bloodsucker/proc/give_warning(atom/source, danger_level, vampire_warning_message, vassal_warning_message)
+ SIGNAL_HANDLER
+ if(!owner)
+ return
+ to_chat(owner, vampire_warning_message)
+
+ switch(danger_level)
+ if(DANGER_LEVEL_FIRST_WARNING)
+ owner.current.playsound_local(null, 'sound/effects/griffin_3.ogg', 50, 1)
+ if(DANGER_LEVEL_SECOND_WARNING)
+ owner.current.playsound_local(null, 'sound/effects/griffin_5.ogg', 50, 1)
+ if(DANGER_LEVEL_THIRD_WARNING)
+ owner.current.playsound_local(null, 'sound/effects/alert.ogg', 75, 1)
+ if(DANGER_LEVEL_SOL_ROSE)
+ owner.current.playsound_local(null, 'sound/ambience/ambimystery.ogg', 100, 1)
+ if(DANGER_LEVEL_SOL_ENDED)
+ owner.current.playsound_local(null, 'sound/spookoween/ghosty_wind.ogg', 90, 1)
/**
* # Torpor
@@ -337,60 +352,65 @@
* You cannot manually exit Torpor, it is instead entered/exited by:
*
* Torpor is triggered by:
- * - Being in a Coffin while Sol is on, dealt with by /HandleTorpor()
+ * - Being in a Coffin while Sol is on, dealt with by Sol
* - Entering a Coffin with more than 10 combined Brute/Burn damage, dealt with by /closet/crate/coffin/close() [bloodsucker_coffin.dm]
* - Death, dealt with by /HandleDeath()
* Torpor is ended by:
- * - Having less than 10 Brute damage while OUTSIDE of your Coffin while it isnt Sol, dealt with by /HandleTorpor()
- * - Having less than 10 Brute & Burn Combined while INSIDE of your Coffin while it isnt Sol, dealt with by /HandleTorpor()
+ * - Having less than 10 Brute damage while OUTSIDE of your Coffin while it isnt Sol.
+ * - Having less than 10 Brute & Burn Combined while INSIDE of your Coffin while it isnt Sol.
* - Sol being over, dealt with by /sunlight/process() [bloodsucker_daylight.dm]
*/
-/datum/antagonist/bloodsucker/proc/HandleTorpor()
- if(!owner.current)
- return
- if(istype(owner.current.loc, /obj/structure/closet/crate/coffin))
- if(!HAS_TRAIT(owner.current, TRAIT_NODEATH))
- /// Staked? Dont heal
- if(owner.current.AmStaked())
- to_chat(owner.current, span_userdanger("You are staked! Remove the offending weapon from your heart before sleeping."))
- return
- /// Otherwise, check if it's Sol, to enter Torpor.
- if(clan.bloodsucker_sunlight.amDay)
- Check_Begin_Torpor(TRUE)
- if(HAS_TRAIT(owner.current, TRAIT_NODEATH)) // Check so I don't go insane.
- Check_End_Torpor()
-
-/datum/antagonist/bloodsucker/proc/Check_Begin_Torpor(SkipChecks = FALSE)
+/**
+ * # Assigning Sol
+ *
+ * Sol is the sunlight, during this period, all Bloodsuckers must be in their coffin, else they burn.
+ */
+
+/// Start Sol, called when someone is assigned Bloodsucker
+/datum/antagonist/bloodsucker/proc/check_start_sunlight()
+ var/list/existing_suckers = get_antag_minds(/datum/antagonist/bloodsucker) - owner
+ if(!length(existing_suckers))
+ message_admins("New Sol has been created due to Bloodsucker assignment.")
+ SSsunlight.can_fire = TRUE
+
+/// End Sol, if you're the last Bloodsucker
+/datum/antagonist/bloodsucker/proc/check_cancel_sunlight()
+ var/list/existing_suckers = get_antag_minds(/datum/antagonist/bloodsucker) - owner
+ if(!length(existing_suckers))
+ message_admins("Sol has been deleted due to the lack of Bloodsuckers")
+ SSsunlight.can_fire = FALSE
+
+/datum/antagonist/bloodsucker/proc/check_begin_torpor(SkipChecks = FALSE)
/// Are we entering Torpor via Sol/Death? Then entering it isnt optional!
if(SkipChecks)
- Torpor_Begin()
- return
+ torpor_begin()
var/mob/living/carbon/user = owner.current
var/total_brute = user.getBruteLoss_nonProsthetic()
var/total_burn = user.getFireLoss_nonProsthetic()
var/total_damage = total_brute + total_burn
/// Checks - Not daylight & Has more than 10 Brute/Burn & not already in Torpor
- if(!clan.bloodsucker_sunlight.amDay && total_damage >= 10 && !HAS_TRAIT(owner.current, TRAIT_NODEATH))
- Torpor_Begin()
+ if(!SSsunlight.sunlight_active && total_damage >= 10 && !HAS_TRAIT(owner.current, TRAIT_NODEATH))
+ torpor_begin()
-/datum/antagonist/bloodsucker/proc/Check_End_Torpor()
+/datum/antagonist/bloodsucker/proc/check_end_torpor()
var/mob/living/carbon/user = owner.current
var/total_brute = user.getBruteLoss_nonProsthetic()
var/total_burn = user.getFireLoss_nonProsthetic()
var/total_damage = total_brute + total_burn
+ if(total_burn >= 199)
+ return
+ if(SSsunlight.sunlight_active)
+ return
// You are in a Coffin, so instead we'll check TOTAL damage, here.
if(istype(user.loc, /obj/structure/closet/crate/coffin))
- if(!clan.bloodsucker_sunlight.amDay && total_damage <= 10)
- Torpor_End()
- // You're not in a Coffin? We won't check for low Burn damage
- else if(!clan.bloodsucker_sunlight.amDay && total_brute <= 10)
- // You're under 10 brute, but over 200 Burn damage? Don't exit Torpor, to prevent spam revival/death. Only way out is healing that Burn.
- if(total_burn >= 199)
- return
- Torpor_End()
+ if(total_damage <= 10)
+ torpor_end()
+ else
+ if(total_brute <= 10)
+ torpor_end()
-/datum/antagonist/bloodsucker/proc/Torpor_Begin()
+/datum/antagonist/bloodsucker/proc/torpor_begin()
var/mob/living/carbon/human/bloodsucker = owner.current
to_chat(owner.current, span_notice("You enter the horrible slumber of deathless Torpor. You will heal until you are renewed."))
/// Force them to go to sleep
@@ -401,33 +421,53 @@
ADD_TRAIT(owner.current, TRAIT_DEATHCOMA, BLOODSUCKER_TRAIT)
ADD_TRAIT(owner.current, TRAIT_RESISTLOWPRESSURE, BLOODSUCKER_TRAIT)
bloodsucker.physiology.brute_mod *= 0
- owner.current.Jitter(0)
+ bloodsucker.physiology.burn_mod *= 0.75
+ owner.current.set_timed_status_effect(0 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE)
/// Disable ALL Powers
DisableAllPowers()
-/datum/antagonist/bloodsucker/proc/Torpor_End()
+/datum/antagonist/bloodsucker/proc/torpor_end()
var/mob/living/carbon/human/bloodsucker = owner.current
owner.current.grab_ghost()
to_chat(owner.current, span_warning("You have recovered from Torpor."))
- if(my_clan == CLAN_LASOMBRA)
- bloodsucker.physiology.brute_mod *= 0
- else
- bloodsucker.physiology.brute_mod = initial(bloodsucker.physiology.brute_mod)
+ bloodsucker.physiology.brute_mod = initial(bloodsucker.physiology.brute_mod)
+ bloodsucker.physiology.burn_mod = initial(bloodsucker.physiology.brute_mod)
REMOVE_TRAIT(owner.current, TRAIT_RESISTLOWPRESSURE, BLOODSUCKER_TRAIT)
REMOVE_TRAIT(owner.current, TRAIT_DEATHCOMA, BLOODSUCKER_TRAIT)
REMOVE_TRAIT(owner.current, TRAIT_FAKEDEATH, BLOODSUCKER_TRAIT)
REMOVE_TRAIT(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT)
ADD_TRAIT(owner.current, TRAIT_SLEEPIMMUNE, BLOODSUCKER_TRAIT)
- HealVampireOrgans()
+ heal_vampire_organs()
+
+ SEND_SIGNAL(src, BLOODSUCKER_EXIT_TORPOR)
+
+/// Makes your blood_volume look like your bloodsucker blood, unless you're Masquerading.
+/datum/antagonist/bloodsucker/proc/update_blood()
+ if(!iscarbon(owner.current))
+ return
+ var/mob/living/carbon/bloodsucker = owner.current
+ if(LAZYFIND(bloodsucker.dna.species.species_traits, NOBLOOD))
+ return
+ //If we're on Masquerade, we appear to have full blood, unless we are REALLY low, in which case we don't look as bad.
+ if(HAS_TRAIT(owner.current, TRAIT_MASQUERADE))
+ switch(bloodsucker_blood_volume)
+ if(BLOOD_VOLUME_OKAY(owner.current) to INFINITY) // 336 and up, we are perfectly fine.
+ owner.current.blood_volume = initial(bloodsucker_blood_volume)
+ if(BLOOD_VOLUME_BAD(owner.current) to BLOOD_VOLUME_OKAY(owner.current)) // 224 to 336
+ owner.current.blood_volume = BLOOD_VOLUME_SAFE(owner.current)
+ else // 224 and below
+ owner.current.blood_volume = BLOOD_VOLUME_OKAY(owner.current)
+ return
+ owner.current.blood_volume = bloodsucker_blood_volume
/// Gibs the Bloodsucker, roundremoving them.
/datum/antagonist/bloodsucker/proc/FinalDeath()
- FreeAllVassals()
// If we have no body, end here.
if(!owner.current)
return
- DisableAllPowers()
+ free_all_vassals()
+ DisableAllPowers(forced = TRUE)
if(!iscarbon(owner.current))
owner.current.gib(TRUE, FALSE, FALSE)
return
@@ -437,19 +477,23 @@
owner.current.unequip_everything()
user.remove_all_embedded_objects()
playsound(owner.current, 'sound/effects/tendril_destroyed.ogg', 40, TRUE)
- // Elders get dusted, Fledglings get gibbed
+ var/unique_death = SEND_SIGNAL(src, BLOODSUCKER_FINAL_DEATH)
+ if(unique_death & DONT_DUST)
+ return
+
+ // Elders get dusted, Fledglings get gibbed.
if(bloodsucker_level >= 4)
- owner.current.visible_message(
- span_warning("[owner.current]'s skin crackles and dries, their skin and bones withering to dust. A hollow cry whips from what is now a sandy pile of remains."),
+ user.visible_message(
+ span_warning("[user]'s skin crackles and dries, their skin and bones withering to dust. A hollow cry whips from what is now a sandy pile of remains."),
span_userdanger("Your soul escapes your withering body as the abyss welcomes you to your Final Death."),
span_hear("You hear a dry, crackling sound."))
- addtimer(CALLBACK(owner.current, TYPE_PROC_REF(/mob/living, dust)), 5 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
+ addtimer(CALLBACK(user, TYPE_PROC_REF(/mob/living, dust)), 5 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
else
- owner.current.visible_message(
- span_warning("[owner.current]'s skin bursts forth in a spray of gore and detritus. A horrible cry echoes from what is now a wet pile of decaying meat."),
+ user.visible_message(
+ span_warning("[user]'s skin bursts forth in a spray of gore and detritus. A horrible cry echoes from what is now a wet pile of decaying meat."),
span_userdanger("Your soul escapes your withering body as the abyss welcomes you to your Final Death."),
span_hear("You hear a wet, bursting sound."))
- owner.current.gib(TRUE, FALSE, FALSE)
+ user.gib(TRUE, FALSE, FALSE)
// Bloodsuckers moodlets //
diff --git a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_recipes.dm b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_recipes.dm
index e69ee3913808..cbe6d872d6d3 100644
--- a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_recipes.dm
+++ b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_recipes.dm
@@ -3,6 +3,7 @@
/////////////////////////
/// Coffins ///
/////////////////////////
+
/datum/crafting_recipe/blackcoffin
name = "Black Coffin"
result = /obj/structure/closet/crate/coffin/blackcoffin
@@ -52,6 +53,7 @@
////////////////////////////
/// Structures ///
////////////////////////////
+
/datum/crafting_recipe/bloodaltar
name = "Blood Altar"
result = /obj/structure/bloodsucker/bloodaltar
@@ -117,6 +119,18 @@
category = CAT_STRUCTURES
always_available = FALSE
+/datum/crafting_recipe/moldingstone
+ name = "Molding Stone"
+ result = /obj/structure/bloodsucker/moldingstone
+ tools = list(TOOL_WELDER, /obj/item/bloodsucker/chisel)
+ reqs = list(
+ /obj/item/stack/sheet/metal = 5,
+ /obj/item/stack/rods = 6,
+ )
+ time = 8 SECONDS
+ category = CAT_STRUCTURES
+ always_available = FALSE
+
/*
/datum/crafting_recipe/bloodthrone
name = "Blood Throne"
@@ -147,6 +161,7 @@
////////////////////////
/// Stakes ///
////////////////////////
+
/datum/crafting_recipe/stake
name = "Stake"
result = /obj/item/stake
@@ -190,3 +205,30 @@
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
always_available = FALSE
+
+////////////////////////
+/// Tools ///
+////////////////////////
+
+/datum/crafting_recipe/chisel
+ name = "Chisel"
+ result = /obj/item/bloodsucker/chisel
+ tools = list(TOOL_WELDER, TOOL_SCREWDRIVER)
+ reqs = list(
+ /obj/item/stack/sheet/metal = 3
+ )
+ time = 5 SECONDS
+ category = CAT_TOOLS
+ always_available = FALSE
+
+/*/datum/crafting_recipe/bloodybrush
+ name = "Artist's Brush"
+ result = /obj/item/bloodsucker/bloodybrush
+ tools = list(TOOL_HATCHET)
+ reqs = list(
+ /obj/item/stack/sheet/mineral/wood = 2,
+ /obj/item/stack/sheet/cloth = 1,
+ )
+ time = 5 SECONDS
+ category = CAT_TOOLS
+ always_available = FALSE*/
diff --git a/code/modules/antagonists/bloodsuckers/subsystem_sunlight.dm b/code/modules/antagonists/bloodsuckers/subsystem_sunlight.dm
new file mode 100644
index 000000000000..e5336dd67d4d
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/subsystem_sunlight.dm
@@ -0,0 +1,90 @@
+/// 40 - 65 seconds
+#define TIME_BLOODSUCKER_DAY (rand(40, 65))
+/// 10 - 15 minutes
+#define TIME_BLOODSUCKER_NIGHT 720
+/// 1.5 minutes
+#define TIME_BLOODSUCKER_DAY_WARN 90
+/// 30 seconds
+#define TIME_BLOODSUCKER_DAY_FINAL_WARN 30
+/// 5 seconds
+#define TIME_BLOODSUCKER_BURN_INTERVAL 5
+
+///How much time Sol can be 'off' by, keeping the time inconsistent.
+#define TIME_BLOODSUCKER_SOL_DELAY 180
+
+SUBSYSTEM_DEF(sunlight)
+ name = "Sol"
+ can_fire = FALSE
+ wait = 2 SECONDS
+ flags = SS_NO_INIT | SS_BACKGROUND | SS_TICKER
+
+ ///If the Sun is currently out our not.
+ var/sunlight_active = FALSE
+ ///The time between the next cycle, randomized every night.
+ var/time_til_cycle = TIME_BLOODSUCKER_NIGHT
+ ///If Bloodsucker levels for the night has been given out yet.
+ var/issued_XP = FALSE
+
+
+/datum/controller/subsystem/sunlight/fire(resumed = FALSE)
+ time_til_cycle--
+ if(sunlight_active)
+ if(time_til_cycle)
+ SEND_SIGNAL(src, COMSIG_SOL_RISE_TICK)
+ if(!issued_XP && time_til_cycle <= 15)
+ issued_XP = TRUE
+ SEND_SIGNAL(src, COMSIG_SOL_RANKUP_BLOODSUCKERS)
+ if(time_til_cycle <= 1)
+ sunlight_active = FALSE
+ issued_XP = FALSE
+ //randomize the next sol timer
+ time_til_cycle = round(rand((TIME_BLOODSUCKER_NIGHT-TIME_BLOODSUCKER_SOL_DELAY), (TIME_BLOODSUCKER_NIGHT+TIME_BLOODSUCKER_SOL_DELAY)), 1)
+ message_admins("BLOODSUCKER NOTICE: Daylight Ended. Resetting to Night (Lasts for [round(time_til_cycle / 60, 1)] minutes.)")
+ SEND_SIGNAL(src, COMSIG_SOL_END)
+ warn_daylight(
+ danger_level = DANGER_LEVEL_SOL_ENDED,
+ vampire_warning_message = span_announce("The solar flare has ended, and the daylight danger has passed... for now."),
+ vassal_warning_message = span_announce("The solar flare has ended, and the daylight danger has passed... for now."),
+ )
+ return
+
+ switch(time_til_cycle)
+ if(TIME_BLOODSUCKER_DAY_WARN)
+ SEND_SIGNAL(src, COMSIG_SOL_NEAR_START)
+ warn_daylight(
+ danger_level = DANGER_LEVEL_FIRST_WARNING,
+ vampire_warning_message = span_danger("Solar Flares will bombard the station with dangerous UV radiation in [TIME_BLOODSUCKER_DAY_WARN / 60] minutes. Prepare to seek cover in a coffin or closet."),
+ )
+ if(TIME_BLOODSUCKER_DAY_FINAL_WARN)
+ message_admins("BLOODSUCKER NOTICE: Daylight beginning in [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds.")
+ warn_daylight(
+ danger_level = DANGER_LEVEL_SECOND_WARNING,
+ vampire_warning_message = span_userdanger("Solar Flares are about to bombard the station! You have [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds to find cover!"),
+ vassal_warning_message = span_danger("In [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds, your master will be at risk of a Solar Flare. Make sure they find cover!"),
+ )
+ if(TIME_BLOODSUCKER_BURN_INTERVAL)
+ warn_daylight(
+ danger_level = DANGER_LEVEL_THIRD_WARNING,
+ vampire_warning_message = span_userdanger("Seek cover, for Sol rises!"),
+ )
+ if(NONE)
+ sunlight_active = TRUE
+ //set the timer to countdown daytime now.
+ time_til_cycle = TIME_BLOODSUCKER_DAY
+ message_admins("BLOODSUCKER NOTICE: Daylight Beginning (Lasts for [round(TIME_BLOODSUCKER_DAY / 60, 1)] minutes.)")
+ warn_daylight(
+ danger_level = DANGER_LEVEL_SOL_ROSE,
+ vampire_warning_message = span_userdanger("Solar flares bombard the station with deadly UV light! Stay in cover for the next [round(TIME_BLOODSUCKER_DAY / 60, 1)] minutes or risk Final Death!"),
+ vassal_warning_message = span_userdanger("Solar flares bombard the station with UV light!"),
+ )
+
+/datum/controller/subsystem/sunlight/proc/warn_daylight(danger_level, vampire_warning_message, vassal_warning_message)
+ SEND_SIGNAL(src, COMSIG_SOL_WARNING_GIVEN, danger_level, vampire_warning_message, vassal_warning_message)
+
+#undef TIME_BLOODSUCKER_SOL_DELAY
+
+#undef TIME_BLOODSUCKER_DAY
+#undef TIME_BLOODSUCKER_NIGHT
+#undef TIME_BLOODSUCKER_DAY_WARN
+#undef TIME_BLOODSUCKER_DAY_FINAL_WARN
+#undef TIME_BLOODSUCKER_BURN_INTERVAL
diff --git a/code/modules/antagonists/bloodsuckers/vassal.dm b/code/modules/antagonists/bloodsuckers/vassal.dm
deleted file mode 100644
index 6e9426e8c97e..000000000000
--- a/code/modules/antagonists/bloodsuckers/vassal.dm
+++ /dev/null
@@ -1,224 +0,0 @@
-#define VASSAL_SCAN_MIN_DISTANCE 5
-#define VASSAL_SCAN_MAX_DISTANCE 500
-/// 2s update time.
-#define VASSAL_SCAN_PING_TIME (2 SECONDS)
-
-/datum/antagonist/vassal
- name = "\improper Vassal"
- roundend_category = "vassals"
- antagpanel_category = "Bloodsucker"
- job_rank = ROLE_BLOODSUCKER
- show_in_roundend = FALSE
-
- /// The Master Bloodsucker's antag datum.
- var/datum/antagonist/bloodsucker/master
- var/datum/game_mode/blooodsucker
- /// List of all Purchased Powers, like Bloodsuckers.
- var/list/datum/action/powers = list()
- /// The favorite vassal gets unique features.
- var/favorite_vassal = FALSE
- /// Bloodsucker levels, but for Vassals.
- var/vassal_level
-
-/datum/antagonist/vassal/antag_panel_data()
- return "Master : [master.owner.name]"
-
-/datum/antagonist/vassal/apply_innate_effects(mob/living/mob_override)
- var/mob/living/current_mob = mob_override || owner.current
- current_mob.apply_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
-
-/datum/antagonist/vassal/remove_innate_effects(mob/living/mob_override)
- var/mob/living/current_mob = mob_override || owner.current
- current_mob.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
-
-
-/datum/antagonist/vassal/on_gain()
- /// Enslave them to their Master
- if(master)
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = master.owner.has_antag_datum(/datum/antagonist/bloodsucker)
- if(bloodsuckerdatum)
- bloodsuckerdatum.vassals |= src
- owner.enslave_mind_to_creator(master.owner.current)
- owner.current.log_message("has been vassalized by [master.owner.current]!", LOG_ATTACK, color="#960000")
- /// Give Recuperate Power
- BuyPower(new /datum/action/bloodsucker/recuperate)
- /// Give Objectives
- var/datum/objective/bloodsucker/vassal/vassal_objective = new
- vassal_objective.owner = owner
- objectives += vassal_objective
- /// Give Vampire Language & Hud
- owner.current.grant_all_languages(FALSE, FALSE, TRUE)
- owner.current.grant_language(/datum/language/vampiric)
- update_vassal_icons_added(owner.current)
- . = ..()
-
-/datum/antagonist/vassal/on_removal()
- //Free them from their Master
- if(master && master.owner)
- master.vassals -= src
- owner.enslaved_to = null
- for(var/all_status_traits in owner.current.status_traits)
- REMOVE_TRAIT(owner.current, all_status_traits, BLOODSUCKER_TRAIT)
- //Remove Recuperate Power
- while(powers.len)
- var/datum/action/bloodsucker/power = pick(powers)
- powers -= power
- power.Remove(owner.current)
- //Remove Language & Hud
- owner.current.remove_language(/datum/language/vampiric)
- update_vassal_icons_removed(owner.current)
- UnregisterSignal(master, COMSIG_BLOODSUCKER_RANKS_SPENT)
- return ..()
-
-/datum/antagonist/vassal/on_body_transfer(mob/living/old_body, mob/living/new_body)
- . = ..()
- for(var/datum/action/bloodsucker/all_powers as anything in powers)
- all_powers.Remove(old_body)
- all_powers.Grant(new_body)
-
-
-/datum/antagonist/vassal/proc/add_objective(datum/objective/added_objective)
- objectives += added_objective
-
-/datum/antagonist/vassal/proc/remove_objectives(datum/objective/removed_objective)
- objectives -= removed_objective
-
-/datum/antagonist/vassal/greet()
- . = ..()
- to_chat(owner, span_userdanger("You are now the mortal servant of [master.owner.current], a Bloodsucker!"))
- to_chat(owner, span_boldannounce("The power of [master.owner.current.p_their()] immortal blood compels you to obey [master.owner.current.p_them()] in all things, even offering your own life to prolong theirs.\n\
- You are not required to obey any other Bloodsucker, for only [master.owner.current] is your master. The laws of Nanotrasen do not apply to you now; only your vampiric master's word must be obeyed."))
- owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
- antag_memory += "You have been vassalized, becoming the mortal servant of [master.owner.current], a bloodsucking vampire!
"
- /// Message told to your Master.
- to_chat(master.owner, span_userdanger("[owner.current] has become addicted to your immortal blood. [owner.current.p_they(TRUE)] [owner.current.p_are()] now your undying servant!"))
- master.owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
-
-/datum/antagonist/vassal/farewell()
- owner.current.visible_message(
- span_deconversion_message("[owner.current]'s eyes dart feverishly from side to side, and then stop. [owner.current.p_they(TRUE)] seem[owner.current.p_s()] calm, \
- like [owner.current.p_they()] [owner.current.p_have()] regained some lost part of [owner.current.p_them()]self."),
- )
- to_chat(owner, span_deconversion_message("With a snap, you are no longer enslaved to [master.owner]! You breathe in heavily, having regained your free will."))
- owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
- /// Message told to your (former) Master.
- if(master && master.owner)
- to_chat(master.owner, span_cultbold("You feel the bond with your vassal [owner.current] has somehow been broken!"))
-
-/// Called when we are made into the Favorite Vassal
-/datum/antagonist/vassal/proc/make_favorite(mob/living/master)
- // Default stuff for all
- favorite_vassal = TRUE
- set_antag_hud(owner.current, "vassal6")
- to_chat(master, span_danger("You have turned [owner.current] into your Favorite Vassal! They will no longer be deconverted upon Mindshielding!"))
- to_chat(owner, span_notice("As Blood drips over your body, you feel closer to your Master... You are now the Favorite Vassal!"))
- var/datum/antagonist/bloodsucker/bloodsuckerdatum = master.mind.has_antag_datum(/datum/antagonist/bloodsucker)
- var/mob/living/carbon/human/vassal = owner.current
- switch(bloodsuckerdatum.my_clan)
- if(CLAN_GANGREL)
- var/obj/effect/proc_holder/spell/targeted/shapeshift/bat/batform = new
- owner.AddSpell(batform)
- if(CLAN_LASOMBRA)
- if(ishuman(owner.current))
- vassal.see_in_dark = 8
- vassal.eye_color = "f00"
- var/list/powers = list(new /obj/effect/proc_holder/spell/targeted/lesser_glare, new /obj/effect/proc_holder/spell/targeted/shadowwalk)
- for(var/obj/effect/proc_holder/spell/targeted/power in powers)
- owner.AddSpell(power)
- if(CLAN_TZIMISCE)
- if(!do_mob(master, owner.current, 1 SECONDS, TRUE))
- return
- playsound(vassal.loc, 'sound/weapons/slash.ogg', 50, TRUE, -1)
- if(!do_mob(master, owner.current, 1 SECONDS, TRUE))
- return
- playsound(vassal.loc, 'sound/effects/splat.ogg', 50, TRUE)
- vassal.set_species(/datum/species/szlachta)
- if(CLAN_TOREADOR)
- BuyPower(/datum/action/bloodsucker/targeted/mesmerize)
- RegisterSignal(master, COMSIG_BLOODSUCKER_RANKS_SPENT, PROC_REF(toreador_levelup_mesmerize))
-
-/datum/antagonist/vassal/proc/toreador_levelup_mesmerize() //Don't need stupid args
- for(var/datum/action/bloodsucker/targeted/mesmerize/mesmerize_power in powers)
- if(!istype(mesmerize_power))
- continue
- mesmerize_power.level_current = max(master.bloodsucker_level, 1)
-
-/// If we weren't created by a bloodsucker, then we cannot be a vassal (assigned from antag panel)
-/datum/antagonist/vassal/can_be_owned(datum/mind/new_owner)
- if(!master)
- return FALSE
- return ..()
-
-/// Used for Admin removing Vassals.
-/datum/mind/proc/remove_vassal()
- var/datum/antagonist/vassal/selected_vassal = has_antag_datum(/datum/antagonist/vassal)
- if(selected_vassal)
- remove_antag_datum(/datum/antagonist/vassal)
-
-/// When a Bloodsucker gets FinalDeath, all Vassals are freed - This is a Bloodsucker proc, not a Vassal one.
-/datum/antagonist/bloodsucker/proc/FreeAllVassals()
- for(var/datum/antagonist/vassal/all_vassals in vassals)
- // Skip over any Bloodsucker Vassals, they're too far gone to have all their stuff taken away from them
- if(all_vassals.owner.has_antag_datum(/datum/antagonist/bloodsucker))
- continue
- remove_vassal(all_vassals.owner)
-
-/// Called by FreeAllVassals()
-/datum/antagonist/bloodsucker/proc/remove_vassal(datum/mind/vassal)
- vassal.remove_antag_datum(/datum/antagonist/vassal)
-
-/// Used when your Master teaches you a new Power.
-/datum/antagonist/vassal/proc/BuyPower(datum/action/bloodsucker/power)
- powers += power
- power.Grant(owner.current)
-
-/datum/antagonist/vassal/proc/LevelUpPowers()
- for(var/datum/action/bloodsucker/power in powers)
- power.level_current++
-
-/**
- * # Vassal Pinpointer
- *
- * Pinpointer that points to their Master's location at all times.
- * Unlike the Monster hunter one, this one is permanently active, and has no power needed to activate it.
- */
-
-/atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition
- name = "Blood Bond"
- desc = "You always know where your master is."
-
-/datum/status_effect/agent_pinpointer/vassal_edition
- id = "agent_pinpointer"
- alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition
- minimum_range = VASSAL_SCAN_MIN_DISTANCE
- tick_interval = VASSAL_SCAN_PING_TIME
- duration = -1
- range_fuzz_factor = 0
-
-/datum/status_effect/agent_pinpointer/vassal_edition/on_creation(mob/living/new_owner, ...)
- ..()
- var/datum/antagonist/vassal/antag_datum = new_owner.mind.has_antag_datum(/datum/antagonist/vassal)
- scan_target = antag_datum?.master?.owner?.current
-
-/datum/status_effect/agent_pinpointer/vassal_edition/scan_for_target()
- return
-
-/datum/status_effect/agent_pinpointer/vassal_edition/Destroy()
- if(scan_target)
- to_chat(owner, span_notice("You've lost your master's trail."))
- return ..()
-
-/**
- * # HUD
- */
-/datum/antagonist/vassal/proc/update_vassal_icons_added(mob/living/vassal, icontype = "vassal")
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_BLOODSUCKER]
- hud.join_hud(vassal)
- /// Located in icons/mob/hud.dmi
- set_antag_hud(vassal, icontype)
- /// FULP ADDITION! Check prepare_huds in mob.dm to see why.
-
-/datum/antagonist/vassal/proc/update_vassal_icons_removed(mob/living/vassal)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_BLOODSUCKER]
- hud.leave_hud(vassal)
- set_antag_hud(vassal, null)
diff --git a/code/modules/antagonists/bloodsuckers/vassal/bloodsucker_conversion.dm b/code/modules/antagonists/bloodsuckers/vassal/bloodsucker_conversion.dm
new file mode 100644
index 000000000000..a25ab010d966
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/vassal/bloodsucker_conversion.dm
@@ -0,0 +1,137 @@
+/**
+ * Checks if the target has antag datums and, if so,
+ * are they allowed to be Vassalized, or not, or banned.
+ * Args:
+ * target - The person we check for antag datums.
+ */
+/datum/antagonist/bloodsucker/proc/AmValidAntag(mob/target)
+ if(!target.mind || target.mind.unconvertable)
+ return VASSALIZATION_BANNED
+
+ var/vassalization_status = VASSALIZATION_ALLOWED
+ for(var/datum/antagonist/antag_datum as anything in target.mind.antag_datums)
+ if(antag_datum.type in vassal_banned_antags)
+ return VASSALIZATION_BANNED
+ vassalization_status = VASSALIZATION_DISLOYAL
+ return vassalization_status
+
+/**
+ * # can_make_vassal
+ * Checks if the person is allowed to turn into the Bloodsucker's
+ * Vassal, ensuring they are a player and valid.
+ * If they are a Vassal themselves, will check if their master
+ * has broken the Masquerade, to steal them.
+ * Args:
+ * conversion_target - Person being vassalized
+ */
+/datum/antagonist/bloodsucker/proc/can_make_vassal(mob/living/conversion_target)
+ if(!iscarbon(conversion_target) || conversion_target.stat > UNCONSCIOUS)
+ return FALSE
+ // No Mind!
+ if(!conversion_target.mind)
+ to_chat(owner.current, span_danger("[conversion_target] isn't self-aware enough to be made into a Vassal."))
+ return FALSE
+ if(AmValidAntag(conversion_target) == VASSALIZATION_BANNED)
+ to_chat(owner.current, span_danger("[conversion_target] resists the power of your blood to dominate their mind!"))
+ return FALSE
+ var/mob/living/master = conversion_target.mind.enslaved_to?.resolve()
+ if(!master || (master == owner.current))
+ return TRUE
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = master.mind.has_antag_datum(/datum/antagonist/bloodsucker)
+ if(bloodsuckerdatum?.broke_masquerade)
+ //vassal stealing
+ return TRUE
+ to_chat(owner.current, span_danger("[conversion_target]'s mind is overwhelmed with too much external force to put your own!"))
+ return FALSE
+
+/**
+ * First will check if the target can be turned into a Vassal, if so then it will
+ * turn them into one, log it, sync their minds, then updates the Rank
+ * Args:
+ * conversion_target - The person converted.
+ */
+/datum/antagonist/bloodsucker/proc/make_vassal(mob/living/conversion_target)
+ if(!can_make_vassal(conversion_target))
+ return FALSE
+
+ //Check if they used to be a Vassal and was stolen.
+ var/datum/antagonist/vassal/old_vassal = conversion_target.mind.has_antag_datum(/datum/antagonist/vassal)
+ if(old_vassal)
+ conversion_target.mind.remove_antag_datum(/datum/antagonist/vassal)
+
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker)
+ bloodsuckerdatum.SelectTitle(am_fledgling = FALSE)
+
+ //set the master, then give the datum.
+ var/datum/antagonist/vassal/vassaldatum = new(conversion_target.mind)
+ vassaldatum.master = bloodsuckerdatum
+ conversion_target.mind.add_antag_datum(vassaldatum)
+
+ message_admins("[conversion_target] has become a Vassal, and is enslaved to [owner.current].")
+ log_admin("[conversion_target] has become a Vassal, and is enslaved to [owner.current].")
+ return TRUE
+
+/*
+ * # can_make_special
+ *
+ * MIND Helper proc that ensures the person can be a Special Vassal,
+ * without actually giving the antag datum to them.
+ * This is because Special Vassals get special abilities, without the unique Bloodsucker blood tracking,
+ * and we don't want this to be infinite.
+ * Args:
+ * creator - Person attempting to convert them.
+ */
+/datum/mind/proc/can_make_special(datum/mind/creator)
+ var/mob/living/user = current
+ if(!(user.mob_biotypes & MOB_ORGANIC))
+ if(creator)
+ to_chat(creator, span_danger("[user]'s DNA isn't compatible!"))
+ return FALSE
+ return TRUE
+
+/*
+ * # make_bloodsucker
+ *
+ * MIND Helper proc that turns the person into a Bloodsucker
+ * Args:
+ * creator - Person attempting to convert them.
+ */
+/datum/mind/proc/make_bloodsucker(datum/mind/creator)
+ var/datum/antagonist/bloodsuckerdatum = add_antag_datum(/datum/antagonist/bloodsucker)
+ if(bloodsuckerdatum && creator)
+ message_admins("[src] has become a Bloodsucker, and was created by [creator].")
+ log_admin("[src] has become a Bloodsucker, and was created by [creator].")
+ return bloodsuckerdatum
+
+/datum/mind/proc/remove_bloodsucker()
+ var/datum/antagonist/bloodsucker/removed_bloodsucker = has_antag_datum(/datum/antagonist/bloodsucker)
+ if(removed_bloodsucker)
+ remove_antag_datum(/datum/antagonist/bloodsucker)
+ special_role = null
+
+/**
+ * # Assigning Bloodsucker status
+ *
+ * Here we assign the Bloodsuckers themselves, ensuring they arent Plasmamen
+ */
+
+/datum/mind/proc/prepare_bloodsucker(datum/mind/convertee, datum/mind/converter)
+ var/mob/living/carbon/human/user = convertee.current
+ //Yogs start -- fixes plasmaman vampires
+ var/species_type = convertee.current.client.prefs.read_preference(/datum/preference/choiced/species)
+ var/datum/species/species = new species_type
+
+ var/noblood = (NOBLOOD in species.species_traits)
+ qdel(species)
+
+ if(noblood)
+ user.set_species(/datum/species/human, TRUE, TRUE)
+ if(user.client?.prefs?.read_preference(/datum/preference/name/backup_human) && !is_banned_from(user.client?.ckey, "Appearance"))
+ user.fully_replace_character_name(user.dna.real_name, user.client.prefs.read_preference(/datum/preference/name/backup_human))
+ else
+ user.fully_replace_character_name(user.dna.real_name, random_unique_name(user.gender))
+ // Check for Fledgeling
+ if(converter)
+ message_admins("[convertee] has become a Bloodsucker, and was created by [converter].")
+ log_admin("[convertee] has become a Bloodsucker, and was created by [converter].")
+ return TRUE
diff --git a/code/modules/antagonists/bloodsuckers/vassal/favorite_vassal.dm b/code/modules/antagonists/bloodsuckers/vassal/favorite_vassal.dm
new file mode 100644
index 000000000000..ac4f96967a10
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/vassal/favorite_vassal.dm
@@ -0,0 +1,23 @@
+/**
+ * Favorite Vassal
+ *
+ * Gets some cool abilities depending on the Clan.
+ */
+/datum/antagonist/vassal/favorite
+ name = "\improper Favorite Vassal"
+ show_in_antagpanel = FALSE
+ antag_hud_name = "vassal6"
+ special_type = FAVORITE_VASSAL
+ vassal_description = "The Favorite Vassal gets unique abilities over other Vassals depending on your Clan \
+ and becomes completely immune to Mindshields. If part of Ventrue, this is the Vassal you will rank up."
+
+ ///Bloodsucker levels, but for Vassals, used by Ventrue.
+ var/vassal_level
+
+/datum/antagonist/vassal/favorite/on_gain()
+ . = ..()
+ SEND_SIGNAL(master, BLOODSUCKER_MAKE_FAVORITE, src)
+
+///Set the Vassal's rank to their Bloodsucker level
+/datum/antagonist/vassal/favorite/proc/set_vassal_level(mob/living/carbon/human/target)
+ master.bloodsucker_level = vassal_level
diff --git a/code/modules/antagonists/bloodsuckers/vassal/revenge_vassal.dm b/code/modules/antagonists/bloodsuckers/vassal/revenge_vassal.dm
new file mode 100644
index 000000000000..b4379f50ed6f
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/vassal/revenge_vassal.dm
@@ -0,0 +1,82 @@
+/**
+ * Revenge Vassal
+ *
+ * Has the goal to 'get revenge' when their Master dies.
+ */
+/datum/antagonist/vassal/revenge
+ name = "\improper Revenge Vassal"
+ roundend_category = "abandoned Vassals"
+ show_in_roundend = FALSE
+ show_in_antagpanel = FALSE
+ antag_hud_name = "vassal4"
+ special_type = REVENGE_VASSAL
+ vassal_description = "The Revenge Vassal will not deconvert on your Final Death, \
+ instead they will gain all your Powers, and the objective to take revenge for your demise. \
+ They additionally maintain your Vassals after your departure, rather than become aimless."
+
+ ///all ex-vassals brought back into the fold.
+ var/list/datum/antagonist/ex_vassal/ex_vassals = list()
+
+/datum/antagonist/vassal/revenge/roundend_report()
+ var/list/report = list()
+ report += printplayer(owner)
+ if(objectives.len)
+ report += printobjectives(objectives)
+
+ // Now list their vassals
+ if(ex_vassals.len)
+ report += ""
+ for(var/datum/antagonist/ex_vassal/all_vassals as anything in ex_vassals)
+ if(!all_vassals.owner)
+ continue
+ report += "[all_vassals.owner.name] the [all_vassals.owner.assigned_role]"
+
+ return report.Join("
")
+
+/datum/antagonist/vassal/revenge/on_gain()
+ . = ..()
+ RegisterSignal(master, BLOODSUCKER_FINAL_DEATH, PROC_REF(on_master_death))
+
+/datum/antagonist/vassal/revenge/on_removal()
+ UnregisterSignal(master, BLOODSUCKER_FINAL_DEATH)
+ return ..()
+
+/datum/antagonist/vassal/revenge/ui_static_data(mob/user)
+ var/list/data = list()
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers)
+ var/list/power_data = list()
+
+ power_data["power_name"] = power.name
+ power_data["power_explanation"] = power.power_explanation
+ power_data["power_icon"] = power.button_icon_state
+
+ data["power"] += list(power_data)
+
+ return data + ..()
+
+/datum/antagonist/vassal/revenge/proc/on_master_death(datum/antagonist/bloodsucker/bloodsuckerdatum, mob/living/carbon/master)
+ SIGNAL_HANDLER
+
+ show_in_roundend = TRUE
+ for(var/datum/objective/all_objectives as anything in objectives)
+ objectives -= all_objectives
+ BuyPower(new /datum/action/cooldown/bloodsucker/vassal_blood)
+ for(var/datum/action/cooldown/bloodsucker/master_powers as anything in bloodsuckerdatum.powers)
+ if(master_powers.purchase_flags & BLOODSUCKER_DEFAULT_POWER)
+ continue
+ master_powers.Grant(owner.current)
+ owner.current.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
+
+ var/datum/objective/survive/new_objective = new
+ new_objective.name = "Avenge Bloodsucker"
+ new_objective.explanation_text = "Avenge your Bloodsucker's death by recruiting their ex-vassals and continuing their operations."
+ new_objective.owner = owner
+ objectives += new_objective
+
+ if(info_button_ref)
+ QDEL_NULL(info_button_ref)
+
+ ui_name = "AntagInfoRevengeVassal" //give their new ui
+ var/datum/action/antag_info/info_button = new(src)
+ info_button.Grant(owner.current)
+ info_button_ref = WEAKREF(info_button)
diff --git a/code/modules/antagonists/bloodsuckers/vassal/vassal.dm b/code/modules/antagonists/bloodsuckers/vassal/vassal.dm
new file mode 100644
index 000000000000..c2f78a0af5d5
--- /dev/null
+++ b/code/modules/antagonists/bloodsuckers/vassal/vassal.dm
@@ -0,0 +1,404 @@
+#define VASSAL_SCAN_MIN_DISTANCE 5
+#define VASSAL_SCAN_MAX_DISTANCE 500
+/// 2s update time.
+#define VASSAL_SCAN_PING_TIME 2 SECONDS
+
+/datum/antagonist/vassal
+ name = "\improper Vassal"
+ roundend_category = "vassals"
+ antagpanel_category = "Bloodsucker"
+ job_rank = ROLE_BLOODSUCKER
+ antag_hud_name = "vassal"
+ show_in_roundend = FALSE
+
+ /// The Master Bloodsucker's antag datum.
+ var/datum/antagonist/bloodsucker/master
+ var/datum/game_mode/blooodsucker
+ /// List of all Purchased Powers, like Bloodsuckers.
+ var/list/datum/action/powers = list()
+ ///Whether this vassal is already a special type of Vassal.
+ var/special_type = FALSE
+ ///Description of what this Vassal does.
+ var/vassal_description
+
+/datum/antagonist/vassal/antag_panel_data()
+ return "Master : [master.owner.name]"
+
+/datum/antagonist/vassal/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current_mob = mob_override || owner.current
+ current_mob.apply_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
+ add_team_hud(current_mob)
+
+/datum/antagonist/vassal/remove_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current_mob = mob_override || owner.current
+ current_mob.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
+
+/datum/antagonist/vassal/add_team_hud(mob/target)
+ QDEL_NULL(team_hud_ref)
+
+ team_hud_ref = WEAKREF(target.add_alt_appearance(
+ /datum/atom_hud/alternate_appearance/basic/has_antagonist,
+ "antag_team_hud_[REF(src)]",
+ hud_image_on(target),
+ ))
+
+ var/datum/atom_hud/alternate_appearance/basic/has_antagonist/hud = team_hud_ref.resolve()
+
+ var/list/mob/living/mob_list = list()
+ mob_list += master.owner.current
+ for(var/datum/antagonist/vassal/vassal as anything in master.vassals)
+ mob_list += vassal.owner.current
+
+ for (var/datum/atom_hud/alternate_appearance/basic/has_antagonist/antag_hud as anything in GLOB.has_antagonist_huds)
+ if(!(antag_hud.target in mob_list))
+ continue
+ antag_hud.show_to(target)
+ hud.show_to(antag_hud.target)
+
+/datum/antagonist/vassal/proc/on_examine(datum/source, mob/examiner, examine_text)
+ SIGNAL_HANDLER
+
+ if(!iscarbon(source))
+ return
+ var/vassal_examine = return_vassal_examine(examiner)
+ examine_text += vassal_examine
+
+/datum/antagonist/vassal/on_gain()
+ RegisterSignal(owner.current, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN, PROC_REF(give_warning))
+ /// Enslave them to their Master
+ if(!master || !istype(master, master))
+ return
+ if(special_type)
+ if(!master.special_vassals[special_type])
+ master.special_vassals[special_type] = list()
+ master.special_vassals[special_type] |= src
+ master.vassals |= src
+ owner.enslave_mind_to_creator(master.owner.current)
+ owner.current.log_message("has been vassalized by [master.owner.current]!", LOG_ATTACK, color="#960000")
+ /// Give Recuperate Power
+ BuyPower(new /datum/action/cooldown/bloodsucker/recuperate)
+ /// Give Objectives
+ var/datum/objective/bloodsucker/vassal/vassal_objective = new
+ vassal_objective.owner = owner
+ objectives += vassal_objective
+ /// Give Vampire Language & Hud
+ owner.current.grant_all_languages(FALSE, FALSE, TRUE)
+ owner.current.grant_language(/datum/language/vampiric)
+ . = ..()
+
+/datum/antagonist/vassal/on_removal()
+ UnregisterSignal(owner.current, COMSIG_PARENT_EXAMINE)
+ UnregisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN)
+ //Free them from their Master
+ if(master && master.owner)
+ if(special_type && master.special_vassals[special_type])
+ master.special_vassals[special_type] -= src
+ master.vassals -= src
+ owner.enslaved_to = null
+ for(var/all_status_traits in owner.current.status_traits)
+ REMOVE_TRAIT(owner.current, all_status_traits, BLOODSUCKER_TRAIT)
+ //Remove Recuperate Power
+ while(powers.len)
+ var/datum/action/cooldown/bloodsucker/power = pick(powers)
+ powers -= power
+ power.Remove(owner.current)
+ //Remove Language & Hud
+ owner.current.remove_language(/datum/language/vampiric)
+ return ..()
+
+/datum/antagonist/vassal/on_body_transfer(mob/living/old_body, mob/living/new_body)
+ . = ..()
+ for(var/datum/action/cooldown/bloodsucker/all_powers as anything in powers)
+ all_powers.Remove(old_body)
+ all_powers.Grant(new_body)
+
+/datum/antagonist/vassal/proc/add_objective(datum/objective/added_objective)
+ objectives += added_objective
+
+/datum/antagonist/vassal/greet()
+ . = ..()
+ if(silent)
+ return
+ to_chat(owner, span_userdanger("You are now the mortal servant of [master.owner.current], a Bloodsucker!"))
+ to_chat(owner, span_boldannounce("The power of [master.owner.current.p_their()] immortal blood compels you to obey [master.owner.current.p_them()] in all things, even offering your own life to prolong theirs.\n\
+ You are not required to obey any other Bloodsucker, for only [master.owner.current] is your master. The laws of Nanotrasen do not apply to you now; only your vampiric master's word must be obeyed."))
+ owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
+ antag_memory += "You have been vassalized, becoming the mortal servant of [master.owner.current], a bloodsucking vampire!
"
+ /// Message told to your Master.
+ to_chat(master.owner, span_userdanger("[owner.current] has become addicted to your immortal blood. [owner.current.p_they(TRUE)] [owner.current.p_are()] now your undying servant!"))
+ master.owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
+
+/datum/antagonist/vassal/farewell()
+ if(silent)
+ return
+ owner.current.visible_message(
+ span_deconversion_message("[owner.current]'s eyes dart feverishly from side to side, and then stop. [owner.current.p_they(TRUE)] seem[owner.current.p_s()] calm, \
+ like [owner.current.p_they()] [owner.current.p_have()] regained some lost part of [owner.current.p_them()]self."),
+ )
+ to_chat(owner, span_deconversion_message("With a snap, you are no longer enslaved to [master.owner]! You breathe in heavily, having regained your free will."))
+ owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
+ /// Message told to your (former) Master.
+ if(master && master.owner)
+ to_chat(master.owner, span_cultbold("You feel the bond with your vassal [owner.current] has somehow been broken!"))
+
+/datum/antagonist/vassal/admin_add(datum/mind/new_owner, mob/admin)
+ var/list/datum/mind/possible_vampires = list()
+ for(var/datum/antagonist/bloodsucker/bloodsuckerdatums in GLOB.antagonists)
+ var/datum/mind/vamp = bloodsuckerdatums.owner
+ if(!vamp)
+ continue
+ if(!vamp.current)
+ continue
+ if(vamp.current.stat == DEAD)
+ continue
+ possible_vampires += vamp
+ if(!length(possible_vampires))
+ message_admins("[key_name_admin(usr)] tried vassalizing [key_name_admin(new_owner)], but there were no bloodsuckers!")
+ return
+ var/datum/mind/choice = input("Which bloodsucker should this vassal belong to?", "Bloodsucker") in possible_vampires
+ if(!choice)
+ return
+ log_admin("[key_name_admin(usr)] turned [key_name_admin(new_owner)] into a vassal of [key_name_admin(choice)]!")
+ var/datum/antagonist/bloodsucker/vampire = choice.has_antag_datum(/datum/antagonist/bloodsucker)
+ master = vampire
+ new_owner.add_antag_datum(src)
+ to_chat(choice, span_notice("Through divine intervention, you've gained a new vassal!"))
+
+/datum/antagonist/vassal/proc/toreador_levelup_mesmerize() //Don't need stupid args
+ for(var/datum/action/cooldown/bloodsucker/targeted/mesmerize/mesmerize_power in powers)
+ if(!istype(mesmerize_power))
+ continue
+ mesmerize_power.level_current = max(master.bloodsucker_level, 1)
+
+/// If we weren't created by a bloodsucker, then we cannot be a vassal (assigned from antag panel)
+/datum/antagonist/vassal/can_be_owned(datum/mind/new_owner)
+ if(!master)
+ return FALSE
+ return ..()
+
+/// When a Bloodsucker gets FinalDeath, all Vassals are freed - This is a Bloodsucker proc, not a Vassal one.
+/datum/antagonist/bloodsucker/proc/free_all_vassals()
+ for(var/datum/antagonist/vassal/all_vassals in vassals)
+ // Skip over any Bloodsucker Vassals, they're too far gone to have all their stuff taken away from them
+ if(all_vassals.owner.has_antag_datum(/datum/antagonist/bloodsucker))
+ all_vassals.owner.current.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
+ continue
+ remove_vassal(all_vassals.owner)
+ if(all_vassals.special_type == REVENGE_VASSAL)
+ continue
+ all_vassals.owner.add_antag_datum(/datum/antagonist/ex_vassal)
+ all_vassals.owner.remove_antag_datum(/datum/antagonist/vassal)
+
+/// Called by FreeAllVassals()
+/datum/antagonist/bloodsucker/proc/remove_vassal(datum/mind/vassal)
+ vassal.remove_antag_datum(/datum/antagonist/vassal)
+
+/// Used when your Master teaches you a new Power.
+/datum/antagonist/vassal/proc/BuyPower(datum/action/cooldown/bloodsucker/power)
+ powers += power
+ power.Grant(owner.current)
+
+/datum/antagonist/vassal/proc/LevelUpPowers()
+ for(var/datum/action/cooldown/bloodsucker/power in powers)
+ power.level_current++
+
+/// Called when we are made into the Favorite Vassal
+/datum/antagonist/vassal/proc/make_special(datum/antagonist/vassal/vassal_type)
+ //store what we need
+ var/datum/mind/vassal_owner = owner
+ var/datum/antagonist/bloodsucker/bloodsuckerdatum = master
+
+ //remove our antag datum
+ silent = TRUE
+ vassal_owner.remove_antag_datum(/datum/antagonist/vassal)
+
+ //give our new one
+ var/datum/antagonist/vassal/vassaldatum = new vassal_type(vassal_owner)
+ vassaldatum.master = bloodsuckerdatum
+ vassaldatum.silent = TRUE
+ vassal_owner.add_antag_datum(vassaldatum)
+ vassaldatum.silent = FALSE
+
+ //send alerts of completion
+ to_chat(master, span_danger("You have turned [vassal_owner.current] into your [vassaldatum.name]! They will no longer be deconverted upon Mindshielding!"))
+ to_chat(vassal_owner, span_notice("As Blood drips over your body, you feel closer to your Master... You are now the Favorite Vassal!"))
+ vassal_owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 75, FALSE, pressure_affected = FALSE)
+
+/**
+ * # Vassal Pinpointer
+ *
+ * Pinpointer that points to their Master's location at all times.
+ * Unlike the Monster hunter one, this one is permanently active, and has no power needed to activate it.
+ */
+
+/atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition
+ name = "Blood Bond"
+ desc = "You always know where your master is."
+
+/datum/status_effect/agent_pinpointer/vassal_edition
+ id = "agent_pinpointer"
+ alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition
+ minimum_range = VASSAL_SCAN_MIN_DISTANCE
+ tick_interval = VASSAL_SCAN_PING_TIME
+ duration = -1
+ range_fuzz_factor = 0
+
+/datum/status_effect/agent_pinpointer/vassal_edition/on_creation(mob/living/new_owner, ...)
+ ..()
+ var/datum/antagonist/vassal/antag_datum = new_owner.mind.has_antag_datum(/datum/antagonist/vassal)
+ scan_target = antag_datum?.master?.owner?.current
+
+/datum/status_effect/agent_pinpointer/vassal_edition/scan_for_target()
+ return
+
+/datum/status_effect/agent_pinpointer/vassal_edition/Destroy()
+ if(scan_target)
+ to_chat(owner, span_notice("You've lost your master's trail."))
+ return ..()
+
+#define BLOOD_TIMER_REQUIREMENT (10 MINUTES)
+#define BLOOD_TIMER_HALWAY (BLOOD_TIMER_REQUIREMENT / 2)
+
+/datum/antagonist/ex_vassal
+ name = "\improper Ex-Vassal"
+ roundend_category = "vassals"
+ antagpanel_category = "Bloodsucker"
+ job_rank = ROLE_BLOODSUCKER
+ antag_hud_name = "vassal_grey"
+ show_in_roundend = FALSE
+ show_in_antagpanel = FALSE
+ silent = TRUE
+ ui_name = FALSE
+ hud_icon = 'yogstation/icons/mob/hud.dmi'
+
+ ///The revenge vassal that brought us into the fold.
+ var/datum/antagonist/vassal/revenge/revenge_vassal
+ ///Timer we have to live
+ COOLDOWN_DECLARE(blood_timer)
+
+/datum/antagonist/ex_vassal/on_gain()
+ . = ..()
+ RegisterSignal(owner.current, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+
+/datum/antagonist/ex_vassal/on_removal()
+ if(revenge_vassal)
+ revenge_vassal.ex_vassals -= src
+ revenge_vassal = null
+ blood_timer = null
+ return ..()
+
+/datum/antagonist/ex_vassal/proc/on_examine(datum/source, mob/examiner, examine_text)
+ SIGNAL_HANDLER
+
+ var/datum/antagonist/vassal/revenge/vassaldatum = examiner.mind.has_antag_datum(/datum/antagonist/vassal/revenge)
+ if(vassaldatum && !revenge_vassal)
+ examine_text += span_notice("[owner.current] is an ex-vassal!")
+
+/datum/antagonist/ex_vassal/add_team_hud(mob/target)
+ QDEL_NULL(team_hud_ref)
+
+ team_hud_ref = WEAKREF(target.add_alt_appearance(
+ /datum/atom_hud/alternate_appearance/basic/has_antagonist,
+ "antag_team_hud_[REF(src)]",
+ hud_image_on(target),
+ ))
+
+ var/datum/atom_hud/alternate_appearance/basic/has_antagonist/hud = team_hud_ref.resolve()
+
+ var/list/mob/living/mob_list = list()
+ mob_list += revenge_vassal.owner.current
+ for(var/datum/antagonist/ex_vassal/former_vassals as anything in revenge_vassal.ex_vassals)
+ mob_list += former_vassals.owner.current
+
+ for (var/datum/atom_hud/alternate_appearance/basic/has_antagonist/antag_hud as anything in GLOB.has_antagonist_huds)
+ if(!(antag_hud.target in mob_list))
+ continue
+ antag_hud.show_to(target)
+ hud.show_to(antag_hud.target)
+
+/**
+ * Fold return
+ *
+ * Called when a Revenge bloodsucker gets a vassal back into the fold.
+ */
+/datum/antagonist/ex_vassal/proc/return_to_fold(datum/antagonist/vassal/revenge/mike_ehrmantraut)
+ revenge_vassal = mike_ehrmantraut
+ mike_ehrmantraut.ex_vassals += src
+ COOLDOWN_START(src, blood_timer, BLOOD_TIMER_REQUIREMENT)
+
+ RegisterSignal(src, COMSIG_LIVING_BIOLOGICAL_LIFE, PROC_REF(on_life))
+
+/datum/antagonist/ex_vassal/proc/on_life(datum/source, delta_time, times_fired)
+ SIGNAL_HANDLER
+
+ if(COOLDOWN_TIMELEFT(src, blood_timer) <= BLOOD_TIMER_HALWAY + 2 && COOLDOWN_TIMELEFT(src, blood_timer) >= BLOOD_TIMER_HALWAY - 2) //just about halfway
+ to_chat(owner.current, span_cultbold("You need new blood from your Master!"))
+ if(!COOLDOWN_FINISHED(src, blood_timer))
+ return
+ to_chat(owner.current, span_cultbold("You are out of blood!"))
+ to_chat(revenge_vassal.owner.current, span_cultbold("[owner.current] has ran out of blood and is no longer in the fold!"))
+ owner.remove_antag_datum(/datum/antagonist/ex_vassal)
+
+/datum/antagonist/vassal/proc/give_warning(atom/source, danger_level, vampire_warning_message, vassal_warning_message)
+ SIGNAL_HANDLER
+ if(vassal_warning_message)
+ to_chat(owner, vassal_warning_message)
+
+/**
+ * Returns a Vassals's examine strings.
+ * Args:
+ * viewer - The person examining.
+ */
+
+/datum/antagonist/vassal/proc/return_vassal_examine(mob/living/viewer)
+ if(!viewer.mind || !iscarbon(owner.current))
+ return FALSE
+ var/mob/living/carbon/carbon_current = owner.current
+ // Target must be a Vassal
+ // Default String
+ var/returnString = "\["
+ var/returnIcon = ""
+ // Vassals and Bloodsuckers recognize eachother, while Monster Hunters can see Vassals.
+ if(!IS_BLOODSUCKER(viewer) && !IS_VASSAL(viewer) && !IS_MONSTERHUNTER(viewer))
+ return FALSE
+ // Am I Viewer's Vassal?
+ if(master.owner == viewer.mind)
+ returnString += "This [carbon_current.dna.species.name] bears YOUR mark!"
+ returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal")]"
+ // Am I someone ELSE'S Vassal?
+ else if(IS_BLOODSUCKER(viewer) || IS_MONSTERHUNTER(viewer))
+ returnString += "This [carbon_current.dna.species.name] bears the mark of [master.return_full_name()][master.broke_masquerade ? " who has broken the Masquerade" : ""]"
+ returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal_grey")]"
+ // Are you serving the same master as I am?
+ else if(viewer.mind.has_antag_datum(/datum/antagonist/vassal) in master.vassals)
+ returnString += "[p_they(TRUE)] bears the mark of your Master"
+ returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal")]"
+ // You serve a different Master than I do.
+ else
+ returnString += "[p_they(TRUE)] bears the mark of another Bloodsucker"
+ returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal_grey")]"
+
+ returnString += "\]" // \n" Don't need spacers. Using . += "" in examine.dm does this on its own.
+ return returnIcon + returnString
+
+/**
+ * Bloodsucker Blood
+ *
+ * Artificially made, this must be fed to ex-vassals to keep them on their high.
+ */
+/datum/reagent/blood/bloodsucker
+ name = "Blood two" //real
+
+/datum/reagent/blood/bloodsucker/reaction_mob(mob/living/exposed_mob, methods, reac_volume, show_message, touch_protection)
+ var/datum/antagonist/ex_vassal/former_vassal = exposed_mob.mind.has_antag_datum(/datum/antagonist/ex_vassal)
+ if(former_vassal)
+ to_chat(exposed_mob, span_cult("You feel the blood restore you... You feel safe."))
+ COOLDOWN_RESET(former_vassal, blood_timer)
+ COOLDOWN_START(former_vassal, blood_timer, BLOOD_TIMER_REQUIREMENT)
+ return ..()
+
+#undef BLOOD_TIMER_REQUIREMENT
+#undef BLOOD_TIMER_HALWAY
diff --git a/code/modules/antagonists/brainwashing/brainwashing.dm b/code/modules/antagonists/brainwashing/brainwashing.dm
index 7cb3d7ece6f6..59f833a0a0d2 100644
--- a/code/modules/antagonists/brainwashing/brainwashing.dm
+++ b/code/modules/antagonists/brainwashing/brainwashing.dm
@@ -28,6 +28,7 @@
job_rank = ROLE_BRAINWASHED
roundend_category = "brainwashed victims"
show_in_antagpanel = TRUE
+ antag_hud_name = "brainwashed"
antagpanel_category = "Other"
show_name_in_check_antagonists = TRUE
@@ -49,24 +50,6 @@
owner.current.clear_alert("brainwash_notif")
owner.announce_objectives()
-/datum/antagonist/brainwashed/apply_innate_effects(mob/living/mob_override)
- . = ..()
- update_traitor_icons_added()
-
-/datum/antagonist/brainwashed/remove_innate_effects(mob/living/mob_override)
- . = ..()
- update_traitor_icons_removed()
-
-/datum/antagonist/brainwashed/proc/update_traitor_icons_added(datum/mind/slave_mind)
- var/datum/atom_hud/antag/brainwashedhud = GLOB.huds[ANTAG_HUD_BRAINWASHED]
- brainwashedhud.join_hud(owner.current)
- set_antag_hud(owner.current, "brainwashed")
-
-/datum/antagonist/brainwashed/proc/update_traitor_icons_removed(datum/mind/slave_mind)
- var/datum/atom_hud/antag/brainwashedhud = GLOB.huds[ANTAG_HUD_BRAINWASHED]
- brainwashedhud.leave_hud(owner.current)
- set_antag_hud(owner.current, null)
-
/datum/antagonist/brainwashed/admin_add(datum/mind/new_owner,mob/admin)
var/mob/living/carbon/C = new_owner.current
if(!istype(C))
diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm
index 917e158b356c..761acc79fec5 100644
--- a/code/modules/antagonists/brother/brother.dm
+++ b/code/modules/antagonists/brother/brother.dm
@@ -3,6 +3,7 @@
antagpanel_category = "Brother"
job_rank = ROLE_BROTHER
var/special_role = ROLE_BROTHER
+ antag_hud_name = "brother"
var/datum/team/brother_team/team
antag_moodlet = /datum/mood_event/focused
can_hijack = HIJACK_HIJACKER
@@ -102,7 +103,6 @@
for(var/datum/mind/M in team.members) // Link the implants of all team members
var/obj/item/implant/bloodbrother/T = locate() in M.current.implants
I.link_implant(T)
- SSticker.mode.update_brother_icons_added(owner)
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE)
/datum/antagonist/brother/admin_add(datum/mind/new_owner,mob/admin)
@@ -133,7 +133,7 @@
.["Convert To Traitor"] = CALLBACK(src, PROC_REF(make_traitor))
/datum/antagonist/brother/proc/make_traitor()
- if(alert("Are you sure? This will turn the blood brother into a traitor with the same objectives!",,"Yes","No") != "Yes")
+ if(tgui_alert("Are you sure? This will turn the blood brother into a traitor with the same objectives!",,list("Yes","No")) != "Yes")
return
var/datum/antagonist/traitor/tot = new()
diff --git a/code/modules/antagonists/changeling/cellular_emporium.dm b/code/modules/antagonists/changeling/cellular_emporium.dm
index 7437cd09e54e..c1a86f6df834 100644
--- a/code/modules/antagonists/changeling/cellular_emporium.dm
+++ b/code/modules/antagonists/changeling/cellular_emporium.dm
@@ -1,29 +1,16 @@
// cellular emporium
// The place where changelings go to buy their biological weaponry.
-/datum/cellular_emporium
- var/name = "cellular emporium"
- var/datum/antagonist/changeling/changeling
+/datum/antag_menu/cellular_emporium
+ name = "cellular emporium"
+ ui_name = "CellularEmporium"
-/datum/cellular_emporium/New(my_changeling)
- . = ..()
- changeling = my_changeling
-
-/datum/cellular_emporium/Destroy()
- changeling = null
- . = ..()
-
-/datum/cellular_emporium/ui_state(mob/user)
- return GLOB.always_state
-
-/datum/cellular_emporium/ui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "CellularEmporium", name)
- ui.open()
-
-/datum/cellular_emporium/ui_data(mob/user)
+/datum/antag_menu/cellular_emporium/ui_data(mob/user)
var/list/data = list()
+ var/datum/antagonist/changeling/changeling = antag_datum
+
+ if(!istype(changeling))
+ CRASH("changeling menu started with wrong datum.")
var/can_readapt = changeling.canrespec
var/genetic_points_remaining = changeling.geneticpoints
@@ -63,9 +50,10 @@
return data
-/datum/cellular_emporium/ui_act(action, params)
+/datum/antag_menu/cellular_emporium/ui_act(action, params)
if(..())
return
+ var/datum/antagonist/changeling/changeling = antag_datum
switch(action)
if("readapt")
@@ -79,18 +67,22 @@
/datum/action/innate/cellular_emporium
name = "Cellular Emporium"
- icon_icon = 'icons/obj/drinks.dmi'
+ button_icon = 'icons/obj/drinks.dmi'
button_icon_state = "changelingsting"
background_icon_state = "bg_changeling"
- var/datum/cellular_emporium/cellular_emporium
+ overlay_icon_state = "bg_changeling_border"
+ var/datum/antag_menu/cellular_emporium/cellular_emporium
/datum/action/innate/cellular_emporium/New(our_target)
. = ..()
- button.name = name
- if(istype(our_target, /datum/cellular_emporium))
+ if(istype(our_target, /datum/antag_menu/cellular_emporium))
cellular_emporium = our_target
else
- CRASH("cellular_emporium action created with non emporium")
+ CRASH("cellular_emporium action created with non emporium.")
+
+/datum/action/innate/cellular_emporium/Destroy()
+ cellular_emporium = null
+ return ..()
/datum/action/innate/cellular_emporium/Activate()
cellular_emporium.ui_interact(owner)
diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm
index 0b45bc86758f..bad368bafc58 100644
--- a/code/modules/antagonists/changeling/changeling.dm
+++ b/code/modules/antagonists/changeling/changeling.dm
@@ -1,6 +1,8 @@
-#define LING_FAKEDEATH_TIME 400 //40 seconds
+/// The duration of the fakedeath coma.
+#define LING_FAKEDEATH_TIME 40 SECONDS
#define LING_DEAD_GENETICDAMAGE_HEAL_CAP 50 //The lowest value of geneticdamage handle_changeling() can take it to while dead.
-#define LING_ABSORB_RECENT_SPEECH 8 //The amount of recent spoken lines to gain on absorbing a mob
+/// The number of recent spoken lines to gain on absorbing a mob
+#define LING_ABSORB_RECENT_SPEECH 8
/datum/antagonist/changeling
name = "Changeling"
@@ -8,7 +10,9 @@
antagpanel_category = "Changeling"
show_to_ghosts = TRUE
job_rank = ROLE_CHANGELING
+ antag_hud_name = "changeling"
antag_moodlet = /datum/mood_event/changeling
+ ui_name = "AntagInfoChangeling"
var/you_are_greet = TRUE
var/give_objectives = TRUE
@@ -38,10 +42,18 @@
var/canrespec = FALSE//set to TRUE in absorb.dm
var/changeling_speak = 0
var/datum/dna/chosen_dna
+ /// The currently active changeling sting.
var/datum/action/changeling/sting/chosen_sting
- var/datum/cellular_emporium/cellular_emporium
+ /// A reference to our cellular emporium datum.
+ var/datum/antag_menu/cellular_emporium/cellular_emporium
+ /// A reference to our cellular emporium action (which opens the UI for the datum).
var/datum/action/innate/cellular_emporium/emporium_action
+ /// UI displaying how many chems we have
+ var/atom/movable/screen/ling/chems/lingchemdisplay
+ /// UI displayng our currently active sting
+ var/atom/movable/screen/ling/sting/lingstingdisplay
+
var/static/list/all_powers = typecacheof(/datum/action/changeling,TRUE)
var/list/stored_snapshots = list() //list of stored snapshots
@@ -57,7 +69,7 @@
/datum/antagonist/changeling/Destroy()
QDEL_NULL(cellular_emporium)
QDEL_NULL(emporium_action)
- . = ..()
+ return ..()
/datum/antagonist/changeling/proc/generate_name()
var/honorific
@@ -89,9 +101,9 @@
if(team_mode)
forge_team_objectives()
forge_objectives()
- remove_clownmut()
+ handle_clown_mutation(owner.current, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.")
owner.current.grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue. We are able to transform our body after all.
- . = ..()
+ return ..()
/datum/antagonist/changeling/on_removal()
//We'll be using this from now on
@@ -104,23 +116,31 @@
remove_changeling_powers()
. = ..()
+/datum/antagonist/changeling/proc/on_hud_created(datum/source)
+ SIGNAL_HANDLER
+
+ var/datum/hud/ling_hud = owner.current.hud_used
+
+ lingchemdisplay = new
+ lingchemdisplay.hud = ling_hud
+ ling_hud.infodisplay += lingchemdisplay
+
+ lingstingdisplay = new
+ lingstingdisplay.hud = ling_hud
+ ling_hud.infodisplay += lingstingdisplay
+
+ INVOKE_ASYNC(ling_hud, TYPE_PROC_REF(/datum/hud/, show_hud),ling_hud.hud_version)
+
/datum/antagonist/changeling/proc/make_absorbable()
var/mob/living/carbon/C = owner.current
if(ishuman(C) && (NO_DNA_COPY in C.dna.species.species_traits || !C.has_dna()))
to_chat(C, span_userdanger("You have been made a human, as your original race had incompatible DNA."))
C.set_species(/datum/species/human, TRUE, TRUE)
- if(C.client?.prefs?.read_preference(/datum/preference/name/real_name) && !is_banned_from(C.client?.ckey, "Appearance"))
- C.fully_replace_character_name(C.dna.real_name, C.client.prefs.read_preference(/datum/preference/name/real_name))
+ if(C.client?.prefs?.read_preference(/datum/preference/name/backup_human) && !is_banned_from(C.client?.ckey, "Appearance"))
+ C.fully_replace_character_name(C.dna.real_name, C.client.prefs.read_preference(/datum/preference/name/backup_human))
else
C.fully_replace_character_name(C.dna.real_name, random_unique_name(C.gender))
-/datum/antagonist/changeling/proc/remove_clownmut()
- if (owner)
- var/mob/living/carbon/human/H = owner.current
- if(istype(H) && owner.assigned_role == "Clown")
- to_chat(H, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.")
- H.dna.remove_mutation(CLOWNMUT)
-
/datum/antagonist/changeling/proc/reset_properties()
changeling_speak = 0
chosen_sting = null
@@ -149,11 +169,6 @@
geneticpoints = additionalpoints
- //MOVE THIS
- if(owner.current.hud_used && owner.current.hud_used.lingstingdisplay)
- owner.current.hud_used.lingstingdisplay.icon_state = null
- owner.current.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT
-
/datum/antagonist/changeling/proc/reset_powers()
if(purchasedpowers)
remove_changeling_powers()
@@ -377,26 +392,46 @@
if(ishuman(C))
add_new_profile(C)
-/datum/antagonist/changeling/apply_innate_effects()
- var/mob/living/living_mob = owner.current
- if(!isliving(living_mob))
+/datum/antagonist/changeling/apply_innate_effects(mob/living/mob_override)
+ var/mob/mob_to_tweak = mob_override || owner.current
+ if(!isliving(mob_to_tweak))
return
-
- RegisterSignals(living_mob, list(COMSIG_MOB_MIDDLECLICKON, COMSIG_MOB_ALTCLICKON), PROC_REF(on_click_sting))
-
+ handle_clown_mutation(mob_to_tweak, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.")
+ RegisterSignal(mob_to_tweak, COMSIG_LIVING_BIOLOGICAL_LIFE, PROC_REF(on_life))
+ RegisterSignals(mob_to_tweak, list(COMSIG_MOB_MIDDLECLICKON, COMSIG_MOB_ALTCLICKON), PROC_REF(on_click_sting))
//Brains optional.
- var/obj/item/organ/brain/our_ling_brain = living_mob.getorganslot(ORGAN_SLOT_BRAIN)
+ var/obj/item/organ/brain/our_ling_brain = mob_to_tweak.getorganslot(ORGAN_SLOT_BRAIN)
if(our_ling_brain)
our_ling_brain.organ_flags &= ~ORGAN_VITAL
our_ling_brain.decoy_override = TRUE
- update_changeling_icons_added()
-/datum/antagonist/changeling/remove_innate_effects()
- var/mob/living/living_mob = owner.current
+ if(mob_to_tweak.hud_used)
+ var/datum/hud/hud_used = mob_to_tweak.hud_used
+ lingchemdisplay = new /atom/movable/screen/ling/chems()
+ lingchemdisplay.hud = hud_used
+ hud_used.infodisplay += lingchemdisplay
+
+ lingstingdisplay = new /atom/movable/screen/ling/sting()
+ lingstingdisplay.hud = hud_used
+ hud_used.infodisplay += lingstingdisplay
+ hud_used.show_hud(hud_used.hud_version)
+ else
+ RegisterSignal(mob_to_tweak, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created))
+
+
+/datum/antagonist/changeling/remove_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/living_mob = mob_override || owner.current
UnregisterSignal(living_mob, list(COMSIG_MOB_MIDDLECLICKON, COMSIG_MOB_ALTCLICKON))
- update_changeling_icons_removed()
+ if(living_mob.hud_used)
+ var/datum/hud/hud_used = living_mob.hud_used
+
+ hud_used.infodisplay -= lingchemdisplay
+ hud_used.infodisplay -= lingstingdisplay
+ QDEL_NULL(lingchemdisplay)
+ QDEL_NULL(lingstingdisplay)
/datum/antagonist/changeling/greet()
if (you_are_greet)
@@ -410,6 +445,34 @@
/datum/antagonist/changeling/farewell()
to_chat(owner.current, span_userdanger("You grow weak and lose your powers! You are no longer a changeling and are stuck in your current form!"))
+/**
+ * Signal proc for [COMSIG_LIVING_BIOLOGICAL_LIFE].
+ * Handles regenerating chemicals on life ticks.
+ */
+/datum/antagonist/changeling/proc/on_life(datum/source, delta_time, times_fired)
+ SIGNAL_HANDLER
+
+ // If dead, we only regenerate up to half chem storage.
+ if(owner.current.stat == DEAD)
+ adjust_chemicals((chem_recharge_rate - chem_recharge_slowdown) * delta_time, chem_storage * 0.5)
+
+ // If we're not dead - we go up to the full chem cap.
+ else
+ adjust_chemicals((chem_recharge_rate - chem_recharge_slowdown) * delta_time)
+
+/*
+ * Adjust the chem charges of the ling by [amount]
+ * and clamp it between 0 and override_cap (if supplied) or chem_storage (if no override supplied)
+ */
+/datum/antagonist/changeling/proc/adjust_chemicals(amount, override_cap)
+ if(!isnum(amount))
+ return
+ var/cap_to = isnum(override_cap) ? override_cap : chem_storage
+ chem_charges = clamp(chem_charges + amount, 0, cap_to)
+
+ lingchemdisplay?.maptext = ANTAG_MAPTEXT(chem_charges, COLOR_CHANGELING_CHEMICALS)
+
+
/datum/antagonist/changeling/proc/forge_team_objectives()
if(GLOB.changeling_team_objective_type)
var/datum/objective/changeling_team_objective/team_objective = new GLOB.changeling_team_objective_type
@@ -514,16 +577,6 @@
objectives += identity_theft
escape_objective_possible = FALSE
-/datum/antagonist/changeling/proc/update_changeling_icons_added()
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_CHANGELING]
- hud.join_hud(owner.current)
- set_antag_hud(owner.current, "changeling")
-
-/datum/antagonist/changeling/proc/update_changeling_icons_removed()
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_CHANGELING]
- hud.leave_hud(owner.current)
- set_antag_hud(owner.current, null)
-
/**
* Signal proc for [COMSIG_MOB_MIDDLECLICKON] and [COMSIG_MOB_ALTCLICKON].
* Allows the changeling to sting people with a click.
@@ -670,6 +723,15 @@
return finish_preview_icon(final_icon)
+/datum/antagonist/changeling/ui_data(mob/user)
+ var/list/data = list()
+
+ data["true_name"] = changelingID
+ data["stolen_antag_info"] = antag_memory
+ data["objectives"] = get_objectives()
+
+ return data
+
/datum/outfit/changeling
name = "Changeling"
diff --git a/code/modules/antagonists/changeling/changeling_power.dm b/code/modules/antagonists/changeling/changeling_power.dm
index b7251e3fac10..4f8ddeee0311 100644
--- a/code/modules/antagonists/changeling/changeling_power.dm
+++ b/code/modules/antagonists/changeling/changeling_power.dm
@@ -5,7 +5,8 @@
/datum/action/changeling
name = "Prototype Sting - Debug button, ahelp this"
background_icon_state = "bg_changeling"
- icon_icon = 'icons/mob/actions/actions_changeling.dmi'
+ overlay_icon_state = "bg_changeling_border"
+ button_icon = 'icons/mob/actions/actions_changeling.dmi'
var/needs_button = TRUE//for passive abilities like hivemind that dont need a button
var/helptext = "" // Details
var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands)
@@ -36,49 +37,61 @@ the same goes for Remove(). if you override Remove(), call parent or else your p
return
try_to_sting(user)
+/**
+ *Contrary to the name, this proc isn't just used by changeling stings. It handles the activation of the action and the deducation of its cost.
+ *The order of the proc chain is:
+ *can_sting(). Should this fail, the process gets aborted early.
+ *sting_action(). This proc usually handles the actual effect of the action.
+ *Should sting_action succeed the following will be done:
+ *sting_feedback(). Produces feedback on the performed action. Don't ask me why this isn't handled in sting_action()
+ *The deduction of the cost of this power.
+ *Returns TRUE on a successful activation.
+ */
/datum/action/changeling/proc/try_to_sting(mob/user, mob/target)
if(!can_sting(user, target))
- return
- var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling)
+ return FALSE
+ var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(sting_action(user, target))
sting_feedback(user, target)
- c.chem_charges -= chemical_cost
+ changeling.adjust_chemicals(-chemical_cost)
+ return TRUE
+ return FALSE
/datum/action/changeling/proc/sting_action(mob/user, mob/target)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
- return 0
+ return FALSE
/datum/action/changeling/proc/sting_feedback(mob/user, mob/target)
- return 0
+ return FALSE
//Fairly important to remember to return 1 on success >.<
/datum/action/changeling/proc/can_sting(mob/living/user, mob/target)
if(!ishuman(user) && !ismonkey(user)) //typecast everything from mob to carbon from this point onwards
- return 0
+ return FALSE
if(req_human && !ishuman(user))
to_chat(user, span_warning("We cannot do that in this form!"))
- return 0
+ return FALSE
var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(c.chem_charges < chemical_cost)
to_chat(user, span_warning("We require at least [chemical_cost] unit\s of chemicals to do that!"))
- return 0
+ return FALSE
if(c.absorbedcount < req_dna)
to_chat(user, span_warning("We require at least [req_dna] sample\s of compatible DNA."))
- return 0
+ return FALSE
if(c.trueabsorbs < req_absorbs)
to_chat(user, span_warning("We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability."))
if(req_stat < user.stat)
to_chat(user, span_warning("We are incapacitated."))
- return 0
+ return FALSE
if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath))
to_chat(user, span_warning("We are incapacitated."))
- return 0
- return 1
+ return FALSE
+ return TRUE
/datum/action/changeling/proc/can_be_used_by(mob/user)
if(!user || QDELETED(user))
- return 0
+ return FALSE
if(!ishuman(user) && !ismonkey(user))
return FALSE
if(req_human && !ishuman(user))
diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm
index fa2004469f62..a630f2f15079 100644
--- a/code/modules/antagonists/changeling/powers/absorb.dm
+++ b/code/modules/antagonists/changeling/powers/absorb.dm
@@ -27,8 +27,6 @@
var/mob/living/carbon/target = user.pulling
return changeling.can_absorb_dna(target)
-
-
/datum/action/changeling/absorbDNA/sting_action(mob/user)
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
absorbtimer = clamp((16 - changeling.trueabsorbs) * 10, 0, 5 SECONDS) //the more people you eat, the faster you can absorb
@@ -101,7 +99,7 @@
if(nlog_type & LOG_SAY)
var/list/reversed = log_source[log_type]
if(islist(reversed))
- say_log = reverseRange(reversed.Copy())
+ say_log = reverse_range(reversed.Copy())
break
if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH)
@@ -130,7 +128,7 @@
target_ling.geneticpoints = 0
target_ling.canrespec = 0
changeling.chem_storage += round(target_ling.chem_storage/2)
- changeling.chem_charges += min(target_ling.chem_charges, changeling.chem_storage)
+ changeling.adjust_chemicals(round(target_ling.chem_storage/2))
target_ling.chem_charges = 0
target_ling.chem_storage = 0
changeling.absorbedcount += (target_ling.absorbedcount)
@@ -138,10 +136,8 @@
target_ling.absorbedcount = 0
target_ling.was_absorbed = TRUE
-
- changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage)
-
changeling.isabsorbing = 0
+ changeling.adjust_chemicals(10)
changeling.canrespec = 1
target.death(0)
diff --git a/code/modules/antagonists/changeling/powers/fakedeath.dm b/code/modules/antagonists/changeling/powers/fakedeath.dm
index 3c495f3c86ee..277002da4209 100644
--- a/code/modules/antagonists/changeling/powers/fakedeath.dm
+++ b/code/modules/antagonists/changeling/powers/fakedeath.dm
@@ -9,7 +9,6 @@
ignores_fakedeath = TRUE
var/revive_ready = FALSE
var/regain_respec = TRUE //variable to figure out if we give them the ability to respec again after they revive
-
//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay.
/datum/action/changeling/fakedeath/sting_action(mob/living/user)
@@ -17,12 +16,9 @@
if(revive_ready)
INVOKE_ASYNC(src, PROC_REF(revive), user)
revive_ready = FALSE
- name = "Reviving Stasis"
- desc = "We fall into a stasis, allowing us to regenerate and trick our enemies."
- button_icon_state = "fake_death"
- UpdateButtonIcon()
chemical_cost = 15
to_chat(user, span_notice("We have revived ourselves."))
+ build_all_button_icons(UPDATE_BUTTON_NAME|UPDATE_BUTTON_ICON)
var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(C)
C.canrespec = regain_respec
@@ -30,7 +26,7 @@
to_chat(user, span_notice("We begin our stasis, preparing energy to arise once more."))
if(user.stat != DEAD)
user.tod = station_time_timestamp()
- user.fakedeath("changeling") //play dead
+ user.fakedeath(CHANGELING_TRAIT) //play dead
user.update_stat()
user.update_mobility()
var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling)
@@ -41,42 +37,71 @@
return TRUE
/datum/action/changeling/fakedeath/proc/revive(mob/living/user)
- if(!user || !istype(user))
+ if(!istype(user))
return
- user.cure_fakedeath("changeling")
+
+ user.cure_fakedeath(CHANGELING_TRAIT)
user.revive(full_heal = TRUE)
- var/list/missing = user.get_missing_limbs()
- missing -= BODY_ZONE_HEAD // headless changelings are funny
- if(missing.len)
- playsound(user, 'sound/magic/demon_consume.ogg', 50, 1)
- user.visible_message("[user]'s missing limbs \
- reform, making a loud, grotesque sound!",
- "Your limbs regrow, making a \
- loud, crunchy sound and giving you great pain!",
- "You hear organic matter ripping \
- and tearing!")
- user.emote("scream")
- user.regenerate_limbs(0, list(BODY_ZONE_HEAD))
+
+ var/static/list/dont_regenerate = list(BODY_ZONE_HEAD) // headless changelings are funny
+ if(!length(user.get_missing_limbs() - dont_regenerate))
+ return
+
+ playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE)
+ user.visible_message(
+ span_warning("[user]'s missing limbs reform, making a loud, grotesque sound!"),
+ span_userdanger("Your limbs regrow, making a loud, crunchy sound and giving you great pain!"),
+ span_hear("You hear organic matter ripping and tearing!"),
+ )
+ user.emote("scream")
+ // Manually call this (outside of revive/fullheal) so we can pass our blacklist
+ user.regenerate_limbs(dont_regenerate)
user.regenerate_organs()
/datum/action/changeling/fakedeath/proc/ready_to_regenerate(mob/user)
- if(user && user.mind)
- var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling)
- if(C && C.purchasedpowers)
- to_chat(user, span_notice("We are ready to revive."))
- name = "Revive"
- desc = "We arise once more."
- button_icon_state = "revive"
- UpdateButtonIcon()
- chemical_cost = 0
- revive_ready = TRUE
+ if(!user?.mind)
+ return
+
+ var/datum/antagonist/changeling/ling = user.mind.has_antag_datum(/datum/antagonist/changeling)
+ if(!ling || !(src in user.actions))
+ return
+
+ to_chat(user, span_notice("We are ready to revive."))
+ chemical_cost = 0
+ revive_ready = TRUE
+ build_all_button_icons(UPDATE_BUTTON_NAME|UPDATE_BUTTON_ICON)
/datum/action/changeling/fakedeath/can_sting(mob/living/user)
- if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling") && !revive_ready)
- to_chat(user, span_warning("We are already reviving."))
+ if(revive_ready)
+ return ..()
+
+ if(!can_enter_stasis(user))
return
- if(!user.stat && !revive_ready) //Confirmation for living changelings if they want to fake their death
- var/result = tgui_alert(usr,"Are we sure we wish to fake our own death?",,list("Yes", "No"))
- if(result != "Yes")
+ //Confirmation for living changelings if they want to fake their death
+ if(user.stat != DEAD)
+ if(tgui_alert(user, "Are we sure we wish to fake our own death?", "Feign Death", list("Yes", "No")) != "Yes")
return
+ if(QDELETED(user) || QDELETED(src) || !can_enter_stasis(user))
+ return
+
return ..()
+
+/datum/action/changeling/fakedeath/proc/can_enter_stasis(mob/living/user)
+ if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, CHANGELING_TRAIT))
+ user.balloon_alert(user, "already reviving!")
+ return FALSE
+ return TRUE
+
+/datum/action/changeling/fakedeath/update_button_name(atom/movable/screen/movable/action_button/button, force)
+ if(revive_ready)
+ name = "Revive"
+ desc = "We arise once more."
+ else
+ name = "Reviving Stasis"
+ desc = "We fall into a stasis, allowing us to regenerate and trick our enemies."
+ return ..()
+
+/datum/action/changeling/fakedeath/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force)
+ button_icon_state = revive_ready ? "revive" : "fake_death"
+ return ..()
+
diff --git a/code/modules/antagonists/changeling/powers/headcrab.dm b/code/modules/antagonists/changeling/powers/headcrab.dm
index 17ab74eeac20..11df61d02919 100644
--- a/code/modules/antagonists/changeling/powers/headcrab.dm
+++ b/code/modules/antagonists/changeling/powers/headcrab.dm
@@ -31,7 +31,7 @@
H.Stun(20)
H.blur_eyes(20)
eyes.applyOrganDamage(5)
- H.confused += 3
+ H.adjust_confusion(3 SECONDS)
for(var/mob/living/silicon/S in range(2,user))
to_chat(S, span_userdanger("Your sensors are disabled by a shower of blood!"))
S.Paralyze(60)
diff --git a/code/modules/antagonists/changeling/powers/lesserform.dm b/code/modules/antagonists/changeling/powers/lesserform.dm
index 36856bc0fd94..e54f4cb515b3 100644
--- a/code/modules/antagonists/changeling/powers/lesserform.dm
+++ b/code/modules/antagonists/changeling/powers/lesserform.dm
@@ -6,7 +6,6 @@
chemical_cost = 5
dna_cost = 1
req_human = 1
- check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN
//Transform into a monkey.
/datum/action/changeling/lesserform/sting_action(mob/living/carbon/human/user)
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index 27254355ca82..6f167a3bd2ae 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -138,7 +138,7 @@
name = "Arm Blade"
desc = "We reform one of our arms into a deadly blade. Costs 20 chemicals."
helptext = "We may retract our armblade in the same manner as we form it. Cannot be used while in lesser form."
-// button_icon = 'icons/obj/changeling.dmi'
+// background_icon = 'icons/obj/changeling.dmi'
button_icon_state = "armblade"
chemical_cost = 20
dna_cost = 2
@@ -570,7 +570,7 @@
name = "Flesh Maul"
desc = "We reform one of our arms into a dense mass of flesh and bone. Costs 20 chemicals."
helptext = "We may reabsorb the mass in the same way it was formed. It is too heavy to be used in our lesser form."
-// button_icon = 'icons/obj/changeling.dmi'
+// background_icon = 'icons/obj/changeling.dmi'
button_icon_state = "flesh_maul"
chemical_cost = 20
dna_cost = 3
diff --git a/code/modules/antagonists/changeling/powers/shriek.dm b/code/modules/antagonists/changeling/powers/shriek.dm
index 7cee363ff333..c45a74b0c493 100644
--- a/code/modules/antagonists/changeling/powers/shriek.dm
+++ b/code/modules/antagonists/changeling/powers/shriek.dm
@@ -19,8 +19,8 @@
var/mob/living/carbon/C = M
if(!C.mind || !C.mind.has_antag_datum(/datum/antagonist/changeling))
C.adjustEarDamage(0, 30)
- C.confused += 25
- C.Jitter(50)
+ C.adjust_confusion(25 SECONDS)
+ C.adjust_jitter(50 SECONDS)
else
SEND_SOUND(C, sound('sound/effects/screech.ogg'))
diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm
index c9982d1d7673..c546bdc52f4f 100644
--- a/code/modules/antagonists/changeling/powers/tiny_prick.dm
+++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm
@@ -21,17 +21,17 @@
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.chosen_sting = src
- user.hud_used.lingstingdisplay.icon = 'icons/obj/changeling.dmi'
- user.hud_used.lingstingdisplay.icon_state = sting_icon
- user.hud_used.lingstingdisplay.invisibility = 0
+ changeling.lingstingdisplay.icon = 'icons/obj/changeling.dmi'
+ changeling.lingstingdisplay.icon_state = sting_icon
+ changeling.lingstingdisplay.invisibility = 0
/datum/action/changeling/sting/proc/unset_sting(mob/user)
to_chat(user, span_warning("We retract our sting, we can't sting anyone for now."))
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.chosen_sting = null
- user.hud_used.lingstingdisplay.icon_state = null
- user.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT
+ changeling.lingstingdisplay.icon_state = null
+ changeling.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT
/mob/living/carbon/proc/unset_sting()
if(mind)
diff --git a/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm b/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm
index 660b191a401f..e3b30c480d61 100644
--- a/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/city_of_cogs_rift.dm
@@ -57,7 +57,7 @@
if(ismob(AM) && is_servant_of_ratvar(AM))
T = GLOB.ark_of_the_clockwork_justiciar ? get_step(GLOB.ark_of_the_clockwork_justiciar, SOUTH) : get_turf(pick(GLOB.servant_spawns))
else // Handle mechas and such
- var/list/target_contents = AM.GetAllContents() + AM
+ var/list/target_contents = AM.get_all_contents() + AM
for(var/mob/living/L in target_contents)
if(is_servant_of_ratvar(L) && L.stat != DEAD) // Having a living cultist in your inventory sends you to the cultist spawn
T = GLOB.ark_of_the_clockwork_justiciar ? get_step(GLOB.ark_of_the_clockwork_justiciar, SOUTH) : get_turf(pick(GLOB.servant_spawns))
@@ -72,6 +72,6 @@
var/mob/living/L = AM
L.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static)
L.clear_fullscreen("flash", 5)
- var/obj/item/transfer_valve/TTV = locate() in L.GetAllContents()
+ var/obj/item/transfer_valve/TTV = locate() in L.get_all_contents()
if(TTV)
to_chat(L, span_userdanger("The air resonates with the Ark's presence; your explosives will be significantly dampened here!"))
diff --git a/code/modules/antagonists/clockcult/clock_effects/servant_blocker.dm b/code/modules/antagonists/clockcult/clock_effects/servant_blocker.dm
index 7b7c3174fd3d..27d456a1bd4c 100644
--- a/code/modules/antagonists/clockcult/clock_effects/servant_blocker.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/servant_blocker.dm
@@ -19,7 +19,7 @@
/obj/effect/clockwork/servant_blocker/Cross(atom/movable/M)
. = ..()
- var/list/target_contents = M.GetAllContents() + M
+ var/list/target_contents = M.get_all_contents() + M
for(var/mob/living/L in target_contents)
if(is_servant_of_ratvar(L) && get_dir(M, src) != dir && L.stat != DEAD) //Unless we're on the side the arrow is pointing directly away from, no-go
to_chat(L, span_danger("The space beyond here can't be accessed by you or other servants."))
diff --git a/code/modules/antagonists/clockcult/clock_helpers/hierophant_network.dm b/code/modules/antagonists/clockcult/clock_helpers/hierophant_network.dm
index 37f6f0b2d77e..592ea8aeb8b3 100644
--- a/code/modules/antagonists/clockcult/clock_helpers/hierophant_network.dm
+++ b/code/modules/antagonists/clockcult/clock_helpers/hierophant_network.dm
@@ -26,23 +26,23 @@
/datum/action/innate/hierophant
name = "Hierophant Network"
desc = "Allows you to communicate with other Servants."
- icon_icon = 'icons/mob/actions/actions_clockcult.dmi'
+ button_icon = 'icons/mob/actions/actions_clockcult.dmi'
button_icon_state = "hierophant"
background_icon_state = "bg_clock"
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS
+ check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS
buttontooltipstyle = "clockcult"
var/title = "Servant"
var/span_for_name = "heavy_brass"
var/span_for_message = "brass"
-/datum/action/innate/hierophant/IsAvailable()
+/datum/action/innate/hierophant/IsAvailable(feedback = FALSE)
if(!is_servant_of_ratvar(owner))
return FALSE
return ..()
/datum/action/innate/hierophant/Activate()
var/input = stripped_input(usr, "Please enter a message to send to other servants.", "Hierophant Network", "")
- if(!input || !IsAvailable())
+ if(!input || !IsAvailable(feedback = FALSE))
return
if(ishuman(owner))
clockwork_say(owner, "[text2ratvar("Servants, hear my words: [input]")]", TRUE)
diff --git a/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm b/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm
index 5a5afc1d9088..37434bb8fb49 100644
--- a/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm
+++ b/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm
@@ -1,71 +1,74 @@
-//The base for slab-bound/based ranged abilities
-/obj/effect/proc_holder/slab
+//The base for slab-bound/based ranged abilities //',:)
+/datum/action/innate/slab
+ click_action = TRUE
var/obj/item/clockwork/slab/slab
var/successful = FALSE
- var/finished = FALSE
var/in_progress = FALSE
+ var/finished = FALSE
-/obj/effect/proc_holder/slab/Destroy()
+/datum/action/innate/slab/Destroy()
slab = null
return ..()
-/obj/effect/proc_holder/slab/remove_ranged_ability(msg)
- ..()
+/datum/action/innate/slab/IsAvailable(feedback = FALSE)
+ return TRUE
+
+/datum/action/innate/slab/unset_ranged_ability(mob/living/on_who)
+ . = ..()
finished = TRUE
- QDEL_IN(src, 6)
+ QDEL_IN(src, 1 SECONDS)
-/obj/effect/proc_holder/slab/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..() || in_progress)
- return TRUE
- if(ranged_ability_user.incapacitated() || !slab || !(slab in ranged_ability_user.held_items) || target == slab)
- remove_ranged_ability()
- return TRUE
+/datum/action/innate/slab/InterceptClickOn(mob/living/caller, params, atom/clicked_on)
+ if(in_progress)
+ return FALSE
+ if(caller.incapacitated() || !slab || !(slab in caller.held_items) || clicked_on == slab)
+ unset_ranged_ability(caller)
+ return FALSE
-//For the Hateful Manacles scripture; applies replicant handcuffs to the target.
-/obj/effect/proc_holder/slab/hateful_manacles
+ . = ..()
+ if(.)
+ unset_ranged_ability(caller || usr)
-/obj/effect/proc_holder/slab/hateful_manacles/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return TRUE
+//For the Hateful Manacles scripture; applies replicant handcuffs to the clicked_on.
+/datum/action/innate/slab/hateful_manacles
- var/turf/T = ranged_ability_user.loc
+/datum/action/innate/slab/hateful_manacles/do_ability(mob/living/caller, params, atom/clicked_on)
+ var/turf/T = caller.loc
if(!isturf(T))
- return TRUE
+ return FALSE
- if(iscarbon(target) && target.Adjacent(ranged_ability_user))
- var/mob/living/carbon/L = target
+ if(iscarbon(clicked_on) && clicked_on.Adjacent(caller))
+ var/mob/living/carbon/L = clicked_on
if(is_servant_of_ratvar(L))
- to_chat(ranged_ability_user, span_neovgre("\"[L.p_theyre(TRUE)] a servant.\""))
- return TRUE
+ to_chat(caller, span_neovgre("\"[L.p_theyre(TRUE)] a servant.\""))
+ return FALSE
else if(L.stat)
- to_chat(ranged_ability_user, span_neovgre("\"There is use in shackling the dead, but for examples.\""))
- return TRUE
- else if (istype(L.handcuffed,/obj/item/restraints/handcuffs/clockwork))
- to_chat(ranged_ability_user, span_neovgre("\"[L.p_theyre(TRUE)] already helpless, no?\""))
- return TRUE
+ to_chat(caller, span_neovgre("\"There is use in shackling the dead, but for examples.\""))
+ return FALSE
+ else if (istype(L.handcuffed, /obj/item/restraints/handcuffs/clockwork))
+ to_chat(caller, span_neovgre("\"[L.p_theyre(TRUE)] already helpless, no?\""))
+ return FALSE
//yogs start -- shackling people with just one arm is right-out
else if(L.get_num_arms(FALSE) < 2 && !L.get_arm_ignore())
- to_chat(ranged_ability_user, span_neovgre("\"[L.p_theyre(TRUE)] lacking in arms necessary for shackling.\""))
+ to_chat(caller, span_neovgre("\"[L.p_theyre(TRUE)] lacking in arms necessary for shackling.\""))
return TRUE
//yogs end
- playsound(loc, 'sound/weapons/handcuffs.ogg', 30, TRUE)
- ranged_ability_user.visible_message(span_danger("[ranged_ability_user] begins forming manacles around [L]'s wrists!"), \
+ playsound(caller.loc, 'sound/weapons/handcuffs.ogg', 30, TRUE)
+ caller.visible_message(span_danger("[caller] begins forming manacles around [L]'s wrists!"), \
"[span_neovgre_small("You begin shaping replicant alloy into manacles around [L]'s wrists...")]")
- to_chat(L, span_userdanger("[ranged_ability_user] begins forming manacles around your wrists!"))
- if(do_mob(ranged_ability_user, L, 30))
+ to_chat(L, span_userdanger("[caller] begins forming manacles around your wrists!"))
+ if(do_mob(caller, L, 30))
if(!(istype(L.handcuffed,/obj/item/restraints/handcuffs/clockwork)))
- L.handcuffed = new/obj/item/restraints/handcuffs/clockwork(L)
+ L.set_handcuffed(new /obj/item/restraints/handcuffs/clockwork(L))
L.update_handcuffed()
- to_chat(ranged_ability_user, "[span_neovgre_small("You shackle [L].")]")
- log_combat(ranged_ability_user, L, "handcuffed")
+ to_chat(caller, "[span_neovgre_small("You shackle [L].")]")
+ log_combat(caller, L, "handcuffed")
else
- to_chat(ranged_ability_user, span_warning("You fail to shackle [L]."))
+ to_chat(caller, span_warning("You fail to shackle [L]."))
successful = TRUE
- remove_ranged_ability()
-
return TRUE
/obj/item/restraints/handcuffs/clockwork
@@ -80,25 +83,22 @@
span_userdanger("Your [name] break apart as they're removed!"))
. = ..()
-//For the Sentinel's Compromise scripture; heals a target servant.
-/obj/effect/proc_holder/slab/compromise
- ranged_mousepointer = 'icons/effects/compromise_target.dmi'
-
-/obj/effect/proc_holder/slab/compromise/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return TRUE
+//For the Sentinel's Compromise scripture; heals a clicked_on servant.
+/datum/action/innate/slab/compromise
+ ranged_mousepointer = 'icons/effects/mouse_pointers/compromise_target.dmi'
- var/turf/T = ranged_ability_user.loc
+/datum/action/innate/slab/compromise/do_ability(mob/living/caller, params, atom/clicked_on)
+ var/turf/T = caller.loc
if(!isturf(T))
- return TRUE
+ return FALSE
- if(isliving(target) && (target in view(7, get_turf(ranged_ability_user))))
- var/mob/living/L = target
+ if(isliving(clicked_on) && (clicked_on in view(7, get_turf(caller))))
+ var/mob/living/L = clicked_on
if(!is_servant_of_ratvar(L))
- to_chat(ranged_ability_user, span_inathneq("\"[L] does not yet serve Ratvar.\""))
+ to_chat(caller, span_inathneq("\"[L] does not yet serve Ratvar.\""))
return TRUE
if(L.stat == DEAD)
- to_chat(ranged_ability_user, span_inathneq("\"[L.p_theyre(TRUE)] dead. [text2ratvar("Oh, child. To have your life cut short...")]\""))
+ to_chat(caller, span_inathneq("\"[L.p_theyre(TRUE)] dead. [text2ratvar("Oh, child. To have your life cut short...")]\""))
return TRUE
var/brutedamage = L.getBruteLoss()
@@ -106,67 +106,60 @@
var/oxydamage = L.getOxyLoss()
var/totaldamage = brutedamage + burndamage + oxydamage
if(!totaldamage && (!L.reagents || !L.reagents.has_reagent(/datum/reagent/water/holywater)))
- to_chat(ranged_ability_user, span_inathneq("\"[L] is unhurt and untainted.\""))
+ to_chat(caller, span_inathneq("\"[L] is unhurt and untainted.\""))
return TRUE
successful = TRUE
- to_chat(ranged_ability_user, span_brass("You bathe [L == ranged_ability_user ? "yourself":"[L]"] in Inath-neq's power!"))
- var/targetturf = get_turf(L)
+ to_chat(caller, span_brass("You bathe [L == caller ? "yourself":"[L]"] in Inath-neq's power!"))
+ var/clicked_onturf = get_turf(L)
var/has_holy_water = (L.reagents && L.reagents.has_reagent(/datum/reagent/water/holywater))
var/healseverity = max(round(totaldamage*0.05, 1), 1) //shows the general severity of the damage you just healed, 1 glow per 20
for(var/i in 1 to healseverity)
- new /obj/effect/temp_visual/heal(targetturf, "#1E8CE1")
+ new /obj/effect/temp_visual/heal(clicked_onturf, "#1E8CE1")
if(totaldamage)
L.adjustBruteLoss(-brutedamage, TRUE, FALSE, BODYPART_ANY)
L.adjustFireLoss(-burndamage, TRUE, FALSE, BODYPART_ANY)
L.adjustOxyLoss(-oxydamage)
L.adjustToxLoss(totaldamage * 0.5, TRUE, TRUE)
- clockwork_say(ranged_ability_user, text2ratvar("[has_holy_water ? "Heal tainted" : "Mend wounded"] flesh!"))
- log_combat(ranged_ability_user, L, "healed with Sentinel's Compromise")
+ clockwork_say(caller, text2ratvar("[has_holy_water ? "Heal tainted" : "Mend wounded"] flesh!"))
+ log_combat(caller, L, "healed with Sentinel's Compromise")
L.visible_message(span_warning("A blue light washes over [L], [has_holy_water ? "causing [L.p_them()] to briefly glow as it mends" : " mending"] [L.p_their()] bruises and burns!"), \
"[span_heavy_brass("You feel Inath-neq's power healing your wounds[has_holy_water ? " and purging the darkness within you" : ""], but a deep nausea overcomes you!")]")
else
- clockwork_say(ranged_ability_user, text2ratvar("Purge foul darkness!"))
- log_combat(ranged_ability_user, L, "purged of holy water with Sentinel's Compromise")
+ clockwork_say(caller, text2ratvar("Purge foul darkness!"))
+ log_combat(caller, L, "purged of holy water with Sentinel's Compromise")
L.visible_message(span_warning("A blue light washes over [L], causing [L.p_them()] to briefly glow!"), \
"[span_heavy_brass("You feel Inath-neq's power purging the darkness within you!")]")
- playsound(targetturf, 'sound/magic/staff_healing.ogg', 50, 1)
+ playsound(clicked_onturf, 'sound/magic/staff_healing.ogg', 50, 1)
if(has_holy_water)
L.reagents.remove_reagent(/datum/reagent/water/holywater, 1000)
- remove_ranged_ability()
-
return TRUE
-//For the Kindle scripture; stuns and mutes a target non-servant.
-/obj/effect/proc_holder/slab/kindle
- ranged_mousepointer = 'icons/effects/volt_target.dmi'
-
-/obj/effect/proc_holder/slab/kindle/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return TRUE
+//For the Kindle scripture; stuns and mutes a clicked_on non-servant.
+/datum/action/innate/slab/kindle
+ ranged_mousepointer = 'icons/effects/mouse_pointers/volt_target.dmi'
- var/turf/T = ranged_ability_user.loc
+/datum/action/innate/slab/kindle/do_ability(mob/living/caller, params, atom/clicked_on)
+ var/turf/T = caller.loc
if(!isturf(T))
- return TRUE
+ return FALSE
- if(target in view(7, get_turf(ranged_ability_user)))
+ if(clicked_on in view(7, get_turf(caller)))
successful = TRUE
- var/turf/U = get_turf(target)
- to_chat(ranged_ability_user, span_brass("You release the light of Ratvar!"))
- clockwork_say(ranged_ability_user, text2ratvar("Purge all untruths and honor Engine!"))
- log_combat(ranged_ability_user, U, "fired at with Kindle")
- playsound(ranged_ability_user, 'sound/magic/blink.ogg', 50, TRUE, frequency = 0.5)
+ var/turf/U = get_turf(clicked_on)
+ to_chat(caller, span_brass("You release the light of Ratvar!"))
+ clockwork_say(caller, text2ratvar("Purge all untruths and honor Engine!"))
+ log_combat(caller, U, "fired at with Kindle")
+ playsound(caller, 'sound/magic/blink.ogg', 50, TRUE, frequency = 0.5)
var/obj/item/projectile/kindle/A = new(T)
- A.preparePixelProjectile(target, caller, params)
+ A.preparePixelProjectile(clicked_on, caller, params)
A.fire()
- remove_ranged_ability()
-
return TRUE
/obj/item/projectile/kindle
@@ -183,9 +176,9 @@
visible_message(span_warning("[src] flickers out!"))
. = ..()
-/obj/item/projectile/kindle/on_hit(atom/target, blocked = FALSE)
- if(isliving(target))
- var/mob/living/L = target
+/obj/item/projectile/kindle/on_hit(atom/clicked_on, blocked = FALSE)
+ if(isliving(clicked_on))
+ var/mob/living/L = clicked_on
if(is_servant_of_ratvar(L) || L.stat || L.has_status_effect(STATUS_EFFECT_KINDLE))
return BULLET_ACT_HIT
var/atom/O = L.anti_magic_check()
@@ -200,78 +193,70 @@
else
L.visible_message(span_warning("[L]'s eyes blaze with brilliant light!"), \
span_userdanger("Your vision suddenly screams with white-hot light!"))
- L.Paralyze(15)
+ L.Paralyze(1.5 SECONDS)
L.apply_status_effect(STATUS_EFFECT_KINDLE)
L.flash_act(1, 1)
if(iscultist(L))
L.adjustFireLoss(15)
- ..()
+ return ..()
-//For the cyborg Linked Vanguard scripture, grants you and a nearby ally Vanguard
-/obj/effect/proc_holder/slab/vanguard
- ranged_mousepointer = 'icons/effects/vanguard_target.dmi'
-/obj/effect/proc_holder/slab/vanguard/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return TRUE
+//For the cyborg Linked Vanguard scripture, grants you and a nearby ally Vanguard
+/datum/action/innate/slab/vanguard
+ ranged_mousepointer = 'icons/effects/mouse_pointers/vanguard_target.dmi'
- var/turf/T = ranged_ability_user.loc
+/datum/action/innate/slab/vanguard/do_ability(mob/living/caller, params, atom/clicked_on)
+ var/turf/T = caller.loc
if(!isturf(T))
- return TRUE
+ return FALSE
- if(isliving(target) && (target in view(7, get_turf(ranged_ability_user))))
- var/mob/living/L = target
+ if(isliving(clicked_on) && (clicked_on in view(7, get_turf(caller))))
+ var/mob/living/L = clicked_on
if(!is_servant_of_ratvar(L))
- to_chat(ranged_ability_user, span_inathneq("\"[L] does not yet serve Ratvar.\""))
- return TRUE
+ to_chat(caller, span_inathneq("\"[L] does not yet serve Ratvar.\""))
+ return FALSE
if(L.stat == DEAD)
- to_chat(ranged_ability_user, span_inathneq("\"[L.p_theyre(TRUE)] dead. [text2ratvar("Oh, child. To have your life cut short...")]\""))
- return TRUE
+ to_chat(caller, span_inathneq("\"[L.p_theyre(TRUE)] dead. [text2ratvar("Oh, child. To have your life cut short...")]\""))
+ return FALSE
if(islist(L.stun_absorption) && L.stun_absorption["vanguard"] && L.stun_absorption["vanguard"]["end_time"] > world.time)
- to_chat(ranged_ability_user, span_inathneq("\"[L.p_theyre(TRUE)] already shielded by a Vanguard.\""))
- return TRUE
+ to_chat(caller, span_inathneq("\"[L.p_theyre(TRUE)] already shielded by a Vanguard.\""))
+ return FALSE
successful = TRUE
- if(L == ranged_ability_user)
+ if(L == caller)
for(var/mob/living/LT in spiral_range(7, T))
- if(LT.stat == DEAD || !is_servant_of_ratvar(LT) || LT == ranged_ability_user || !(LT in view(7, get_turf(ranged_ability_user))) || \
+ if(LT.stat == DEAD || !is_servant_of_ratvar(LT) || LT == caller || !(LT in view(7, get_turf(caller))) || \
(islist(LT.stun_absorption) && LT.stun_absorption["vanguard"] && LT.stun_absorption["vanguard"]["end_time"] > world.time))
continue
L = LT
break
L.apply_status_effect(STATUS_EFFECT_VANGUARD)
- ranged_ability_user.apply_status_effect(STATUS_EFFECT_VANGUARD)
+ caller.apply_status_effect(STATUS_EFFECT_VANGUARD)
- clockwork_say(ranged_ability_user, text2ratvar("Shield us from darkness!"))
-
- remove_ranged_ability()
+ clockwork_say(caller, text2ratvar("Shield us from darkness!"))
return TRUE
//For the cyborg Judicial Marker scripture, places a judicial marker
-/obj/effect/proc_holder/slab/judicial
+/datum/action/innate/slab/judicial
ranged_mousepointer = 'icons/effects/visor_reticule.dmi'
-/obj/effect/proc_holder/slab/judicial/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return TRUE
-
- var/turf/T = ranged_ability_user.loc
+/datum/action/innate/slab/judicial/do_ability(mob/living/caller, params, atom/clicked_on)
+ var/turf/T = caller.loc
if(!isturf(T))
- return TRUE
+ return FALSE
- if(target in view(7, get_turf(ranged_ability_user)))
+ if(clicked_on in view(7, get_turf(caller)))
successful = TRUE
- clockwork_say(ranged_ability_user, text2ratvar("Kneel, heathens!"))
- ranged_ability_user.visible_message(span_warning("[ranged_ability_user]'s eyes fire a stream of energy at [target], creating a strange mark!"), \
- "[span_heavy_brass("You direct the judicial force to [target].")]")
- var/turf/targetturf = get_turf(target)
- new/obj/effect/clockwork/judicial_marker(targetturf, ranged_ability_user)
- log_combat(ranged_ability_user, targetturf, "created a judicial marker")
- remove_ranged_ability()
+ clockwork_say(caller, text2ratvar("Kneel, heathens!"))
+ caller.visible_message(span_warning("[caller]'s eyes fire a stream of energy at [clicked_on], creating a strange mark!"), \
+ "[span_heavy_brass("You direct the judicial force to [clicked_on].")]")
+ var/turf/clicked_onturf = get_turf(clicked_on)
+ new/obj/effect/clockwork/judicial_marker(clicked_onturf, caller)
+ log_combat(caller, clicked_onturf, "created a judicial marker")
return TRUE
diff --git a/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm b/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm
index ed164e9e3793..ba47f370d95f 100644
--- a/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm
+++ b/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm
@@ -3,16 +3,16 @@
/datum/action/innate/call_weapon
name = "Call Weapon"
desc = "This definitely shouldn't exist."
- icon_icon = 'icons/mob/actions/actions_clockcult.dmi'
+ button_icon = 'icons/mob/actions/actions_clockcult.dmi'
button_icon_state = "ratvarian_spear"
background_icon_state = "bg_clock"
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS
+ check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS
buttontooltipstyle = "clockcult"
var/cooldown = 0
var/obj/item/clockwork/weapon/weapon_type //The type of weapon to create
var/obj/item/clockwork/weapon/weapon
-/datum/action/innate/call_weapon/IsAvailable()
+/datum/action/innate/call_weapon/IsAvailable(feedback = FALSE)
if(!is_servant_of_ratvar(owner))
qdel(src)
return
@@ -39,11 +39,11 @@
owner.visible_message(span_warning("A [weapon.name] materializes in [owner]'s hands!"), span_brass("You call forth your [weapon.name]!"))
weapon.forceMove(get_turf(owner))
owner.put_in_hands(weapon)
- owner.update_action_buttons_icon()
+ owner.update_mob_action_buttons()
return TRUE
/datum/action/innate/call_weapon/proc/weapon_reset(cooldown_time)
cooldown = world.time + cooldown_time
- addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob, update_action_buttons_icon)), cooldown_time)
- owner.update_action_buttons_icon()
+ addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob, update_mob_action_buttons)), cooldown_time)
+ owner.update_mob_action_buttons()
QDEL_NULL(weapon)
diff --git a/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm
index b78f7d95a07f..ffe71c9c2aad 100644
--- a/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm
+++ b/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm
@@ -117,7 +117,7 @@
user.emote("scream")
user.apply_damage(15, BURN, BODY_ZONE_CHEST)
user.adjust_fire_stacks(2)
- user.IgniteMob()
+ user.ignite_mob()
addtimer(CALLBACK(user, TYPE_PROC_REF(/mob/living, dropItemToGround), src, TRUE), 1)
/obj/item/clothing/gloves/clockwork
diff --git a/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm
index 5506c421231a..ff0819cb91b9 100644
--- a/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm
+++ b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm
@@ -16,7 +16,7 @@
var/no_cost = FALSE //If the slab is admin-only and needs no components and has no scripture locks
var/speed_multiplier = 1 //multiples how fast this slab recites scripture
var/selected_scripture = SCRIPTURE_DRIVER
- var/obj/effect/proc_holder/slab/slab_ability //the slab's current bound ability, for certain scripture
+ var/datum/action/innate/slab/slab_ability //the slab's current bound ability, for certain scripture
var/recollecting = FALSE //if we're looking at fancy recollection
var/recollection_category = "Default"
@@ -93,8 +93,8 @@
/obj/item/clockwork/slab/Destroy()
STOP_PROCESSING(SSobj, src)
- if(slab_ability && slab_ability.ranged_ability_user)
- slab_ability.remove_ranged_ability()
+ if(isliving(loc))
+ slab_ability.unset_ranged_ability(loc)
slab_ability = null
return ..()
@@ -102,6 +102,10 @@
. = ..()
addtimer(CALLBACK(src, PROC_REF(check_on_mob), user), 1) //dropped is called before the item is out of the slot, so we need to check slightly later
+/obj/item/clockwork/slab/equipped(mob/user)
+ . = ..()
+ update_quickbind()
+
/obj/item/clockwork/slab/worn_overlays(isinhands = FALSE, icon_file)
. = list()
if(isinhands && item_state && inhand_overlay)
@@ -109,8 +113,9 @@
. += M
/obj/item/clockwork/slab/proc/check_on_mob(mob/user)
- if(user && !(src in user.held_items) && slab_ability && slab_ability.ranged_ability_user) //if we happen to check and we AREN'T in user's hands, remove whatever ability we have
- slab_ability.remove_ranged_ability()
+ if(user && !(src in user.held_items) && slab_ability?.owner) //if we happen to check and we AREN'T in user's hands, remove whatever ability we have
+ slab_ability.unset_ranged_ability(user)
+ update_quickbind(user, TRUE)
//Power generation
/obj/item/clockwork/slab/process()
@@ -149,8 +154,8 @@
return 0
if(!is_servant_of_ratvar(user))
to_chat(user, span_warning("The information on [src]'s display shifts rapidly. After a moment, your head begins to pound, and you tear your eyes away."))
- user.confused += 5
- user.dizziness += 5
+ user.adjust_confusion(5 SECONDS)
+ user.adjust_dizzy(5 SECONDS)
return 0
if(busy)
to_chat(user, span_warning("[src] refuses to work, displaying the message: \"[busy]!\""))
@@ -504,19 +509,31 @@
quickbound[index] = scripture
update_quickbind()
-/obj/item/clockwork/slab/proc/update_quickbind()
- for(var/datum/action/item_action/clock/quickbind/Q in actions)
- qdel(Q) //regenerate all our quickbound scriptures
- if(LAZYLEN(quickbound))
- for(var/i in 1 to quickbound.len)
- if(!quickbound[i])
- continue
- var/datum/action/item_action/clock/quickbind/Q = new /datum/action/item_action/clock/quickbind(src)
- Q.scripture_index = i
- var/datum/clockwork_scripture/quickbind_slot = GLOB.all_scripture[quickbound[i]]
- Q.name = "[quickbind_slot.name] ([Q.scripture_index])"
- Q.desc = quickbind_slot.quickbind_desc
- Q.button_icon_state = quickbind_slot.name
- Q.UpdateButtonIcon()
- if(isliving(loc))
- Q.Grant(loc)
+/obj/item/clockwork/slab/proc/update_quickbind(mob/mob_override, removing_only = FALSE)
+ var/list/actions_to_elim = list()
+ if(!mob_override && !isliving(loc))
+ actions_to_elim = actions
+ else
+ var/mob/living/user = mob_override || loc
+ if(!is_servant_of_ratvar(user))
+ return
+ actions_to_elim = user.actions
+ for(var/datum/action/item_action/clock/quickbind/existing_binds in actions_to_elim)
+ existing_binds.Remove(existing_binds.owner) //regenerate all our quickbound scriptures
+ if(removing_only)
+ return
+ if(!LAZYLEN(quickbound))
+ return
+ for(var/i in 1 to quickbound.len)
+ if(!quickbound[i])
+ continue
+ var/datum/action/item_action/clock/quickbind/Q = new /datum/action/item_action/clock/quickbind(src)
+ Q.scripture_index = i
+ var/datum/clockwork_scripture/quickbind_slot = GLOB.all_scripture[quickbound[i]]
+ Q.name = "[quickbind_slot.name] ([Q.scripture_index])"
+ Q.desc = quickbind_slot.quickbind_desc
+ Q.button_icon_state = quickbind_slot.name
+ qdel(Q.GetComponent(/datum/component/action_item_overlay))
+ if(isliving(loc))
+ Q.Grant(loc)
+ Q.build_all_button_icons()
diff --git a/code/modules/antagonists/clockcult/clock_items/judicial_visor.dm b/code/modules/antagonists/clockcult/clock_items/judicial_visor.dm
index 399b4e75e386..1534d5553c8e 100644
--- a/code/modules/antagonists/clockcult/clock_items/judicial_visor.dm
+++ b/code/modules/antagonists/clockcult/clock_items/judicial_visor.dm
@@ -9,7 +9,7 @@
flash_protect = 1
var/active = FALSE //If the visor is online
var/recharging = FALSE //If the visor is currently recharging
- var/obj/effect/proc_holder/judicial_visor/blaster
+ var/datum/action/cooldown/judicial_visor/blaster
var/recharge_cooldown = 300 //divided by 10 if ratvar is alive
actions_types = list(/datum/action/item_action/clock/toggle_visor)
@@ -21,8 +21,8 @@
/obj/item/clothing/glasses/judicial_visor/Destroy()
GLOB.all_clockwork_objects -= src
- if(blaster.ranged_ability_user)
- blaster.remove_ranged_ability()
+ if(blaster.owner)
+ blaster.unset_click_ability(blaster.owner)
blaster.visor = null
qdel(blaster)
return ..()
@@ -36,8 +36,8 @@
..()
if(slot != SLOT_GLASSES)
update_status(FALSE)
- if(blaster.ranged_ability_user)
- blaster.remove_ranged_ability()
+ if(blaster.owner)
+ blaster.unset_click_ability(blaster.owner)
return 0
if(is_servant_of_ratvar(user))
update_status(TRUE)
@@ -47,7 +47,7 @@
to_chat(user, "[span_heavy_brass("\"Consider yourself judged, whelp.\"")]")
to_chat(user, span_userdanger("You suddenly catch fire!"))
user.adjust_fire_stacks(5)
- user.IgniteMob()
+ user.ignite_mob()
return 1
/obj/item/clothing/glasses/judicial_visor/dropped(mob/user)
@@ -57,12 +57,12 @@
/obj/item/clothing/glasses/judicial_visor/proc/check_on_mob(mob/user)
if(user && src != user.get_item_by_slot(SLOT_GLASSES)) //if we happen to check and we AREN'T in the slot, we need to remove our shit from whoever we got dropped from
update_status(FALSE)
- if(blaster.ranged_ability_user)
- blaster.remove_ranged_ability()
+ if(blaster.owner)
+ blaster.unset_click_ability(user)
/obj/item/clothing/glasses/judicial_visor/attack_self(mob/user)
if(is_servant_of_ratvar(user) && src == user.get_item_by_slot(SLOT_GLASSES))
- blaster.toggle(user)
+ blaster.Trigger()
/obj/item/clothing/glasses/judicial_visor/proc/update_status(change_to)
if(recharging || !isliving(loc))
@@ -73,7 +73,7 @@
var/mob/living/L = loc
active = change_to
icon_state = "judicial_visor_[active]"
- L.update_action_buttons_icon()
+ L.update_mob_action_buttons()
L.update_inv_glasses()
if(!is_servant_of_ratvar(L) || L.stat)
return 0
@@ -95,55 +95,46 @@
active = FALSE
icon_state = "judicial_visor_[active]"
if(user)
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
user.update_inv_glasses()
-/obj/effect/proc_holder/judicial_visor
- active = FALSE
+/datum/action/cooldown/judicial_visor
ranged_mousepointer = 'icons/effects/visor_reticule.dmi'
var/obj/item/clothing/glasses/judicial_visor/visor
-/obj/effect/proc_holder/judicial_visor/proc/toggle(mob/user)
- var/message
- if(active)
- message = span_brass("You dispel the power of [visor].")
- remove_ranged_ability(message)
- else
- message = span_brass("You harness [visor]'s power. Left-click to place a judicial marker!")
- add_ranged_ability(user, message)
-
-/obj/effect/proc_holder/judicial_visor/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return
- if(ranged_ability_user.incapacitated() || !visor || visor != ranged_ability_user.get_item_by_slot(SLOT_GLASSES))
- remove_ranged_ability()
- return
+/datum/action/cooldown/judicial_visor/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(!..())
+ return FALSE
+ if(owner.incapacitated() || !visor || visor != owner.get_item_by_slot(SLOT_GLASSES))
+ unset_click_ability(owner)
+ return FALSE
- var/turf/T = ranged_ability_user.loc
+ var/turf/T = owner.loc
if(!isturf(T))
return FALSE
- if(target in view(7, get_turf(ranged_ability_user)))
+ if(target in view(7, get_turf(owner)))
visor.recharging = TRUE
visor.update_status()
- for(var/obj/item/clothing/glasses/judicial_visor/V in ranged_ability_user.GetAllContents())
+ for(var/obj/item/clothing/glasses/judicial_visor/V in caller.get_all_contents())
if(V == visor)
continue
V.recharging = TRUE //To prevent exploiting multiple visors to bypass the cooldown
V.update_status()
- addtimer(CALLBACK(V, TYPE_PROC_REF(/obj/item/clothing/glasses/judicial_visor, recharge_visor), ranged_ability_user), (GLOB.ratvar_awakens ? visor.recharge_cooldown*0.1 : visor.recharge_cooldown) * 2)
- clockwork_say(ranged_ability_user, text2ratvar("Kneel, heathens!"))
- ranged_ability_user.visible_message(span_warning("[ranged_ability_user]'s judicial visor fires a stream of energy at [target], creating a strange mark!"), "[span_heavy_brass("You direct [visor]'s power to [target]. You must wait for some time before doing this again.")]")
+ addtimer(CALLBACK(V, TYPE_PROC_REF(/obj/item/clothing/glasses/judicial_visor, recharge_visor), owner), (GLOB.ratvar_awakens ? visor.recharge_cooldown*0.1 : visor.recharge_cooldown) * 2)
+ clockwork_say(owner, text2ratvar("Kneel, heathens!"))
+ owner.visible_message(span_warning("[owner]'s judicial visor fires a stream of energy at [target], creating a strange mark!"), "[span_heavy_brass("You direct [visor]'s power to [target]. You must wait for some time before doing this again.")]")
var/turf/targetturf = get_turf(target)
- new/obj/effect/clockwork/judicial_marker(targetturf, ranged_ability_user)
- log_combat(ranged_ability_user, targetturf, "created a judicial marker")
- ranged_ability_user.update_action_buttons_icon()
- ranged_ability_user.update_inv_glasses()
- addtimer(CALLBACK(visor, TYPE_PROC_REF(/obj/item/clothing/glasses/judicial_visor, recharge_visor), ranged_ability_user), GLOB.ratvar_awakens ? visor.recharge_cooldown*0.1 : visor.recharge_cooldown)//Cooldown is reduced by 10x if Ratvar is up
- remove_ranged_ability()
+ new/obj/effect/clockwork/judicial_marker(targetturf, owner)
+ log_combat(owner, targetturf, "created a judicial marker")
+ owner.update_mob_action_buttons()
+ owner.update_inv_glasses()
+ addtimer(CALLBACK(visor, TYPE_PROC_REF(/obj/item/clothing/glasses/judicial_visor, recharge_visor), owner), GLOB.ratvar_awakens ? visor.recharge_cooldown*0.1 : visor.recharge_cooldown)//Cooldown is reduced by 10x if Ratvar is up
+ unset_click_ability(owner)
+
+ return FALSE
- return TRUE
- return FALSE
+ return TRUE
//Judicial marker: Created by the judicial visor. Immediately applies Belligerent and briefly knocks down, then after 3 seconds does large damage and briefly knocks down again
/obj/effect/clockwork/judicial_marker
@@ -204,7 +195,7 @@
L.visible_message(span_warning("[L] is struck by a judicial explosion!"), \
"[span_heavy_brass("\"Keep an eye out, filth.\"")]\n[span_userdanger("A burst of heat crushes you against the ground!")]")
L.adjust_fire_stacks(2) //sets cultist targets on fire
- L.IgniteMob()
+ L.ignite_mob()
L.adjustFireLoss(5)
targetsjudged++
if(!QDELETED(L))
diff --git a/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm b/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm
index 6e7c20b4768a..fcfd2773bfb7 100644
--- a/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm
+++ b/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm
@@ -214,11 +214,11 @@
/datum/action/innate/eminence
name = "Eminence Action"
desc = "You shouldn't see this. File a bug report!"
- icon_icon = 'icons/mob/actions/actions_clockcult.dmi'
+ button_icon = 'icons/mob/actions/actions_clockcult.dmi'
background_icon_state = "bg_clock"
buttontooltipstyle = "clockcult"
-/datum/action/innate/eminence/IsAvailable()
+/datum/action/innate/eminence/IsAvailable(feedback = FALSE)
if(!iseminence(owner))
qdel(src)
return
@@ -269,7 +269,7 @@
desc = "Initiates a mass recall, warping all servants to the Ark after a short delay. This can only be used once."
button_icon_state = "Spatial Gateway"
-/datum/action/innate/eminence/mass_recall/IsAvailable()
+/datum/action/innate/eminence/mass_recall/IsAvailable(feedback = FALSE)
. = ..()
if(.)
var/obj/structure/destructible/clockwork/massive/celestial_gateway/G = GLOB.ark_of_the_clockwork_justiciar
diff --git a/code/modules/antagonists/clockcult/clock_scripture.dm b/code/modules/antagonists/clockcult/clock_scripture.dm
index 8916fe1ed071..4d02e411a297 100644
--- a/code/modules/antagonists/clockcult/clock_scripture.dm
+++ b/code/modules/antagonists/clockcult/clock_scripture.dm
@@ -302,7 +302,7 @@ GLOBAL_LIST_INIT(scripture_states,scripture_states_init_value()) //list of clock
//Uses a ranged slab ability, returning only when the ability no longer exists(ie, when interrupted) or finishes.
/datum/clockwork_scripture/ranged_ability
var/slab_overlay
- var/ranged_type = /obj/effect/proc_holder/slab
+ var/ranged_type = /datum/action/innate/slab
var/ranged_message = "This is a huge goddamn bug, how'd you cast this?"
var/timeout_time = 0
var/allow_mobility = TRUE //if moving and swapping hands is allowed during the while
@@ -321,7 +321,7 @@ GLOBAL_LIST_INIT(scripture_states,scripture_states_init_value()) //list of clock
slab.inhand_overlay = slab_overlay
slab.slab_ability = new ranged_type(slab)
slab.slab_ability.slab = slab
- slab.slab_ability.add_ranged_ability(invoker, ranged_message)
+ slab.slab_ability.set_ranged_ability(invoker, ranged_message)
invoker.update_inv_hands()
var/end_time = world.time + timeout_time
var/successful = FALSE
@@ -339,13 +339,14 @@ GLOBAL_LIST_INIT(scripture_states,scripture_states_init_value()) //list of clock
if(slab)
if(slab.slab_ability)
successful = slab.slab_ability.successful
- if(!slab.slab_ability.finished)
- slab.slab_ability.remove_ranged_ability()
+ if(!slab.slab_ability.finished && invoker)
+ invoker.client?.mouse_override_icon = initial(invoker.client?.mouse_pointer_icon)
+ invoker.update_mouse_pointer()
+ invoker.click_intercept = null
slab.cut_overlays()
slab.item_state = initial(slab.item_state)
slab.item_state = initial(slab.lefthand_file)
slab.item_state = initial(slab.righthand_file)
slab.inhand_overlay = null
- if(invoker)
- invoker.update_inv_hands()
+ invoker?.update_inv_hands()
return successful //slab doesn't look like a word now.
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_cyborg.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_cyborg.dm
index db0080c0739d..e9681692a995 100644
--- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_cyborg.dm
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_cyborg.dm
@@ -10,7 +10,7 @@
primary_component = VANGUARD_COGWHEEL
quickbind_desc = "Allows you to grant a Servant and yourself stun immunity, as the Vanguard scripture.
Click your slab to disable."
slab_overlay = "vanguard"
- ranged_type = /obj/effect/proc_holder/slab/vanguard
+ ranged_type = /datum/action/innate/slab/vanguard
ranged_message = "You charge the clockwork slab with defensive strength.\n\
Left-click a fellow Servant or yourself to grant Vanguard!\n\
Click your slab to cancel."
@@ -39,7 +39,7 @@
primary_component = BELLIGERENT_EYE
quickbind_desc = "Allows you to smite an area, applying Belligerent and briefly stunning.
Click your slab to disable."
slab_overlay = "judicial"
- ranged_type = /obj/effect/proc_holder/slab/judicial
+ ranged_type = /datum/action/innate/slab/judicial
ranged_message = "You charge the clockwork slab with judicial force.\n\
Left-click a target to place a Judicial Marker!\n\
Click your slab to cancel."
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm
index 047bf1d5246a..0aaec602b06a 100644
--- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_drivers.dm
@@ -78,7 +78,7 @@
primary_component = BELLIGERENT_EYE
sort_priority = 4
slab_overlay = "volt"
- ranged_type = /obj/effect/proc_holder/slab/kindle
+ ranged_type = /datum/action/innate/slab/kindle
ranged_message = "You charge the clockwork slab with divine energy.\n\
Left-click a target within melee range to stun!\n\
Click your slab to cancel."
@@ -102,7 +102,7 @@
tier = SCRIPTURE_DRIVER
primary_component = BELLIGERENT_EYE
sort_priority = 5
- ranged_type = /obj/effect/proc_holder/slab/hateful_manacles
+ ranged_type = /datum/action/innate/slab/hateful_manacles
slab_overlay = "hateful_manacles"
ranged_message = "You charge the clockwork slab with divine energy.\n\
Left-click a target within melee range to shackle!\n\
@@ -161,7 +161,7 @@
quickbind = TRUE
quickbind_desc = "Allows you to convert a Servant's brute, burn, and oxygen damage to half toxin damage.
Click your slab to disable."
slab_overlay = "compromise"
- ranged_type = /obj/effect/proc_holder/slab/compromise
+ ranged_type = /datum/action/innate/slab/compromise
ranged_message = "You charge the clockwork slab with healing power.\n\
Left-click a fellow Servant or yourself to heal!\n\
Click your slab to cancel."
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm
index 99b181a11115..29a78405bcb6 100644
--- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm
@@ -146,10 +146,10 @@
/datum/action/innate/clockwork_armaments
name = "Clockwork Armaments"
desc = "Outfits you in a full set of Ratvarian armor."
- icon_icon = 'icons/mob/actions/actions_clockcult.dmi'
+ button_icon = 'icons/mob/actions/actions_clockcult.dmi'
button_icon_state = "clockwork_armor"
background_icon_state = "bg_clock"
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS
+ check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS
buttontooltipstyle = "clockcult"
var/cooldown = 0
var/static/list/ratvarian_armor_typecache = typecacheof(list(
@@ -162,7 +162,7 @@
/obj/item/clothing/head/helmet/space,
/obj/item/clothing/shoes/magboots)) //replace this only if ratvar is up
-/datum/action/innate/clockwork_armaments/IsAvailable()
+/datum/action/innate/clockwork_armaments/IsAvailable(feedback = FALSE)
if(!is_servant_of_ratvar(owner))
qdel(src)
return
@@ -188,8 +188,8 @@
owner.visible_message(span_warning("Strange armor appears on [owner]!"), "[span_heavy_brass("A bright shimmer runs down your body, equipping you with Ratvarian armor.")]")
playsound(owner, 'sound/magic/clockwork/fellowship_armory.ogg', 15 * do_message, TRUE) //get sound loudness based on how much we equipped
cooldown = CLOCKWORK_ARMOR_COOLDOWN + world.time
- owner.update_action_buttons_icon()
- addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob, update_action_buttons_icon)), CLOCKWORK_ARMOR_COOLDOWN)
+ owner.update_mob_action_buttons()
+ addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob, update_mob_action_buttons)), CLOCKWORK_ARMOR_COOLDOWN)
return TRUE
/datum/action/innate/clockwork_armaments/proc/remove_item_if_better(obj/item/I, mob/user)
diff --git a/code/modules/antagonists/clockcult/clock_structures/ocular_warden.dm b/code/modules/antagonists/clockcult/clock_structures/ocular_warden.dm
index 42d19ae3876b..d654f58020cc 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ocular_warden.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ocular_warden.dm
@@ -79,7 +79,7 @@
last_process = world.time
if(GLOB.ratvar_awakens && L)
L.adjust_fire_stacks(damage_per_tick)
- L.IgniteMob()
+ L.ignite_mob()
else if(ismecha(target))
var/obj/mecha/M = target
Beam(M, icon_state = "warden_beam", time = 10) //yogs: gives a beam
@@ -115,7 +115,7 @@
if(B)
if(!(B.resistance_flags & ON_FIRE))
to_chat(L, span_warning("Your [B.name] bursts into flames!"))
- for(var/obj/item/storage/book/bible/BI in L.GetAllContents())
+ for(var/obj/item/storage/book/bible/BI in L.get_all_contents())
if(!(BI.resistance_flags & ON_FIRE))
BI.fire_act()
continue
diff --git a/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm b/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm
index 8be3334257f7..62de05b8dbeb 100644
--- a/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm
@@ -55,8 +55,7 @@
/obj/structure/destructible/clockwork/taunting_trail/proc/affect_mob(mob/living/L)
if(istype(L) && !is_servant_of_ratvar(L))
if(!L.anti_magic_check(chargecost = 0))
- L.confused = min(L.confused + 15, 50)
- L.dizziness = min(L.dizziness + 15, 50)
- if(L.confused >= 25)
- L.Paralyze(FLOOR(L.confused * 0.8, 1))
+ L.adjust_confusion_up_to(15 SECONDS, 50 SECONDS)
+ L.adjust_dizzy_up_to(15 SECONDS, 50 SECONDS)
+ L.Paralyze(FLOOR(L.get_timed_status_effect_duration(/datum/status_effect/confusion) * 0.8, 1))
take_damage(max_integrity)
diff --git a/code/modules/antagonists/clockcult/clockcult.dm b/code/modules/antagonists/clockcult/clockcult.dm
index 6f6c27cb0136..9f6645683cc7 100644
--- a/code/modules/antagonists/clockcult/clockcult.dm
+++ b/code/modules/antagonists/clockcult/clockcult.dm
@@ -4,6 +4,7 @@
roundend_category = "clock cultists"
antagpanel_category = "Clockcult"
job_rank = ROLE_SERVANT_OF_RATVAR
+ antag_hud_name = "clockwork"
show_to_ghosts = TRUE
antag_moodlet = /datum/mood_event/cult
var/datum/action/innate/hierophant/hierophant_network = new()
@@ -62,7 +63,6 @@
/datum/antagonist/clockcult/on_gain()
var/mob/living/current = owner.current
SSticker.mode.servants_of_ratvar += owner
- SSticker.mode.update_servant_icons_added(owner)
owner.special_role = ROLE_SERVANT_OF_RATVAR
owner.current.log_message("has been converted to the cult of Ratvar!", LOG_ATTACK, color="#BE8700")
if(issilicon(current))
@@ -90,7 +90,7 @@
GLOB.all_clockwork_mobs += current
current.faction |= "ratvar"
current.grant_language(/datum/language/ratvar)
- current.update_action_buttons_icon() //because a few clockcult things are action buttons and we may be wearing/holding them for whatever reason, we need to update buttons
+ current.update_mob_action_buttons() //because a few clockcult things are action buttons and we may be wearing/holding them for whatever reason, we need to update buttons
if(issilicon(current))
var/mob/living/silicon/S = current
if(iscyborg(S))
@@ -132,8 +132,10 @@
current.throw_alert("clockinfo", /atom/movable/screen/alert/clockwork/infodump)
if(clockwork_ark_active() && ishuman(current))
current.add_overlay(mutable_appearance('icons/effects/genetics.dmi', "servitude", -MUTATIONS_LAYER))
+ add_team_hud(current)
/datum/antagonist/clockcult/remove_innate_effects(mob/living/mob_override)
+ . = ..()
var/mob/living/current = owner.current
if(istype(mob_override))
current = mob_override
@@ -156,18 +158,16 @@
S.update_icons()
S.show_laws()
var/mob/living/temp_owner = current
- ..()
if(iscyborg(temp_owner))
var/mob/living/silicon/robot/R = temp_owner
R.module.rebuild_modules()
if(temp_owner)
- temp_owner.update_action_buttons_icon() //because a few clockcult things are action buttons and we may be wearing/holding them, we need to update buttons
+ temp_owner.update_mob_action_buttons() //because a few clockcult things are action buttons and we may be wearing/holding them, we need to update buttons
temp_owner.cut_overlays()
temp_owner.regenerate_icons()
/datum/antagonist/clockcult/on_removal()
SSticker.mode.servants_of_ratvar -= owner
- SSticker.mode.update_servant_icons_removed(owner)
if(!silent)
owner.current.visible_message("[span_deconversion_message("[owner.current] seems to have remembered [owner.current.p_their()] true allegiance!")]", null, null, null, owner.current)
to_chat(owner, span_userdanger("A cold, cold darkness flows through your mind, extinguishing the Justiciar's light and all of your memories as his servant."))
@@ -190,9 +190,9 @@
/datum/antagonist/clockcult/get_admin_commands()
. = ..()
- .["Give slab"] = CALLBACK(src, PROC_REF(admin_give_slab))
+ .["Equip Cultist"] = CALLBACK(src, PROC_REF(admin_equip))
-/datum/antagonist/clockcult/proc/admin_give_slab(mob/admin)
+/datum/antagonist/clockcult/proc/admin_equip(mob/admin)
if(!SSticker.mode.equip_servant(owner.current))
to_chat(admin, span_warning("Failed to outfit [owner.current]!"))
else
diff --git a/code/modules/antagonists/creep/creep.dm b/code/modules/antagonists/creep/creep.dm
index cc887dafe65c..09cd27c49b63 100644
--- a/code/modules/antagonists/creep/creep.dm
+++ b/code/modules/antagonists/creep/creep.dm
@@ -4,6 +4,7 @@
antagpanel_category = "Other"
preview_outfit = /datum/outfit/obsessed
job_rank = ROLE_OBSESSED
+ antag_hud_name = "obsessed"
show_name_in_check_antagonists = TRUE
roundend_category = "obsessed"
silent = TRUE //not actually silent, because greet will be called by the trauma anyway.
@@ -72,14 +73,13 @@
/datum/antagonist/obsessed/apply_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
- update_obsession_icons_added(M)
- if(owner.current && ishuman(owner.current) && !owner.current.GetComponent(/datum/component/mood))
+ if(M && ishuman(M) && !M.GetComponent(/datum/component/mood))
to_chat(owner, span_danger("You feel more aware of your condition, mood has been enabled!"))
- owner.current.AddComponent(/datum/component/mood) //you fool you absolute buffoon to think you could escape
+ M.AddComponent(/datum/component/mood) //you fool you absolute buffoon to think you could escape
/datum/antagonist/obsessed/remove_innate_effects(mob/living/mob_override)
+ . = ..()
var/mob/living/M = mob_override || owner.current
- update_obsession_icons_removed(M)
var/mob/living/carbon/human/H = M
if(H && !H.mood_enabled)
var/datum/component/C = M.GetComponent(/datum/component/mood)
@@ -310,7 +310,7 @@
for(var/datum/mind/M in owners)
if(!isliving(M.current))
continue
- var/list/all_items = M.current.GetAllContents() //this should get things in cheesewheels, books, etc.
+ var/list/all_items = M.current.get_all_contents() //this should get things in cheesewheels, books, etc.
for(var/obj/I in all_items) //Check for wanted items
if(istype(I, /obj/item/photo))
var/obj/item/photo/P = I
@@ -328,13 +328,3 @@
explanation_text = "Steal [target.name]'s family heirloom, [steal_target] they cherish."
else
explanation_text = "Free Objective"
-
-/datum/antagonist/obsessed/proc/update_obsession_icons_added(var/mob/living/carbon/human/obsessed)
- var/datum/atom_hud/antag/creephud = GLOB.huds[ANTAG_HUD_OBSESSED]
- creephud.join_hud(obsessed)
- set_antag_hud(obsessed, "obsessed")
-
-/datum/antagonist/obsessed/proc/update_obsession_icons_removed(var/mob/living/carbon/human/obsessed)
- var/datum/atom_hud/antag/creephud = GLOB.huds[ANTAG_HUD_OBSESSED]
- creephud.leave_hud(obsessed)
- set_antag_hud(obsessed, null)
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index 45e6d0e65465..16b5f36a91f0 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -2,35 +2,36 @@
name = "Prepare Blood Magic"
button_icon_state = "carve"
desc = "Prepare blood magic by carving runes into your flesh. This is easier with an empowering rune."
+ default_button_position = DEFAULT_BLOODSPELLS
var/list/spells = list()
var/channeling = FALSE
-/datum/action/innate/cult/blood_magic/Grant()
- ..()
- button.screen_loc = DEFAULT_BLOODSPELLS
- button.moved = DEFAULT_BLOODSPELLS
- button.ordered = FALSE
-
/datum/action/innate/cult/blood_magic/Remove()
for(var/X in spells)
qdel(X)
..()
-/datum/action/innate/cult/blood_magic/IsAvailable()
+/datum/action/innate/cult/blood_magic/IsAvailable(feedback = FALSE)
if(!iscultist(owner))
return FALSE
return ..()
/datum/action/innate/cult/blood_magic/proc/Positioning()
- var/list/screen_loc_split = splittext(button.screen_loc,",")
- var/list/screen_loc_X = splittext(screen_loc_split[1],":")
- var/list/screen_loc_Y = splittext(screen_loc_split[2],":")
- var/pix_X = text2num(screen_loc_X[2])
- for(var/datum/action/innate/cult/blood_spell/B in spells)
- if(B.button.locked)
- var/order = pix_X+spells.Find(B)*31
- B.button.screen_loc = "[screen_loc_X[1]]:[order],[screen_loc_Y[1]]:[screen_loc_Y[2]]"
- B.button.moved = B.button.screen_loc
+ for(var/datum/hud/hud as anything in viewers)
+ var/our_view = hud.mymob?.client?.view || "15x15"
+ var/atom/movable/screen/movable/action_button/button = viewers[hud]
+ var/position = screen_loc_to_offset(button.screen_loc)
+ var/spells_iterated = 0
+ for(var/datum/action/innate/cult/blood_spell/blood_spell in spells)
+ spells_iterated += 1
+ if(blood_spell.positioned)
+ continue
+ var/atom/movable/screen/movable/action_button/moving_button = blood_spell.viewers[hud]
+ if(!moving_button)
+ continue
+ var/our_x = position[1] + spells_iterated * world.icon_size // Offset any new buttons into our list
+ hud.position_action(moving_button, offset_to_screen_loc(our_x, position[2], our_view))
+ blood_spell.positioned = TRUE
/datum/action/innate/cult/blood_magic/Activate()
var/rune = FALSE
@@ -40,16 +41,15 @@
break
if(rune)
limit = MAX_BLOODCHARGE
- listclearnulls(spells)
- if(spells.len >= limit)
+ if(length(spells) >= limit)
if(rune)
to_chat(owner, span_cultitalic("You cannot store more than [MAX_BLOODCHARGE] spells. Pick a spell to remove."))
else
to_chat(owner, span_cultitalic("You cannot store more than [RUNELESS_MAX_BLOODCHARGE] spells without an empowering rune! Pick a spell to remove."))
- var/nullify_spell = input(owner, "Choose a spell to remove.", "Current Spells") as null|anything in spells
- if(nullify_spell)
- qdel(nullify_spell)
- return
+ var/nullify_spell = tgui_input_list(owner, "Spell to remove", "Current Spells", spells)
+ if(isnull(nullify_spell))
+ return
+ qdel(nullify_spell)
var/entered_spell_name
var/datum/action/innate/cult/blood_spell/BS
var/list/possible_spells = list()
@@ -58,14 +58,16 @@
var/cult_name = initial(J.name)
possible_spells[cult_name] = J
possible_spells += "(REMOVE SPELL)"
- entered_spell_name = input(owner, "Pick a blood spell to prepare...", "Spell Choices") as null|anything in possible_spells
- if(entered_spell_name == "(REMOVE SPELL)")
- var/nullify_spell = input(owner, "Choose a spell to remove.", "Current Spells") as null|anything in spells
- if(nullify_spell)
- qdel(nullify_spell)
+ entered_spell_name = tgui_input_list(owner, "Blood spell to prepare", "Spell Choices", possible_spells)
+ if(isnull(entered_spell_name))
return
+ if(entered_spell_name == "(REMOVE SPELL)")
+ var/nullify_spell = tgui_input_list(owner, "Spell to remove", "Current Spells", spells)
+ if(isnull(nullify_spell))
+ return
+ qdel(nullify_spell)
BS = possible_spells[entered_spell_name]
- if(QDELETED(src) || owner.incapacitated() || !BS || (rune && !(locate(/obj/effect/rune/empower) in range(1, owner))) || (spells.len >= limit))
+ if(QDELETED(src) || owner.incapacitated() || !BS || (rune && !(locate(/obj/effect/rune/empower) in range(1, owner))) || (length(spells) >= limit))
return
to_chat(owner,span_warning("You begin to carve unnatural symbols into your flesh!"))
SEND_SOUND(owner, sound('sound/weapons/slice.ogg',0,1,10))
@@ -74,7 +76,7 @@
else
to_chat(owner, span_cultitalic("You are already invoking blood magic!"))
return
- if(do_after(owner, (10 - rune*6) SECONDS, owner))
+ if(do_after(owner, 100 - rune*60, target = owner))
if(ishuman(owner))
var/mob/living/carbon/human/H = owner
H.bleed(40 - rune*32)
@@ -96,6 +98,8 @@
var/base_desc //To allow for updating tooltips
var/invocation
var/health_cost = 0
+ /// Have we already been positioned into our starting location?
+ var/positioned = FALSE
/datum/action/innate/cult/blood_spell/Grant(mob/living/owner, datum/action/innate/cult/blood_magic/BM)
if(health_cost)
@@ -103,9 +107,7 @@
base_desc = desc
desc += "
Has [charges] use\s remaining."
all_magic = BM
- ..()
- button.locked = TRUE
- button.ordered = FALSE
+ return ..()
/datum/action/innate/cult/blood_spell/Remove()
if(all_magic)
@@ -115,7 +117,7 @@
hand_magic = null
..()
-/datum/action/innate/cult/blood_spell/IsAvailable()
+/datum/action/innate/cult/blood_spell/IsAvailable(feedback = FALSE)
if(!iscultist(owner) || owner.incapacitated() || !charges)
return FALSE
return ..()
@@ -157,70 +159,34 @@
desc = "Emits a large electromagnetic pulse."
button_icon_state = "emp"
health_cost = 10
- var/obj/effect/proc_holder/bloodemp/PH
invocation = "Ta'gh fara'qha fel d'amar det!"
+ click_action = TRUE
+ enable_text = span_cult("You prepare to emp a target...")
+ disable_text = span_cult("You dispel the magic...")
-/datum/action/innate/cult/blood_spell/emp/New()
- PH = new()
- PH.attached_action = src
- ..()
-
-/datum/action/innate/cult/blood_spell/emp/Destroy()
- var/obj/effect/proc_holder/bloodemp/destroy = PH
- . = ..()
- if(destroy && !QDELETED(destroy))
- QDEL_NULL(destroy)
-
-/datum/action/innate/cult/blood_spell/emp/Activate()
- PH.toggle(owner) //the important bit
- return TRUE
-
-/obj/effect/proc_holder/bloodemp
- active = FALSE
- ranged_mousepointer = 'icons/effects/cult_target.dmi'
- var/datum/action/innate/cult/blood_spell/attached_action
-
-/obj/effect/proc_holder/bloodemp/Destroy()
- var/datum/action/innate/cult/blood_spell/AA = attached_action
- . = ..()
- if(AA && !QDELETED(AA))
- QDEL_NULL(AA)
-
-/obj/effect/proc_holder/bloodemp/proc/toggle(mob/user)
- if(active)
- remove_ranged_ability(span_cult("You dispel the magic..."))
- else
- add_ranged_ability(user, span_cult("You prepare to emp a target..."))
-
-/obj/effect/proc_holder/bloodemp/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return
- if(ranged_ability_user.incapacitated() || !iscultist(caller))
- remove_ranged_ability()
- return
- var/turf/T = get_turf(ranged_ability_user)
+/datum/action/innate/cult/blood_spell/emp/do_ability(mob/living/caller, params, atom/target)
+ var/turf/T = get_turf(caller)
if(!isturf(T))
return FALSE
- var/mob/living/carbon/user = ranged_ability_user
+ var/mob/living/carbon/user = caller
user.visible_message(span_warning("[user]'s hand flashes a bright blue!"), \
span_cultitalic("You speak the cursed words, emitting an EMP blast from your hand."))
- user.whisper(attached_action.invocation, language = /datum/language/common)
var/obj/item/projectile/magic/ion/A = new /obj/item/projectile/magic/ion(user.loc)
A.firer = user
A.preparePixelProjectile(target, user, params)
A.fire()
- if(attached_action.health_cost)
+ if(health_cost)
if(user.active_hand_index == 1)
- user.apply_damage(attached_action.health_cost, BRUTE, BODY_ZONE_L_ARM)
+ user.apply_damage(health_cost, BRUTE, BODY_ZONE_L_ARM)
else
- user.apply_damage(attached_action.health_cost, BRUTE, BODY_ZONE_R_ARM)
+ user.apply_damage(health_cost, BRUTE, BODY_ZONE_R_ARM)
- attached_action.charges--
- if(attached_action.charges <= 0)
- remove_ranged_ability(span_cult("You have exhausted the spell's power!"))
+ charges--
+ if(!charges)
+ unset_ranged_ability(caller, span_cult("You have exhausted the spell's power!"))
qdel(src)
return TRUE
@@ -270,67 +236,43 @@
name = "Hallucinations"
desc = "Gives hallucinations to a target at range. A silent and invisible spell."
button_icon_state = "horror"
- var/obj/effect/proc_holder/horror/PH
charges = 4
+ click_action = TRUE
+ enable_text = span_cult("You prepare to horrify a target...")
+ disable_text = span_cult("You dispel the magic...")
-/datum/action/innate/cult/blood_spell/horror/New()
- PH = new()
- PH.attached_action = src
- ..()
+/datum/action/innate/cult/blood_spell/horror/InterceptClickOn(mob/living/caller, params, atom/clicked_on)
+ var/turf/caller_turf = get_turf(caller)
+ if(!isturf(caller_turf))
+ return FALSE
-/datum/action/innate/cult/blood_spell/horror/Destroy()
- var/obj/effect/proc_holder/horror/destroy = PH
- . = ..()
- if(destroy && !QDELETED(destroy))
- QDEL_NULL(destroy)
+ if(!ishuman(clicked_on) || get_dist(caller, clicked_on) > 7)
+ return FALSE
-/datum/action/innate/cult/blood_spell/horror/Activate()
- PH.toggle(owner) //the important bit
- return TRUE
+ var/mob/living/carbon/human/human_clicked = clicked_on
+ if(iscultist(human_clicked))
+ return FALSE
-/obj/effect/proc_holder/horror
- active = FALSE
- ranged_mousepointer = 'icons/effects/cult_target.dmi'
- var/datum/action/innate/cult/blood_spell/attached_action
+ return ..()
-/obj/effect/proc_holder/horror/Destroy()
- var/datum/action/innate/cult/blood_spell/AA = attached_action
- . = ..()
- if(AA && !QDELETED(AA))
- QDEL_NULL(AA)
+/datum/action/innate/cult/blood_spell/horror/do_ability(mob/living/caller, params, mob/living/carbon/human/clicked_on)
+ clicked_on.set_hallucinations_if_lower(2 MINUTES)
+ SEND_SOUND(caller, sound('sound/effects/ghost.ogg', FALSE, TRUE, 50))
+ var/image/sparkle_image = image('icons/effects/cult_effects.dmi', clicked_on, "bloodsparkles", ABOVE_MOB_LAYER)
+ clicked_on.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/cult, "cult_apoc", sparkle_image, NONE)
-/obj/effect/proc_holder/horror/proc/toggle(mob/user)
- if(active)
- remove_ranged_ability(span_cult("You dispel the magic..."))
- else
- add_ranged_ability(user, span_cult("You prepare to horrify a target..."))
+ addtimer(CALLBACK(clicked_on, TYPE_PROC_REF(/atom/, remove_alt_appearance), "cult_apoc", TRUE), 4 MINUTES, TIMER_OVERRIDE|TIMER_UNIQUE)
+ to_chat(caller, span_cultbold("[clicked_on] has been cursed with living nightmares!"))
-/obj/effect/proc_holder/horror/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return
- if(ranged_ability_user.incapacitated() || !iscultist(caller))
- remove_ranged_ability()
- return
- var/turf/T = get_turf(ranged_ability_user)
- if(!isturf(T))
- return FALSE
- if(target in view(7, get_turf(ranged_ability_user)))
- if(!ishuman(target) || iscultist(target))
- return
- var/mob/living/carbon/human/H = target
- H.hallucination = max(H.hallucination, 120)
- SEND_SOUND(ranged_ability_user, sound('sound/effects/ghost.ogg',0,1,50))
- var/image/C = image('icons/effects/cult_effects.dmi',H,"bloodsparkles", ABOVE_MOB_LAYER)
- add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/cult, "cult_apoc", C, NONE)
- addtimer(CALLBACK(H, TYPE_PROC_REF(/atom, remove_alt_appearance),"cult_apoc",TRUE), 2400, TIMER_OVERRIDE|TIMER_UNIQUE)
- to_chat(ranged_ability_user,span_cult("[H] has been cursed with living nightmares!"))
- attached_action.charges--
- attached_action.desc = attached_action.base_desc
- attached_action.desc += "
Has [attached_action.charges] use\s remaining."
- attached_action.UpdateButtonIcon()
- if(attached_action.charges <= 0)
- remove_ranged_ability(span_cult("You have exhausted the spell's power!"))
- qdel(src)
+ charges--
+ desc = base_desc
+ desc += "
Has [charges] use\s remaining."
+ build_all_button_icons()
+ if(charges <= 0)
+ to_chat(caller, span_cult("You have exhausted the spell's power!"))
+ qdel(src)
+
+ return TRUE
/datum/action/innate/cult/blood_spell/veiling
name = "Conceal Presence"
@@ -379,7 +321,7 @@
qdel(src)
desc = base_desc
desc += "
Has [charges] use\s remaining."
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/cult/blood_spell/manipulation
name = "Blood Rites"
@@ -431,7 +373,7 @@
source.charges = uses
source.desc = source.base_desc
source.desc += "
Has [uses] use\s remaining."
- source.UpdateButtonIcon()
+ source.build_all_button_icons()
..()
/obj/item/melee/blood_magic/attack_self(mob/living/user)
@@ -460,7 +402,7 @@
else if(source)
source.desc = source.base_desc
source.desc += "
Has [uses] use\s remaining."
- source.UpdateButtonIcon()
+ source.build_all_button_icons()
//Stun
/obj/item/melee/blood_magic/stun
@@ -510,7 +452,7 @@
else if(iscarbon(target))
var/mob/living/carbon/C = L
C.cultslurring += 10
- C.Jitter(15)
+ C.adjust_jitter(15 SECONDS)
if(is_servant_of_ratvar(L))
L.adjustBruteLoss(30)
else if(cult.cult_risen)
@@ -523,9 +465,9 @@
else if(iscarbon(target))
var/mob/living/carbon/C = L
C.silent += 3
- C.stuttering += 7
+ C.adjust_stutter(7 SECONDS)
C.cultslurring += 7
- C.Jitter(7)
+ C.adjust_jitter(7 SECONDS)
if(is_servant_of_ratvar(L))
L.adjustBruteLoss(20)
else
@@ -538,9 +480,9 @@
else if(iscarbon(target))
var/mob/living/carbon/C = L
C.silent += 6
- C.stuttering += 15
+ C.adjust_stutter(15 SECONDS)
C.cultslurring += 15
- C.Jitter(15)
+ C.adjust_jitter(15 SECONDS)
if(is_servant_of_ratvar(L))
L.adjustBruteLoss(15)
else
@@ -619,7 +561,7 @@
span_userdanger("[user] begins shaping dark magic shackles around your wrists!"))
if(do_mob(user, C, 30))
if(!C.handcuffed)
- C.handcuffed = new /obj/item/restraints/handcuffs/energy/cult/used(C)
+ C.set_handcuffed(new /obj/item/restraints/handcuffs/energy/cult/used(C))
C.update_handcuffed()
C.silent += 5
to_chat(user, span_notice("You shackle [C]."))
diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm
index 39d6404bd90c..0f5f7bd3d991 100644
--- a/code/modules/antagonists/cult/cult.dm
+++ b/code/modules/antagonists/cult/cult.dm
@@ -13,6 +13,7 @@
var/datum/action/innate/cult/blood_magic/magic = new
preview_outfit = /datum/outfit/cultist
job_rank = ROLE_CULTIST
+ antag_hud_name = "cult"
var/ignore_implant = FALSE
var/give_equipment = FALSE
var/datum/team/cult/cult_team
@@ -69,16 +70,15 @@
owner.announce_objectives()
/datum/antagonist/cult/on_gain()
+ add_objectives()
. = ..()
var/mob/living/current = owner.current
if(ishuman(current))
var/mob/living/carbon/human/H = current
original_eye_color = H.eye_color
- add_objectives()
if(give_equipment)
equip_cultist(TRUE)
SSticker.mode.cult += owner // Only add after they've been given objectives
- SSticker.mode.update_cult_icons_added(owner)
current.log_message("has been converted to the cult of Nar'sie!", LOG_ATTACK, color="#960000")
if(cult_team.blood_target && cult_team.blood_target_image && current.client)
@@ -136,35 +136,30 @@
r_hand = /obj/item/melee/cultblade
/datum/antagonist/cult/proc/equip_cultist(metal=TRUE)
- var/mob/living/carbon/H = owner.current
- if(!istype(H))
- return
- if (owner.assigned_role == "Clown")
- to_chat(owner, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
- H.dna.remove_mutation(CLOWNMUT)
- . += cult_give_item(/obj/item/melee/cultblade/dagger, H)
+ handle_clown_mutation(owner.current, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
+ . += cult_give_item(/obj/item/melee/cultblade/dagger, owner.current)
if(metal)
- . += cult_give_item(/obj/item/stack/sheet/runed_metal/ten, H)
+ . += cult_give_item(/obj/item/stack/sheet/runed_metal/ten, owner.current)
to_chat(owner, "These will help you start the cult on this station. Use them well, and remember - you are not the only one.")
-/datum/antagonist/cult/proc/cult_give_item(obj/item/item_path, mob/living/carbon/human/mob)
+/datum/antagonist/cult/proc/cult_give_item(obj/item/item_path, mob/living/carbon/human/current_mob)
var/list/slots = list(
"backpack" = SLOT_IN_BACKPACK,
"left pocket" = SLOT_L_STORE,
"right pocket" = SLOT_R_STORE
)
- var/T = new item_path(mob)
+ var/T = new item_path(current_mob)
var/item_name = initial(item_path.name)
- var/where = mob.equip_in_one_of_slots(T, slots)
+ var/where = current_mob.equip_in_one_of_slots(T, slots)
if(!where)
- to_chat(mob, span_userdanger("Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1)."))
+ to_chat(current_mob, span_userdanger("Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1)."))
return 0
else
- to_chat(mob, span_danger("You have a [item_name] in your [where]."))
+ to_chat(current_mob, span_danger("You have a [item_name] in your [where]."))
if(where == "backpack")
- SEND_SIGNAL(mob.back, COMSIG_TRY_STORAGE_SHOW, mob)
+ SEND_SIGNAL(current_mob.back, COMSIG_TRY_STORAGE_SHOW, current_mob)
return TRUE
/datum/antagonist/cult/apply_innate_effects(mob/living/mob_override)
@@ -185,6 +180,8 @@
if(cult_team.cult_ascendent)
cult_team.ascend(current)
+ add_team_hud(current)
+
/datum/antagonist/cult/remove_innate_effects(mob/living/mob_override)
. = ..()
var/mob/living/current = owner.current
@@ -206,7 +203,6 @@
/datum/antagonist/cult/on_removal()
SSticker.mode.cult -= owner
- SSticker.mode.update_cult_icons_removed(owner)
if(!silent)
owner.current.visible_message("[span_deconversion_message("[owner.current] looks like [owner.current.p_theyve()] just reverted to [owner.current.p_their()] old faith!")]", null, null, null, owner.current)
to_chat(owner.current, span_userdanger("An unfamiliar white light flashes through your mind, cleansing the taint of the Geometer and all your memories as her servant."))
@@ -242,13 +238,14 @@
/datum/antagonist/cult/proc/admin_take_all(mob/admin)
var/mob/living/current = owner.current
- for(var/o in current.GetAllContents())
+ for(var/o in current.get_all_contents())
if(istype(o, /obj/item/melee/cultblade/dagger) || istype(o, /obj/item/stack/sheet/runed_metal))
qdel(o)
/datum/antagonist/cult/master
ignore_implant = TRUE
show_in_antagpanel = FALSE //Feel free to add this later
+ antag_hud_name = "cultmaster"
var/datum/action/innate/cult/master/finalreck/reckoning = new
var/datum/action/innate/cult/master/cultmark/bloodmark = new
var/datum/action/innate/cult/master/pulse/throwing = new
@@ -259,11 +256,6 @@
QDEL_NULL(throwing)
return ..()
-/datum/antagonist/cult/master/on_gain()
- . = ..()
- var/mob/living/current = owner.current
- set_antag_hud(current, "cultmaster")
-
/datum/antagonist/cult/master/greet()
to_chat(owner.current, "[span_cultlarge("You are the cult's Master")]. As the cult's Master, you have a unique title and loud voice when communicating, are capable of marking \
targets, such as a location or a noncultist, to direct the cult to them, and, finally, you are capable of summoning the entire living cult to your location once.")
@@ -278,12 +270,13 @@
reckoning.Grant(current)
bloodmark.Grant(current)
throwing.Grant(current)
- current.update_action_buttons_icon()
+ current.update_mob_action_buttons()
current.apply_status_effect(/datum/status_effect/cult_master)
if(cult_team.cult_risen)
cult_team.rise(current)
if(cult_team.cult_ascendent)
cult_team.ascend(current)
+ add_team_hud(current, /datum/antagonist/cult)
/datum/antagonist/cult/master/remove_innate_effects(mob/living/mob_override)
. = ..()
@@ -293,7 +286,7 @@
reckoning.Remove(current)
bloodmark.Remove(current)
throwing.Remove(current)
- current.update_action_buttons_icon()
+ current.update_mob_action_buttons()
current.remove_status_effect(/datum/status_effect/cult_master)
if(ishuman(current))
@@ -307,8 +300,11 @@
/datum/team/cult
name = "Cult"
- var/blood_target
+ ///The blood mark target
+ var/atom/blood_target
+ ///Image of the blood mark target
var/image/blood_target_image
+ ///Timer for the blood mark expiration
var/blood_target_reset_timer
var/cult_vote_called = FALSE
@@ -338,7 +334,7 @@
if(B.current)
SEND_SOUND(B.current, 'sound/hallucinations/i_see_you2.ogg')
to_chat(B.current, span_cultlarge("The veil weakens as your cult grows, your eyes begin to glow..."))
- addtimer(CALLBACK(src, PROC_REF(rise), B.current), 200)
+ addtimer(CALLBACK(src, PROC_REF(rise), B.current), 20 SECONDS)
cult_risen = TRUE
if(ratio > CULT_ASCENDENT && !cult_ascendent)
@@ -346,7 +342,7 @@
if(B.current)
SEND_SOUND(B.current, 'sound/hallucinations/im_here1.ogg')
to_chat(B.current, "Your cult is ascendent and the red harvest approaches - you cannot hide your true nature for much longer!!")
- addtimer(CALLBACK(src, PROC_REF(ascend), B.current), 200)
+ addtimer(CALLBACK(src, PROC_REF(ascend), B.current), 20 SECONDS)
cult_ascendent = TRUE
@@ -574,6 +570,64 @@
/datum/team/cult/is_gamemode_hero()
return SSticker.mode.name == "cult"
+/// Sets a blood target for the cult.
+/datum/team/cult/proc/set_blood_target(atom/new_target, mob/marker, duration = 90 SECONDS)
+ if(QDELETED(new_target))
+ CRASH("A null or invalid target was passed to set_blood_target.")
+
+ if(blood_target_reset_timer)
+ return FALSE
+
+ blood_target = new_target
+ RegisterSignal(blood_target, COMSIG_PARENT_QDELETING, PROC_REF(unset_blood_target_and_timer))
+ var/area/target_area = get_area(new_target)
+
+ blood_target_image = image('icons/effects/mouse_pointers/cult_target.dmi', new_target, "glow", ABOVE_MOB_LAYER)
+ blood_target_image.appearance_flags = RESET_COLOR
+ blood_target_image.pixel_x = -new_target.pixel_x
+ blood_target_image.pixel_y = -new_target.pixel_y
+
+ for(var/datum/mind/cultist as anything in members)
+ if(!cultist.current)
+ continue
+ if(cultist.current.stat == DEAD || !cultist.current.client)
+ continue
+
+ to_chat(cultist.current, span_bold(span_cultlarge("[marker] has marked [blood_target] in the [target_area.name] as the cult's top priority, get there immediately!")))
+ SEND_SOUND(cultist.current, sound(pick('sound/hallucinations/over_here2.ogg','sound/hallucinations/over_here3.ogg'), 0, 1, 75))
+ cultist.current.client.images += blood_target_image
+
+ blood_target_reset_timer = addtimer(CALLBACK(src, PROC_REF(unset_blood_target)), duration, TIMER_STOPPABLE)
+ return TRUE
+
+/// Unsets out blood target, clearing the images from all the cultists.
+/datum/team/cult/proc/unset_blood_target()
+ blood_target_reset_timer = null
+
+ for(var/datum/mind/cultist as anything in members)
+ if(!cultist.current)
+ continue
+ if(cultist.current.stat == DEAD || !cultist.current.client)
+ continue
+
+ if(QDELETED(blood_target))
+ to_chat(cultist.current, span_bold(span_cultlarge("The blood mark's target is lost!")))
+ else
+ to_chat(cultist.current, span_bold(span_cultlarge("The blood mark has expired!")))
+ cultist.current.client.images -= blood_target_image
+
+ UnregisterSignal(blood_target, COMSIG_PARENT_QDELETING)
+ blood_target = null
+
+ QDEL_NULL(blood_target_image)
+
+/// Unsets our blood target when they get deleted.
+/datum/team/cult/proc/unset_blood_target_and_timer(datum/source)
+ SIGNAL_HANDLER
+
+ deltimer(blood_target_reset_timer)
+ unset_blood_target()
+
/datum/outfit/cultist
name = "Cultist (Preview only)"
diff --git a/code/modules/antagonists/cult/cult_comms.dm b/code/modules/antagonists/cult/cult_comms.dm
index bc9938777155..90723ebcd760 100644
--- a/code/modules/antagonists/cult/cult_comms.dm
+++ b/code/modules/antagonists/cult/cult_comms.dm
@@ -1,12 +1,15 @@
// Contains cult communion, guide, and cult master abilities
/datum/action/innate/cult
- icon_icon = 'icons/mob/actions/actions_cult.dmi'
+ button_icon = 'icons/mob/actions/actions_cult.dmi'
background_icon_state = "bg_demon"
+ overlay_icon_state = "bg_demon_border"
+
buttontooltipstyle = "cult"
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS
+ check_flags = AB_CHECK_INCAPACITATED|AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS
+ ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi'
-/datum/action/innate/cult/IsAvailable()
+/datum/action/innate/cult/IsAvailable(feedback = FALSE)
if(!iscultist(owner))
return FALSE
return ..()
@@ -18,7 +21,7 @@
/datum/action/innate/cult/comm/Activate()
var/input = stripped_input(usr, "Please choose a message to tell to the other acolytes.", "Voice of Blood", "")
- if(!input || !IsAvailable())
+ if(!input || !IsAvailable(feedback = FALSE))
return
cultist_commune(usr, input)
@@ -51,7 +54,7 @@
name = "Spiritual Communion"
desc = "Conveys a message from the spirit realm that all cultists can hear."
-/datum/action/innate/cult/comm/spirit/IsAvailable()
+/datum/action/innate/cult/comm/spirit/IsAvailable(feedback = FALSE)
if(iscultist(owner.mind.current))
return TRUE
@@ -72,7 +75,7 @@
name = "Assert Leadership"
button_icon_state = "cultvote"
-/datum/action/innate/cult/mastervote/IsAvailable()
+/datum/action/innate/cult/mastervote/IsAvailable(feedback = FALSE)
var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
if(!C || C.cult_team.cult_vote_called || !ishuman(owner))
return FALSE
@@ -80,7 +83,7 @@
/datum/action/innate/cult/mastervote/Activate()
var/choice = tgui_alert(owner, "The mantle of leadership is heavy. Success in this role requires an expert level of communication and experience. Are you sure?",, list("Yes", "No"))
- if(choice == "Yes" && IsAvailable())
+ if(choice == "Yes" && IsAvailable(feedback = FALSE))
var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
pollCultists(owner,C.cult_team)
@@ -91,7 +94,7 @@
team.cult_vote_called = TRUE //somebody's trying to be a master, make sure we don't let anyone else try
for(var/datum/mind/B in team.members)
if(B.current)
- B.current.update_action_buttons_icon()
+ B.current.update_mob_action_buttons()
if(!B.current.incapacitated())
SEND_SOUND(B.current, 'sound/hallucinations/im_here1.ogg')
to_chat(B.current, span_cultlarge("Acolyte [Nominee] has asserted that [Nominee.p_theyre()] worthy of leading the cult. A vote will be called shortly."))
@@ -106,7 +109,7 @@
team.cult_vote_called = FALSE
for(var/datum/mind/B in team.members)
if(B.current)
- B.current.update_action_buttons_icon()
+ B.current.update_mob_action_buttons()
if(!B.current.incapacitated())
to_chat(B.current,span_cultlarge("[Nominee] has died in the process of attempting to win the cult's support!"))
return FALSE
@@ -114,7 +117,7 @@
team.cult_vote_called = FALSE
for(var/datum/mind/B in team.members)
if(B.current)
- B.current.update_action_buttons_icon()
+ B.current.update_mob_action_buttons()
if(!B.current.incapacitated())
to_chat(B.current,span_cultlarge("[Nominee] has gone catatonic in the process of attempting to win the cult's support!"))
return FALSE
@@ -122,7 +125,7 @@
team.cult_vote_called = FALSE
for(var/datum/mind/B in team.members)
if(B.current)
- B.current.update_action_buttons_icon()
+ B.current.update_mob_action_buttons()
if(!B.current.incapacitated())
to_chat(B.current, span_cultlarge("[Nominee] could not win the cult's support and shall continue to serve as an acolyte."))
return FALSE
@@ -141,7 +144,7 @@
to_chat(B.current,span_cultlarge("[Nominee] has won the cult's support and is now their master. Follow [Nominee.p_their()] orders to the best of your ability!"))
return TRUE
-/datum/action/innate/cult/master/IsAvailable()
+/datum/action/innate/cult/master/IsAvailable(feedback = FALSE)
if(!owner.mind || !owner.mind.has_antag_datum(/datum/antagonist/cult/master) || GLOB.cult_narsie)
return 0
return ..()
@@ -215,163 +218,122 @@
name = "Mark Target"
desc = "Marks a target for the cult."
button_icon_state = "cult_mark"
- var/obj/effect/proc_holder/cultmark/CM
- var/cooldown = 0
- var/base_cooldown = 1200
-
-/datum/action/innate/cult/master/cultmark/New(Target)
- CM = new()
- CM.attached_action = src
- ..()
-
-/datum/action/innate/cult/master/cultmark/IsAvailable()
- if(cooldown > world.time)
- if(!CM.active)
- to_chat(owner, span_cultlarge("You need to wait [DisplayTimeText(cooldown - world.time)] before you can mark another target!"))
+ click_action = TRUE
+ enable_text = span_cult("You prepare to mark a target for your cult. Click a target to mark them!")
+ disable_text = span_cult("You cease the marking ritual.")
+ /// The duration of the mark itself
+ var/cult_mark_duration = 90 SECONDS
+ /// The duration of the cooldown for cult marks
+ var/cult_mark_cooldown_duration = 2 MINUTES
+ /// The actual cooldown tracked of the action
+ COOLDOWN_DECLARE(cult_mark_cooldown)
+
+/datum/action/innate/cult/master/cultmark/IsAvailable(feedback = FALSE)
+ return ..() && COOLDOWN_FINISHED(src, cult_mark_cooldown)
+
+/datum/action/innate/cult/master/cultmark/InterceptClickOn(mob/caller, params, atom/clicked_on)
+ var/turf/caller_turf = get_turf(caller)
+ if(!isturf(caller_turf))
return FALSE
- return ..()
-/datum/action/innate/cult/master/cultmark/Destroy()
- QDEL_NULL(CM)
+ if(!(clicked_on in view(7, caller_turf)))
+ return FALSE
return ..()
-/datum/action/innate/cult/master/cultmark/Activate()
- CM.toggle(owner) //the important bit
+/datum/action/innate/cult/master/cultmark/do_ability(mob/living/caller, params, atom/clicked_on)
+ var/datum/antagonist/cult/cultist = caller.mind.has_antag_datum(/datum/antagonist/cult, TRUE)
+ if(!cultist)
+ CRASH("[type] was casted by someone without a cult antag datum.")
+ var/datum/team/cult/cult_team = cultist.get_team()
+ if(!cult_team)
+ CRASH("[type] was casted by a cultist without a cult team datum.")
+ if(cult_team.blood_target)
+ to_chat(caller, span_cult("The cult has already designated a target!"))
+ return FALSE
+ if(cult_team.set_blood_target(clicked_on, caller, cult_mark_duration))
+ unset_ranged_ability(caller, span_cult("The marking rite is complete! It will last for [DisplayTimeText(cult_mark_duration)] seconds."))
+ COOLDOWN_START(src, cult_mark_cooldown, cult_mark_cooldown_duration)
+ build_all_button_icons()
+ addtimer(CALLBACK(src, PROC_REF(build_all_button_icons)), cult_mark_cooldown_duration + 1)
+ return TRUE
+ unset_ranged_ability(caller, span_cult("The marking rite failed!"))
return TRUE
-/obj/effect/proc_holder/cultmark
- active = FALSE
- ranged_mousepointer = 'icons/effects/cult_target.dmi'
- var/datum/action/innate/cult/master/cultmark/attached_action
-
-/obj/effect/proc_holder/cultmark/Destroy()
- attached_action = null
- return ..()
+/datum/action/innate/cult/ghostmark //Ghost version
+ name = "Blood Mark your Target"
+ desc = "Marks whatever you are orbiting for the entire cult to track."
+ button_icon_state = "cult_mark"
+ /// The duration of the mark on the target
+ var/cult_mark_duration = 60 SECONDS
+ /// The cooldown between marks - the ability can be used in between cooldowns, but can't mark (only clear)
+ var/cult_mark_cooldown_duration = 60 SECONDS
+ /// The actual cooldown tracked of the action
+ COOLDOWN_DECLARE(cult_mark_cooldown)
-/obj/effect/proc_holder/cultmark/proc/toggle(mob/user)
- if(active)
- remove_ranged_ability(span_cult("You cease the marking ritual."))
- else
- add_ranged_ability(user, span_cult("You prepare to mark a target for your cult..."))
+/datum/action/innate/cult/ghostmark/IsAvailable(feedback = FALSE)
+ return ..() && isobserver(owner)
-/obj/effect/proc_holder/cultmark/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return
- if(ranged_ability_user.incapacitated())
- remove_ranged_ability()
- return
- var/turf/T = get_turf(ranged_ability_user)
- if(!isturf(T))
- return FALSE
+/datum/action/innate/cult/ghostmark/Activate()
+ var/datum/antagonist/cult/cultist = owner.mind?.has_antag_datum(/datum/antagonist/cult, TRUE)
+ if(!cultist)
+ CRASH("[type] was casted by someone without a cult antag datum.")
- var/datum/antagonist/cult/C = caller.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
+ var/datum/team/cult/cult_team = cultist.get_team()
+ if(!cult_team)
+ CRASH("[type] was casted by a cultist without a cult team datum.")
- if(target in view(7, get_turf(ranged_ability_user)))
- if(C.cult_team.blood_target)
- to_chat(ranged_ability_user, span_cult("The cult has already designated a target!"))
- return FALSE
- C.cult_team.blood_target = target
- var/area/A = get_area(target)
- attached_action.cooldown = world.time + attached_action.base_cooldown
- addtimer(CALLBACK(attached_action.owner, TYPE_PROC_REF(/mob, update_action_buttons_icon)), attached_action.base_cooldown)
- C.cult_team.blood_target_image = image('icons/effects/cult_target.dmi', target, "glow", ABOVE_MOB_LAYER)
- C.cult_team.blood_target_image.appearance_flags = RESET_COLOR
- C.cult_team.blood_target_image.pixel_x = -target.pixel_x
- C.cult_team.blood_target_image.pixel_y = -target.pixel_y
- for(var/datum/mind/B in SSticker.mode.cult)
- if(B.current && B.current.stat != DEAD && B.current.client)
- to_chat(B.current, span_cultlarge("[ranged_ability_user] has marked [C.cult_team.blood_target] in the [A.name] as the cult's top priority, get there immediately!"))
- SEND_SOUND(B.current, sound(pick('sound/hallucinations/over_here2.ogg','sound/hallucinations/over_here3.ogg'),0,1,75))
- B.current.client.images += C.cult_team.blood_target_image
- attached_action.owner.update_action_buttons_icon()
- remove_ranged_ability(span_cult("The marking rite is complete! It will last for 90 seconds."))
- C.cult_team.blood_target_reset_timer = addtimer(CALLBACK(GLOBAL_PROC, PROC_REF(reset_blood_target),C.cult_team), 900, TIMER_STOPPABLE)
- return TRUE
- return FALSE
+ if(cult_team.blood_target)
+ if(!COOLDOWN_FINISHED(src, cult_mark_cooldown))
+ cult_team.unset_blood_target_and_timer()
+ to_chat(owner, span_cultbold("You have cleared the cult's blood target!"))
+ return TRUE
-/proc/reset_blood_target(datum/team/cult/team)
- for(var/datum/mind/B in team.members)
- if(B.current && B.current.stat != DEAD && B.current.client)
- if(team.blood_target)
- to_chat(B.current,span_cultlarge("The blood mark has expired!"))
- B.current.client.images -= team.blood_target_image
- QDEL_NULL(team.blood_target_image)
- team.blood_target = null
+ to_chat(owner, span_cultbold("The cult has already designated a target!"))
+ return FALSE
+ if(!COOLDOWN_FINISHED(src, cult_mark_cooldown))
+ to_chat(owner, span_cultbold("You aren't ready to place another blood mark yet!"))
+ return FALSE
-/datum/action/innate/cult/master/cultmark/ghost
- name = "Mark a Blood Target for the Cult"
- desc = "Marks a target for the entire cult to track."
+ var/atom/mark_target = owner.orbiting?.parent || get_turf(owner)
+ if(!mark_target)
+ return FALSE
-/datum/action/innate/cult/master/cultmark/ghost/IsAvailable()
- if(istype(owner, /mob/dead/observer) && iscultist(owner.mind.current))
+ if(cult_team.set_blood_target(mark_target, owner, 60 SECONDS))
+ to_chat(owner, span_cultbold("You have marked [mark_target] for the cult! It will last for [DisplayTimeText(cult_mark_duration)]."))
+ COOLDOWN_START(src, cult_mark_cooldown, cult_mark_cooldown_duration)
+ build_all_button_icons(UPDATE_BUTTON_NAME|UPDATE_BUTTON_ICON)
+ addtimer(CALLBACK(src, PROC_REF(reset_button)), cult_mark_cooldown_duration + 1)
return TRUE
+
+ to_chat(owner, span_cult("The marking failed!"))
+ return FALSE
+
+/datum/action/innate/cult/ghostmark/update_button_name(atom/movable/screen/movable/action_button/current_button, force = FALSE)
+ if(COOLDOWN_FINISHED(src, cult_mark_duration))
+ name = initial(name)
+ desc = initial(desc)
else
- qdel(src)
+ name = "Clear the Blood Mark"
+ desc = "Remove the Blood Mark you previously set."
-/datum/action/innate/cult/ghostmark //Ghost version
- name = "Blood Mark your Target"
- desc = "Marks whatever you are orbitting - for the entire cult to track."
- button_icon_state = "cult_mark"
- var/tracking = FALSE
- var/cooldown = 0
- var/base_cooldown = 600
+ return ..()
-/datum/action/innate/cult/ghostmark/IsAvailable()
- if(istype(owner, /mob/dead/observer) && iscultist(owner.mind.current))
- return TRUE
+/datum/action/innate/cult/ghostmark/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force = FALSE)
+ if(COOLDOWN_FINISHED(src, cult_mark_duration))
+ button_icon_state = initial(button_icon_state)
else
- qdel(src)
+ button_icon_state = "emp"
-/datum/action/innate/cult/ghostmark/proc/reset_button()
- if(owner)
- name = "Blood Mark your Target"
- desc = "Marks whatever you are orbitting - for the entire cult to track."
- button_icon_state = "cult_mark"
- owner.update_action_buttons_icon()
- SEND_SOUND(owner, 'sound/magic/enter_blood.ogg')
- to_chat(owner,span_cultbold("Your previous mark is gone - you are now ready to create a new blood mark."))
+ return ..()
-/datum/action/innate/cult/ghostmark/Activate()
- var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
- if(C.cult_team.blood_target)
- if(cooldown>world.time)
- reset_blood_target(C.cult_team)
- to_chat(owner, span_cultbold("You have cleared the cult's blood target!"))
- deltimer(C.cult_team.blood_target_reset_timer)
- return
- else
- to_chat(owner, span_cultbold("The cult has already designated a target!"))
- return
- if(cooldown>world.time)
- to_chat(owner, span_cultbold("You aren't ready to place another blood mark yet!"))
- return
- target = owner.orbiting?.parent || get_turf(owner)
- if(!target)
+/datum/action/innate/cult/ghostmark/proc/reset_button()
+ if(QDELETED(owner) || QDELETED(src))
return
- C.cult_team.blood_target = target
- var/area/A = get_area(target)
- cooldown = world.time + base_cooldown
- addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob, update_action_buttons_icon)), base_cooldown)
- C.cult_team.blood_target_image = image('icons/effects/cult_target.dmi', target, "glow", ABOVE_MOB_LAYER)
- C.cult_team.blood_target_image.appearance_flags = RESET_COLOR
- C.cult_team.blood_target_image.pixel_x = -target.pixel_x
- C.cult_team.blood_target_image.pixel_y = -target.pixel_y
- SEND_SOUND(owner, sound(pick('sound/hallucinations/over_here2.ogg','sound/hallucinations/over_here3.ogg'),0,1,75))
- owner.client.images += C.cult_team.blood_target_image
- for(var/datum/mind/B in SSticker.mode.cult)
- if(B.current && B.current.stat != DEAD && B.current.client)
- to_chat(B.current, span_cultlarge("[owner] has marked [C.cult_team.blood_target] in the [A.name] as the cult's top priority, get there immediately!"))
- SEND_SOUND(B.current, sound(pick('sound/hallucinations/over_here2.ogg','sound/hallucinations/over_here3.ogg'),0,1,75))
- B.current.client.images += C.cult_team.blood_target_image
- to_chat(owner,span_cultbold("You have marked the [target] for the cult! It will last for [DisplayTimeText(base_cooldown)]."))
- name = "Clear the Blood Mark"
- desc = "Remove the Blood Mark you previously set."
- button_icon_state = "emp"
- owner.update_action_buttons_icon()
- C.cult_team.blood_target_reset_timer = addtimer(CALLBACK(GLOBAL_PROC, PROC_REF(reset_blood_target),C.cult_team), base_cooldown, TIMER_STOPPABLE)
- addtimer(CALLBACK(src, PROC_REF(reset_button)), base_cooldown)
+ SEND_SOUND(owner, 'sound/magic/enter_blood.ogg')
+ to_chat(owner, span_cultbold("Your previous mark is gone - you are now ready to create a new blood mark."))
+ build_all_button_icons(UPDATE_BUTTON_NAME|UPDATE_BUTTON_ICON)
//////// ELDRITCH PULSE /////////
@@ -380,84 +342,88 @@
/datum/action/innate/cult/master/pulse
name = "Eldritch Pulse"
desc = "Seize upon a fellow cultist or cult structure and teleport it to a nearby location."
- icon_icon = 'icons/mob/actions/actions_spells.dmi'
+ button_icon = 'icons/mob/actions/actions_spells.dmi'
button_icon_state = "arcane_barrage"
- var/obj/effect/proc_holder/pulse/PM
- var/cooldown = 0
- var/base_cooldown = 150
- var/throwing = FALSE
- var/mob/living/throwee
-
-/datum/action/innate/cult/master/pulse/New()
- PM = new()
- PM.attached_action = src
- ..()
-
-/datum/action/innate/cult/master/pulse/IsAvailable()
- if(!owner.mind || !owner.mind.has_antag_datum(/datum/antagonist/cult/master))
+ click_action = TRUE
+ enable_text = span_cult("You prepare to tear through the fabric of reality... Click a target to sieze them!")
+ disable_text = span_cult("You cease your preparations.")
+ /// Weakref to whoever we're currently about to toss
+ var/datum/weakref/throwee_ref
+ /// Cooldown of the ability
+ var/pulse_cooldown_duration = 15 SECONDS
+ /// The actual cooldown tracked of the action
+ COOLDOWN_DECLARE(pulse_cooldown)
+
+/datum/action/innate/cult/master/pulse/IsAvailable(feedback = FALSE)
+ return ..() && COOLDOWN_FINISHED(src, pulse_cooldown)
+
+/datum/action/innate/cult/master/pulse/InterceptClickOn(mob/living/caller, params, atom/clicked_on)
+ var/turf/caller_turf = get_turf(caller)
+ if(!isturf(caller_turf))
return FALSE
- if(cooldown > world.time)
- if(!PM.active)
- to_chat(owner, span_cultlarge("You need to wait [DisplayTimeText(cooldown - world.time)] before you can pulse again!"))
+
+ if(!(clicked_on in view(7, caller_turf)))
return FALSE
- return ..()
-/datum/action/innate/cult/master/pulse/Destroy()
- PM.attached_action = null //What the fuck is even going on here.
- QDEL_NULL(PM)
+ if(clicked_on == caller)
+ return FALSE
return ..()
+/datum/action/innate/cult/master/pulse/do_ability(mob/living/caller, params, atom/clicked_on)
+ var/atom/throwee = throwee_ref?.resolve()
+ if(QDELETED(throwee))
+ to_chat(caller, span_cult("You lost your target!"))
+ throwee = null
+ throwee_ref = null
+ return FALSE
+ if(throwee)
+ if(get_dist(throwee, clicked_on) >= 16)
+ to_chat(caller, span_cult("You can't teleport [clicked_on.p_them()] that far!"))
+ return FALSE
+ var/turf/throwee_turf = get_turf(throwee)
+
+ playsound(throwee_turf, 'sound/magic/exit_blood.ogg')
+ new /obj/effect/temp_visual/cult/sparks(throwee_turf, caller.dir)
+ throwee.visible_message(
+ span_warning("A pulse of magic whisks [throwee] away!"),
+ span_cult("A pulse of blood magic whisks you away..."),
+ )
+
+ if(!do_teleport(throwee, clicked_on, channel = TELEPORT_CHANNEL_CULT))
+ to_chat(caller, span_cult("The teleport fails!"))
+ throwee.visible_message(
+ span_warning("...Except they don't go very far"),
+ span_cult("...Except you don't appear to have moved very far."),
+ )
+ return FALSE
-/datum/action/innate/cult/master/pulse/Activate()
- PM.toggle(owner) //the important bit
- return TRUE
-
-/obj/effect/proc_holder/pulse
- active = FALSE
- ranged_mousepointer = 'icons/effects/throw_target.dmi'
- var/datum/action/innate/cult/master/pulse/attached_action
+ throwee_turf.Beam(clicked_on, icon_state = "sendbeam", time = 0.4 SECONDS)
+ new /obj/effect/temp_visual/cult/sparks(get_turf(clicked_on), caller.dir)
+ throwee.visible_message(
+ span_warning("[throwee] appears suddenly in a pulse of magic!"),
+ span_cult("...And you appear elsewhere."),
+ )
-/obj/effect/proc_holder/pulse/Destroy()
- attached_action = null
- return ..()
+ COOLDOWN_START(src, pulse_cooldown, pulse_cooldown_duration)
+ to_chat(caller, span_cult("A pulse of blood magic surges through you as you shift [throwee] through time and space."))
+ caller.click_intercept = null
+ throwee_ref = null
+ build_all_button_icons()
+ addtimer(CALLBACK(src, PROC_REF(build_all_button_icons)), pulse_cooldown_duration + 1)
+ return TRUE
-/obj/effect/proc_holder/pulse/proc/toggle(mob/user)
- if(active)
- remove_ranged_ability(span_cult("You cease your preparations..."))
- attached_action.throwing = FALSE
- else
- add_ranged_ability(user, span_cult("You prepare to tear through the fabric of reality..."))
+ if(isliving(clicked_on))
+ var/mob/living/living_clicked = clicked_on
+ if(!iscultist(living_clicked))
+ return FALSE
+ SEND_SOUND(caller, sound('sound/weapons/thudswoosh.ogg'))
+ to_chat(caller, span_cultbold("You reach through the veil with your mind's eye and seize [clicked_on]! Click anywhere nearby to teleport [clicked_on.p_them()]!"))
+ throwee_ref = WEAKREF(clicked_on)
+ return TRUE
-/obj/effect/proc_holder/pulse/InterceptClickOn(mob/living/caller, params, atom/target)
- if(..())
- return
- if(ranged_ability_user.incapacitated())
- remove_ranged_ability()
- return
- var/turf/T = get_turf(ranged_ability_user)
- if(!isturf(T))
- return FALSE
- if(target in view(7, get_turf(ranged_ability_user)))
- if((!(iscultist(target) || istype(target, /obj/structure/destructible/cult)) || target == caller) && !(attached_action.throwing))
- return
- if(!attached_action.throwing)
- attached_action.throwing = TRUE
- attached_action.throwee = target
- SEND_SOUND(ranged_ability_user, sound('sound/weapons/thudswoosh.ogg'))
- to_chat(ranged_ability_user,span_cult("You reach through the veil with your mind's eye and seize [target]!"))
- return
- else
- new /obj/effect/temp_visual/cult/sparks(get_turf(attached_action.throwee), ranged_ability_user.dir)
- var/distance = get_dist(attached_action.throwee, target)
- if(distance >= 16)
- return
- playsound(target,'sound/magic/exit_blood.ogg')
- attached_action.throwee.Beam(target,icon_state="sendbeam",time=4)
- attached_action.throwee.forceMove(get_turf(target))
- new /obj/effect/temp_visual/cult/sparks(get_turf(target), ranged_ability_user.dir)
- attached_action.throwing = FALSE
- attached_action.cooldown = world.time + attached_action.base_cooldown
- remove_ranged_ability(span_cult("A pulse of blood magic surges through you as you shift [attached_action.throwee] through time and space."))
- caller.update_action_buttons_icon()
- addtimer(CALLBACK(caller, TYPE_PROC_REF(/mob, update_action_buttons_icon)), attached_action.base_cooldown)
+ if(istype(clicked_on, /obj/structure/destructible/cult))
+ to_chat(caller, span_cultbold("You reach through the veil with your mind's eye and lift [clicked_on]! Click anywhere nearby to teleport it!"))
+ throwee_ref = WEAKREF(clicked_on)
+ return TRUE
+ return FALSE
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index 2c926711c60b..be5cb128225e 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -210,7 +210,7 @@
/datum/action/innate/dash/cult
name = "Rend the Veil"
desc = "Use the sword to shear open the flimsy fabric of this reality and teleport to your target."
- icon_icon = 'icons/mob/actions/actions_cult.dmi'
+ button_icon = 'icons/mob/actions/actions_cult.dmi'
button_icon_state = "phaseshift"
dash_sound = 'sound/magic/enter_blood.ogg'
recharge_sound = 'sound/magic/exit_blood.ogg'
@@ -218,7 +218,7 @@
phasein = /obj/effect/temp_visual/dir_setting/cult/phase
phaseout = /obj/effect/temp_visual/dir_setting/cult/phase/out
-/datum/action/innate/dash/cult/IsAvailable()
+/datum/action/innate/dash/cult/IsAvailable(feedback = FALSE)
if(iscultist(holder) && current_charges)
return TRUE
else
@@ -240,7 +240,7 @@
sword = bastard
holder = user
-/datum/action/innate/cult/spin2win/IsAvailable()
+/datum/action/innate/cult/spin2win/IsAvailable(feedback = FALSE)
if(iscultist(holder) && cooldown <= world.time)
return TRUE
else
@@ -254,14 +254,14 @@
sword.block_chance = 100
sword.slowdown += 1.5
addtimer(CALLBACK(src, PROC_REF(stop_spinning)), 50)
- holder.update_action_buttons_icon()
+ holder.update_mob_action_buttons()
/datum/action/innate/cult/spin2win/proc/stop_spinning()
sword.spinning = FALSE
sword.block_chance = 50
sword.slowdown -= 1.5
sleep(sword.spin_cooldown)
- holder.update_action_buttons_icon()
+ holder.update_mob_action_buttons()
/obj/item/restraints/legcuffs/bola/cult
name = "nar'sien bola"
@@ -414,7 +414,7 @@
to_chat(user, span_cultlarge("\"I wouldn't advise that.\""))
to_chat(user, span_warning("An overwhelming sense of nausea overpowers you!"))
user.dropItemToGround(src, TRUE)
- user.Dizzy(30)
+ user.adjust_dizzy(3 SECONDS)
user.Paralyze(100)
else
to_chat(user, span_cultlarge("\"Trying to use things you don't own is bad, you know.\""))
@@ -466,7 +466,7 @@
to_chat(user, span_cultlarge("\"I wouldn't advise that.\""))
to_chat(user, span_warning("An overwhelming sense of nausea overpowers you!"))
user.dropItemToGround(src, TRUE)
- user.Dizzy(30)
+ user.adjust_dizzy(30)
user.Paralyze(100)
else
to_chat(user, span_cultlarge("\"Trying to use things you don't own is bad, you know.\""))
@@ -487,7 +487,7 @@
if(!iscultist(user))
to_chat(user, span_cultlarge("\"You want to be blind, do you?\""))
user.dropItemToGround(src, TRUE)
- user.Dizzy(30)
+ user.adjust_dizzy(30)
user.Paralyze(100)
user.blind_eyes(30)
@@ -646,7 +646,7 @@ GLOBAL_VAR_INIT(curselimit, 0)
to_chat(user, "[cultist_to_receive] is not a follower of the Geometer!")
log_game("Void torch failed - target was deconverted")
return
- if(A in user.GetAllContents())
+ if(A in user.get_all_contents())
to_chat(user, "[A] must be on a surface in order to teleport it!")
return
to_chat(user, "You ignite [A] with \the [src], turning it to ash, but through the torch's flames you see that [A] has reached [cultist_to_receive]!")
@@ -728,15 +728,16 @@ GLOBAL_VAR_INIT(curselimit, 0)
name = "Bloody Bond"
desc = "Call the blood spear back to your hand!"
background_icon_state = "bg_demon"
+ overlay_icon_state = "bg_demon_border"
+
button_icon_state = "bloodspear"
+ default_button_position = "6:157,4:-2"
var/obj/item/twohanded/cult_spear/spear
var/cooldown = 0
/datum/action/innate/cult/spear/Grant(mob/user, obj/blood_spear)
. = ..()
spear = blood_spear
- button.screen_loc = "6:157,4:-2"
- button.moved = "6:157,4:-2"
/datum/action/innate/cult/spear/Activate()
if(owner == spear.loc || cooldown > world.time)
diff --git a/code/modules/antagonists/cult/rune_spawn_action.dm b/code/modules/antagonists/cult/rune_spawn_action.dm
index 582ae9056e1b..53d8d08b3576 100644
--- a/code/modules/antagonists/cult/rune_spawn_action.dm
+++ b/code/modules/antagonists/cult/rune_spawn_action.dm
@@ -3,10 +3,12 @@
name = "Summon Rune"
desc = "Summons a rune"
background_icon_state = "bg_demon"
+ overlay_icon_state = "bg_demon_border"
+
var/obj/effect/rune/rune_type
var/cooldown = 0
- var/base_cooldown = 1800
- var/scribe_time = 60
+ var/base_cooldown = 3 MINUTES
+ var/scribe_time = 6 SECONDS
var/damage_interrupt = TRUE
var/action_interrupt = TRUE
var/obj/effect/temp_visual/cult/rune_spawn/rune_word_type
@@ -14,7 +16,7 @@
var/obj/effect/temp_visual/cult/rune_spawn/rune_center_type
var/rune_color
-/datum/action/innate/cult/create_rune/IsAvailable()
+/datum/action/innate/cult/create_rune/IsAvailable(feedback = FALSE)
if(!rune_type || cooldown > world.time)
return FALSE
return ..()
@@ -56,8 +58,8 @@
R4 = new rune_center_type(T, scribe_time, rune_color)
cooldown = base_cooldown + world.time
- owner.update_action_buttons_icon()
- addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob, update_action_buttons_icon)), base_cooldown)
+ owner.update_mob_action_buttons()
+ addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob, update_mob_action_buttons)), base_cooldown)
var/list/health
if(damage_interrupt && isliving(owner))
var/mob/living/L = owner
@@ -78,7 +80,7 @@
if(R4)
qdel(R4)
cooldown = 0
- owner.update_action_buttons_icon()
+ owner.update_mob_action_buttons()
//teleport rune
/datum/action/innate/cult/create_rune/tele
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index 7fdb19a1170c..81f1f960f9ee 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -302,8 +302,8 @@ structure_check() searches for nearby cultist structures required for the invoca
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(H)]) to replace a jobbanned player.")
H.key = C.key
H.uncuff()
- H.stuttering = 0
- H.cultslurring = 0
+ H.remove_status_effect(/datum/status_effect/speech/slurring/cult)
+ H.remove_status_effect(/datum/status_effect/speech/stutter)
return TRUE
/obj/effect/rune/convert/proc/do_sacrifice(mob/living/sacrificial, list/invokers)
@@ -1057,7 +1057,7 @@ structure_check() searches for nearby cultist structures required for the invoca
continue
if(ishuman(M))
if(!iscultist(M))
- AH.remove_hud_from(M)
+ AH.hide_from(M)
addtimer(CALLBACK(GLOBAL_PROC, PROC_REF(hudFix), M), duration)
var/image/A = image('icons/mob/mob.dmi',M,"cultist", ABOVE_MOB_LAYER)
A.override = 1
@@ -1150,4 +1150,4 @@ structure_check() searches for nearby cultist structures required for the invoca
var/obj/O = target.get_item_by_slot(SLOT_GLASSES)
if(istype(O, /obj/item/clothing/glasses/hud/security))
var/datum/atom_hud/AH = GLOB.huds[DATA_HUD_SECURITY_ADVANCED]
- AH.add_hud_to(target)
+ AH.show_to(target)
diff --git a/code/modules/antagonists/demon/demons.dm b/code/modules/antagonists/demon/demons.dm
index 5e3a55003cee..aead8194ab0b 100644
--- a/code/modules/antagonists/demon/demons.dm
+++ b/code/modules/antagonists/demon/demons.dm
@@ -22,14 +22,15 @@
///The list of choosable sins for demons. One will be assigned to a demon when spawned naturally.
var/static/list/demonsins = list(SIN_GLUTTONY, SIN_GREED, SIN_WRATH, SIN_ENVY, SIN_PRIDE)
var/static/list/demon_spells = typecacheof(list(
- /obj/effect/proc_holder/spell/targeted/shapeshift/demon,
- /obj/effect/proc_holder/spell/targeted/shapeshift/demon/gluttony,
- /obj/effect/proc_holder/spell/targeted/shapeshift/demon/wrath,
- /obj/effect/proc_holder/spell/targeted/forcewall/gluttony,
- /obj/effect/proc_holder/spell/aoe_turf/conjure/summon_greedslots,
- /obj/effect/proc_holder/spell/targeted/inflict_handler/ignite,
- /obj/effect/proc_holder/spell/targeted/touch/envy,
- /obj/effect/proc_holder/spell/aoe_turf/conjure/summon_mirror))
+ /datum/action/cooldown/spell/shapeshift/demon,
+ /datum/action/cooldown/spell/shapeshift/demon/gluttony,
+ /datum/action/cooldown/spell/shapeshift/demon/wrath,
+ /datum/action/cooldown/spell/forcewall/gluttony,
+ /datum/action/cooldown/spell/conjure/summon_greedslots,
+ /datum/action/cooldown/spell/pointed/ignite,
+ /datum/action/cooldown/spell/touch/envy,
+ /datum/action/cooldown/spell/conjure/summon_mirror
+ ))
var/static/list/sinfuldemon_traits = list(
TRAIT_GENELESS,
@@ -65,7 +66,7 @@
else
L.visible_message(span_warning("[L] continues to burn!"), span_danger("You continue to burn!"))
L.adjust_fire_stacks(5)
- L.IgniteMob()
+ L.ignite_mob()
return
/datum/antagonist/sinfuldemon/New()
@@ -120,7 +121,7 @@
to_chat(owner.current, "[span_warning("Do your best to complete your objectives without unnessecary death, unless you are a wrathful demon.")]
")
owner.announce_objectives()
SEND_SOUND(owner.current, sound('sound/magic/ethereal_exit.ogg'))
- .=..()
+ . = ..()
/datum/antagonist/sinfuldemon/on_gain()
forge_objectives()
@@ -128,46 +129,61 @@
owner.current.faction += "hell"
for(var/all_traits in sinfuldemon_traits) ///adds demon traits
ADD_TRAIT(owner.current, all_traits, SINFULDEMON_TRAIT)
- if(owner.assigned_role == "Clown" && ishuman(owner.current))
- var/mob/living/carbon/human/S = owner.current
- to_chat(S, span_notice("Your infernal nature has allowed you to overcome your clownishness."))
- S.dna.remove_mutation(CLOWNMUT)
+ handle_clown_mutation(owner.current, "Your infernal nature has allowed you to overcome your clownishness.")
switch(demonsin)
if(SIN_GLUTTONY)
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/shapeshift/demon/gluttony)
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/forcewall/gluttony)
+ var/datum/action/cooldown/spell/forcewall/gluttony/fat_wall = new(owner.current)
+ fat_wall.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/shapeshift/demon/gluttony/fat_demon = new(owner.current)
+ fat_demon.Grant(owner.current)
+
ADD_TRAIT(owner.current, TRAIT_AGEUSIA, SINFULDEMON_TRAIT) // nothing disgusts you
ADD_TRAIT(owner.current, TRAIT_EAT_MORE, SINFULDEMON_TRAIT) // 3x hunger rate
ADD_TRAIT(owner.current, TRAIT_BOTTOMLESS_STOMACH, SINFULDEMON_TRAIT) // nutrition is capped for infinite eating
ADD_TRAIT(owner.current, TRAIT_VORACIOUS, SINFULDEMON_TRAIT) // eat and drink faster & eat infinite snacks
+
if(SIN_GREED)
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/shapeshift/demon)
- owner.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/summon_greedslots)
+ var/datum/action/cooldown/spell/shapeshift/demon/demon_form = new(owner.current)
+ demon_form.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/conjure/summon_greedslots/gambling_addiction = new(owner.current)
+ gambling_addiction.Grant(owner.current)
+
if(SIN_WRATH)
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/shapeshift/demon/wrath)
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/inflict_handler/ignite)
+ var/datum/action/cooldown/spell/shapeshift/demon/wrath/wrath_demon = new(owner.current)
+ wrath_demon.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/pointed/ignite/not_fireball = new(owner.current)
+ not_fireball.Grant(owner.current)
+
if(SIN_ENVY)
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/shapeshift/demon)
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/touch/envy)
+ var/datum/action/cooldown/spell/shapeshift/demon/demon_form = new(owner.current)
+ demon_form.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/touch/envy/agent_id = new(owner.current)
+ agent_id.Grant(owner.current)
+
if(SIN_PRIDE)
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/shapeshift/demon)
- owner.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/summon_mirror)
- .=..()
+ var/datum/action/cooldown/spell/shapeshift/demon/demon_form = new(owner.current)
+ demon_form.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/conjure/summon_mirror/space_hole = new(owner.current)
+ space_hole.Grant(owner.current)
+
+ return ..()
/datum/antagonist/sinfuldemon/on_removal()
owner.special_role = null
owner.current.faction -= "hell"
for(var/all_status_traits in owner.current.status_traits) //removes demon traits
REMOVE_TRAIT(owner.current, all_status_traits, SINFULDEMON_TRAIT)
- remove_spells()
+ for(var/datum/action/cooldown/spell/spell in owner.current.actions)
+ if(spell.target == owner)
+ qdel(spell)
+ owner.current.actions -= spell
to_chat(owner.current, span_userdanger("Your infernal link has been severed! You are no longer a demon!"))
- .=..()
-
-/datum/antagonist/sinfuldemon/proc/remove_spells()
- for(var/X in owner.spell_list)
- var/obj/effect/proc_holder/spell/S = X
- if(is_type_in_typecache(S, demon_spells))
- owner.RemoveSpell(S)
+ return ..()
/datum/antagonist/sinfuldemon/roundend_report()
var/list/parts = list()
diff --git a/code/modules/antagonists/demon/general_powers.dm b/code/modules/antagonists/demon/general_powers.dm
index 2cdf2e657145..419442d6a616 100644
--- a/code/modules/antagonists/demon/general_powers.dm
+++ b/code/modules/antagonists/demon/general_powers.dm
@@ -1,13 +1,15 @@
-/obj/effect/proc_holder/spell/targeted/shapeshift/demon //emergency get out of jail card.
+/datum/action/cooldown/spell/shapeshift/demon //emergency get out of jail card.
name = "Lesser Demon Form"
desc = "Take on your true demon form. This form is strong but very obvious. It's full demonic nature in this realm is taxing on you \
and you will slowly lose life while in this form, while also being especially weak to holy influences. \
Be aware low health transfers between forms. If gravely wounded, attack live mortals to siphon life energy from them!"
+ background_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon_state = "daemontransform"
+ background_icon_state = "bg_demon"
+
invocation = "COWER, MORTALS!!"
+
shapeshift_type = /mob/living/simple_animal/lesserdemon
- action_icon = 'icons/mob/actions/actions_minor_antag.dmi'
- action_icon_state = "daemontransform"
- action_background_icon_state = "bg_demon"
/mob/living/simple_animal/lesserdemon
name = "demon"
diff --git a/code/modules/antagonists/demon/sins/envy.dm b/code/modules/antagonists/demon/sins/envy.dm
index 3123497af01f..1b890d7a4905 100644
--- a/code/modules/antagonists/demon/sins/envy.dm
+++ b/code/modules/antagonists/demon/sins/envy.dm
@@ -1,54 +1,64 @@
-/obj/effect/proc_holder/spell/targeted/touch/envy
+/datum/action/cooldown/spell/touch/envy
name = "Vanity Steal"
desc = "Engulfs your arm in a jealous might, allowing you to steal the look of the first human-like struck with it. Note, the form change is not reversible."
+ button_icon = 'icons/mob/actions/actions_changeling.dmi'
+ button_icon_state = "transform"
+ background_icon_state = "bg_demon"
+
+ school = SCHOOL_EVOCATION
+ invocation = "I'M BETTER THAN YOU!!"
+ invocation_type = INVOCATION_WHISPER
+
+ cooldown_time = 15 SECONDS
+ spell_requirements = NONE
+
hand_path = /obj/item/melee/touch_attack/envy
- school = "evocation"
- charge_max = 150
- clothes_req = FALSE
- invocation = "ETERNAL FLAMES"
- invocation_type = SPELL_INVOCATION_WHISPER
- action_icon = 'icons/mob/actions/actions_changeling.dmi'
- action_icon_state = "transform"
- action_background_icon_state = "bg_demon"
+ ///The ID acess we store
var/list/stored_access
-/obj/effect/proc_holder/spell/targeted/touch/envy/on_gain(mob/living/user)
+/datum/action/cooldown/spell/touch/envy/Grant(mob/living/user)
+ . = ..()
RegisterSignal(user, COMSIG_MOB_ALLOWED, PROC_REF(envy_access))
-/obj/effect/proc_holder/spell/targeted/touch/envy/on_lose(mob/living/user)
+/datum/action/cooldown/spell/touch/envy/Remove(mob/living/user)
UnregisterSignal(user, COMSIG_MOB_ALLOWED)
+ return ..()
-/obj/effect/proc_holder/spell/targeted/touch/envy/proc/envy_access(datum/source, obj/O)
- return O.check_access_list(stored_access)
+/datum/action/cooldown/spell/touch/envy/proc/envy_access(datum/source, obj/access_checker)
+ return access_checker.check_access_list(stored_access)
/obj/item/melee/touch_attack/envy
name = "Envious Hand"
desc = "A writhing, burning aura of jealousy, ready to be unleashed."
icon_state = "flagellation"
item_state = "hivemind"
- catchphrase = "I'M BETTER THAN YOU!!"
/obj/item/melee/touch_attack/envy/afterattack(atom/target, mob/living/carbon/human/user, proximity_flag, click_parameters)
if(!proximity_flag)
return
- var/mob/living/M = target
- if(M.anti_magic_check())
- to_chat(user, span_warning("[M] resists your unholy jealousy!"))
- to_chat(M, span_warning("A creeping feeling of jealousy dances around your mind before being suddenly dispelled."))
- ..()
- return
- playsound(user, 'sound/magic/demon_attack1.ogg', 75, TRUE)
- if(ishuman(target))
- var/mob/living/carbon/human/H = target
- if(user.real_name != H.dna.real_name)
- if(attached_spell)
- var/obj/effect/proc_holder/spell/targeted/touch/envy/E = attached_spell
- var/obj/item/card/id/A = H.get_idcard()
- E.stored_access = A?.access
- user.fully_replace_character_name(user.real_name, H.dna.real_name)
- H.dna.transfer_identity(user, transfer_SE=1)
- user.updateappearance(mutcolor_update=1)
- user.domutcheck()
- user.visible_message(span_warning("[user]'s appearance shifts into [H]'s!"), \
- span_boldannounce("[H.p_they(TRUE)] think[H.p_s()] [H.p_theyre()] sooo much better than you. Not anymore, [H.p_they()] won't."))
+ var/mob/living/living_target = target
+ if(living_target.anti_magic_check())
+ to_chat(user, span_warning("[living_target] resists your unholy jealousy!"))
+ to_chat(living_target, span_warning("A creeping feeling of jealousy dances around your mind before being suddenly dispelled."))
return ..()
+ playsound(user, 'sound/magic/demon_attack1.ogg', 75, TRUE)
+ if(!ishuman(target))
+ return
+
+ var/mob/living/carbon/human/human_target = target
+ if(user.real_name == human_target.dna.real_name)
+ return
+
+ var/datum/action/cooldown/spell/touch/envy/envy_spell = spell_which_made_us?.resolve()
+ var/obj/item/card/id/ID = human_target.get_idcard()
+ envy_spell?.stored_access = ID?.access
+ user.fully_replace_character_name(user.real_name, human_target.dna.real_name)
+ human_target.dna.transfer_identity(user, transfer_SE=1)
+ user.updateappearance(mutcolor_update=1)
+ user.domutcheck()
+ user.visible_message(
+ span_warning("[user]'s appearance shifts into [human_target]'s!"), \
+ span_boldannounce("[human_target.p_they(TRUE)] think[human_target.p_s()] human_targetH.p_theyre()] \
+ sooo much better than you. Not anymore, [human_target.p_they()] won't.")
+ )
+ return ..()
diff --git a/code/modules/antagonists/demon/sins/gluttony.dm b/code/modules/antagonists/demon/sins/gluttony.dm
index 795a568df846..d17739dccd08 100644
--- a/code/modules/antagonists/demon/sins/gluttony.dm
+++ b/code/modules/antagonists/demon/sins/gluttony.dm
@@ -1,21 +1,17 @@
-/obj/effect/proc_holder/spell/targeted/forcewall/gluttony
+/datum/action/cooldown/spell/forcewall/gluttony
name = "Gluttonous Wall"
desc = "Create a magical barrier that only allows fat people to pass through."
- school = "transmutation"
- charge_max = 150
- clothes_req = FALSE
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ background_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon_state = "blob"
+ background_icon_state = "bg_demon"
+
invocation = "INDULGE"
- invocation_type = SPELL_INVOCATION_SAY
- sound = 'sound/magic/forcewall.ogg'
- action_icon = 'icons/mob/actions/actions_minor_antag.dmi'
- action_icon_state = "blob"
- action_background_icon_state = "bg_demon"
- range = -1
- include_user = TRUE
- cooldown_min = 50 //12 deciseconds reduction per rank
+ invocation_type = INVOCATION_SHOUT
+
wall_type = /obj/effect/gluttony/timed
-/obj/effect/proc_holder/spell/targeted/shapeshift/demon/gluttony //emergency get out of jail card, but better. It also eats everything.
+/datum/action/cooldown/spell/shapeshift/demon/gluttony //emergency get out of jail card, but better. It also eats everything.
name = "Gluttony Demon Form"
desc = "Take on your true demon form. This form is strong but very obvious. It's full demonic nature in this realm is taxing on you \
and you will slowly lose life while in this form, while also being especially weak to holy influences. \
@@ -48,8 +44,8 @@
return TRUE
/obj/effect/gluttony/timed
- ///Time in deciseconds before it deletes itself.
- var/timeleft = 150
+ ///Time before it deletes itself.
+ var/timeleft = 15 SECONDS
/obj/effect/gluttony/timed/Initialize()
. = ..()
diff --git a/code/modules/antagonists/demon/sins/greed.dm b/code/modules/antagonists/demon/sins/greed.dm
index 4d795e1b434a..6b3ce1022b09 100644
--- a/code/modules/antagonists/demon/sins/greed.dm
+++ b/code/modules/antagonists/demon/sins/greed.dm
@@ -1,17 +1,15 @@
-/obj/effect/proc_holder/spell/aoe_turf/conjure/summon_greedslots
+/datum/action/cooldown/spell/conjure/summon_greedslots
name = "Summon Slotmachine"
desc = "Summon forth a temporary slot machine of greed, allowing you to offer patrons a deadly game where the price is their life (and some money if you'd like) and the possible prize is a one use die of fate."
- invocation = "Just one game?"
- invocation_type = SPELL_INVOCATION_WHISPER
- clothes_req = FALSE
- charge_max = 600
- cooldown_min = 200
- summon_type = list(/obj/structure/cursed_slot_machine/betterchance)
- summon_lifespan = 600
- summon_amt = 1
- range = 1
- action_icon = 'icons/mob/actions/actions_minor_antag.dmi'
- action_icon_state = "slots"
- action_background_icon_state = "bg_demon"
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon_state = "slots"
+ background_icon_state = "bg_demon"
+ invocation = "Just one game?"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+ cooldown_time = 30 SECONDS
+ summon_lifespan = 1 MINUTES
+ summon_radius = 0 //spawns on top of us
+ summon_type = list(/obj/structure/cursed_slot_machine/betterchance)
diff --git a/code/modules/antagonists/demon/sins/pride.dm b/code/modules/antagonists/demon/sins/pride.dm
index 99a66d1bef2c..8a9049612114 100644
--- a/code/modules/antagonists/demon/sins/pride.dm
+++ b/code/modules/antagonists/demon/sins/pride.dm
@@ -1,17 +1,16 @@
-/obj/effect/proc_holder/spell/aoe_turf/conjure/summon_mirror
+/datum/action/cooldown/spell/conjure/summon_mirror
name = "Summon Mirror"
desc = "Summon forth a temporary mirror of sin that will allow you and others to change anything they want about themselves."
- invocation = "Aren't I so amazing?"
- invocation_type = SPELL_INVOCATION_WHISPER
- clothes_req = FALSE
- charge_max = 600
- cooldown_min = 200
- summon_type = list(/obj/structure/mirror/magic/lesser)
- summon_lifespan = 60 SECONDS
- range = 1
- action_icon = 'icons/mob/actions/actions_minor_antag.dmi'
- action_icon_state = "magic_mirror"
- action_background_icon_state = "bg_demon"
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon_state = "magic_mirror"
+ background_icon_state = "bg_demon"
+ invocation = "Aren't I so amazing?"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+ cooldown_time = 30 SECONDS
+ summon_lifespan = 1 MINUTES
+ summon_radius = 0
+ summon_type = list(/obj/structure/mirror/magic/lesser)
diff --git a/code/modules/antagonists/demon/sins/wrath.dm b/code/modules/antagonists/demon/sins/wrath.dm
index 536bb31d300d..f066513e2ee6 100644
--- a/code/modules/antagonists/demon/sins/wrath.dm
+++ b/code/modules/antagonists/demon/sins/wrath.dm
@@ -1,4 +1,4 @@
-/obj/effect/proc_holder/spell/targeted/shapeshift/demon/wrath //emergency get out of jail card, but better.
+/datum/action/cooldown/spell/shapeshift/demon/wrath //emergency get out of jail card, but better.
name = "Wrath Demon Form"
shapeshift_type = /mob/living/simple_animal/lesserdemon/wrath
@@ -10,32 +10,37 @@
icon_state = "lesserdaemon_wrath"
icon_living = "lesserdaemon_wrath"
-/obj/effect/proc_holder/spell/pointed/trigger/ignite
+#define WRATHFUL_FIRE_AMOUNT 5
+
+/datum/action/cooldown/spell/pointed/ignite
name = "Ignite"
desc = "This ranged spell sets a person on fire."
- school = "transmutation"
- charge_max = 600
- clothes_req = FALSE
- invocation = "BURN IN HELL!!"
- invocation_type = SPELL_INVOCATION_SAY
- message = span_notice("You ignite in a flash of hellfire!")
- cooldown_min = 75
- ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
- action_icon = 'icons/mob/actions/humble/actions_humble.dmi'
- action_icon_state = "sacredflame"
+ button_icon = 'icons/mob/actions/humble/actions_humble.dmi'
+ base_icon_state = "sacredflame"
active_msg = "You prepare to ignite a target..."
+ ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
-/obj/effect/proc_holder/spell/targeted/inflict_handler/ignite
- name = "Ignite"
- desc = "This spell sets a person on fire from range."
- school = "transmutation"
+ school = SCHOOL_TRANSMUTATION
invocation = "BURN IN HELL!!"
- invocation_type = SPELL_INVOCATION_SAY
- charge_max = 600
- clothes_req = FALSE
- action_icon = 'icons/mob/actions/humble/actions_humble.dmi'
- action_icon_state = "sacredflame"
- amt_firestacks = 5
- ignites = TRUE
+ invocation_type = INVOCATION_SHOUT
+
sound = 'sound/magic/fireball.ogg'
+ cooldown_time = 1 MINUTES
+ active_msg = span_notice("You ignite in a flash of hellfire!")
+ spell_requirements = NONE
+
+/datum/action/cooldown/spell/pointed/ignite/InterceptClickOn(mob/living/demon, params, atom/victim)
+ . = ..()
+ if(!.)
+ return FALSE
+
+ if(!isliving(victim))
+ return FALSE
+ var/mob/living/target = victim
+ target.ignite_mob()
+ target.adjust_fire_stacks(WRATHFUL_FIRE_AMOUNT)
+
+ return TRUE
+
+#undef WRATHFUL_FIRE_AMOUNT
diff --git a/code/modules/antagonists/devil/devil.dm b/code/modules/antagonists/devil/devil.dm
index a2316d486882..98f192554837 100644
--- a/code/modules/antagonists/devil/devil.dm
+++ b/code/modules/antagonists/devil/devil.dm
@@ -84,11 +84,16 @@ GLOBAL_LIST_INIT(devil_pre_title, list("Dark ", "Hellish ", "Fallen ", "Fiery ",
GLOBAL_LIST_INIT(devil_title, list("Lord ", "Prelate ", "Count ", "Viscount ", "Vizier ", "Elder ", "Adept "))
GLOBAL_LIST_INIT(devil_syllable, list("hal", "ve", "odr", "neit", "ci", "quon", "mya", "folth", "wren", "geyr", "hil", "niet", "twou", "phi", "coa"))
GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master", ", the Lord of all things", ", Jr."))
+
+/*
+ * Datum
+ */
/datum/antagonist/devil
name = "Devil"
roundend_category = "devils"
antagpanel_category = "Devil"
job_rank = ROLE_DEVIL
+ antag_hud_name = "devil"
show_to_ghosts = TRUE
var/obligation
var/ban
@@ -99,16 +104,16 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
var/reviveNumber = 0
var/form = BASIC_DEVIL
var/static/list/devil_spells = typecacheof(list(
- /obj/effect/proc_holder/spell/aimed/fireball/hellish,
- /obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork,
- /obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork/greater,
- /obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork/ascended,
- /obj/effect/proc_holder/spell/targeted/infernal_jaunt,
- /obj/effect/proc_holder/spell/targeted/sintouch,
- /obj/effect/proc_holder/spell/targeted/sintouch/ascended,
- /obj/effect/proc_holder/spell/targeted/summon_contract,
- /obj/effect/proc_holder/spell/targeted/conjure_item/violin,
- /obj/effect/proc_holder/spell/targeted/summon_dancefloor))
+ /datum/action/cooldown/spell/pointed/projectile/fireball/hellish,
+ /datum/action/cooldown/spell/conjure_item/summon_pitchfork,
+ /datum/action/cooldown/spell/conjure_item/summon_pitchfork/greater,
+ /datum/action/cooldown/spell/conjure_item/summon_pitchfork/ascended,
+ /datum/action/cooldown/spell/jaunt/infernal_jaunt,
+ /datum/action/cooldown/spell/aoe/sintouch,
+ /datum/action/cooldown/spell/aoe/sintouch/ascended,
+ /datum/action/cooldown/spell/pointed/summon_contract,
+ /datum/action/cooldown/spell/conjure_item/violin,
+ /datum/action/cooldown/spell/summon_dancefloor))
var/ascendable = FALSE
/datum/antagonist/devil/can_be_owned(datum/mind/new_owner)
@@ -317,17 +322,19 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
form = ARCH_DEVIL
/datum/antagonist/devil/proc/remove_spells()
- for(var/X in owner.spell_list)
- var/obj/effect/proc_holder/spell/S = X
- if(is_type_in_typecache(S, devil_spells))
- owner.RemoveSpell(S)
+ for(var/datum/action/cooldown/spell/spells in owner.current.actions)
+ if(is_type_in_typecache(spells, devil_spells))
+ spells.Remove(owner.current)
/datum/antagonist/devil/proc/give_summon_contract()
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_contract(null))
+ var/datum/action/cooldown/spell/pointed/summon_contract/summon_contract = new(owner.current)
+ summon_contract.Grant(owner.current)
if(obligation == OBLIGATION_FIDDLE)
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/violin(null))
+ var/datum/action/cooldown/spell/conjure_item/violin/violin = new(owner.current)
+ violin.Grant(owner.current)
else if(obligation == OBLIGATION_DANCEOFF)
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_dancefloor(null))
+ var/datum/action/cooldown/spell/summon_dancefloor/dance_floor = new(owner.current)
+ dance_floor.Grant(owner.current)
/datum/antagonist/devil/proc/give_appropriate_spells()
remove_spells()
@@ -342,23 +349,41 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
give_base_spells()
/datum/antagonist/devil/proc/give_base_spells()
- owner.AddSpell(new /obj/effect/proc_holder/spell/aimed/fireball/hellish(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork(null))
+ var/datum/action/cooldown/spell/pointed/projectile/fireball/hellish/fireball = new(owner.current)
+ fireball.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/conjure_item/summon_pitchfork/pitchfork = new(owner.current)
+ pitchfork.Grant(owner.current)
/datum/antagonist/devil/proc/give_blood_spells()
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/aimed/fireball/hellish(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/infernal_jaunt(null))
+ var/datum/action/cooldown/spell/conjure_item/summon_pitchfork/pitchfork = new(owner.current)
+ pitchfork.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/pointed/projectile/fireball/hellish/fireball = new(owner.current)
+ fireball.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/jaunt/infernal_jaunt/jaunt = new(owner.current)
+ jaunt.Grant(owner.current)
/datum/antagonist/devil/proc/give_true_spells()
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork/greater(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/aimed/fireball/hellish(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/infernal_jaunt(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/sintouch(null))
+ var/datum/action/cooldown/spell/conjure_item/summon_pitchfork/greater/better_pitchfork = new(owner.current)
+ better_pitchfork.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/pointed/projectile/fireball/hellish/fireball = new(owner.current)
+ fireball.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/jaunt/infernal_jaunt/jaunt = new(owner.current)
+ jaunt.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/aoe/sintouch/sintouch = new(owner.current)
+ sintouch.Grant(owner.current)
/datum/antagonist/devil/proc/give_arch_spells()
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/summon_pitchfork/ascended(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/sintouch/ascended(null))
+ var/datum/action/cooldown/spell/conjure_item/summon_pitchfork/ascended/betterer_pitchfork = new(owner.current)
+ betterer_pitchfork.Grant(owner.current)
+
+ var/datum/action/cooldown/spell/aoe/sintouch/ascended/better_sintouch = new(owner.current)
+ better_sintouch.Grant(owner.current)
/datum/antagonist/devil/proc/beginResurrectionCheck(mob/living/body)
if(SOULVALUE>0)
@@ -515,15 +540,12 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
var/laws = list("You may not use violence to coerce someone into selling their soul.", "You may not directly and knowingly physically harm a devil, other than yourself.", GLOB.lawlorify[LAW][ban], GLOB.lawlorify[LAW][obligation], "Accomplish your objectives at all costs.")
robot_devil.set_law_sixsixsix(laws)
sleep(1 SECONDS)
- if(owner.assigned_role == "Clown" && ishuman(owner.current))
- var/mob/living/carbon/human/S = owner.current
- to_chat(S, span_notice("Your infernal nature has allowed you to overcome your clownishness."))
- S.dna.remove_mutation(CLOWNMUT)
- .=..()
+ handle_clown_mutation(owner.current, "Your infernal nature has allowed you to overcome your clownishness.")
+ . = ..()
/datum/antagonist/devil/on_removal()
to_chat(owner.current, span_userdanger("Your infernal link has been severed! You are no longer a devil!"))
- .=..()
+ . = ..()
/datum/antagonist/devil/apply_innate_effects(mob/living/mob_override)
give_appropriate_spells()
@@ -532,10 +554,9 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
.=..()
/datum/antagonist/devil/remove_innate_effects(mob/living/mob_override)
- for(var/X in owner.spell_list)
- var/obj/effect/proc_holder/spell/S = X
- if(is_type_in_typecache(S, devil_spells))
- owner.RemoveSpell(S)
+ for(var/datum/action/cooldown/spell/spells in owner.current.actions)
+ if(is_type_in_typecache(spells, devil_spells))
+ spells.Remove(owner.current)
owner.current.remove_all_languages(LANGUAGE_DEVIL)
.=..()
diff --git a/code/modules/antagonists/devil/imp/imp.dm b/code/modules/antagonists/devil/imp/imp.dm
index 28a8cb1c15f2..c35eeb7b1677 100644
--- a/code/modules/antagonists/devil/imp/imp.dm
+++ b/code/modules/antagonists/devil/imp/imp.dm
@@ -62,6 +62,7 @@
/datum/antagonist/imp
name = "Imp"
antagpanel_category = "Devil"
+ ui_name = "AntagInfoDemon"
show_in_roundend = FALSE
/datum/antagonist/imp/on_gain()
diff --git a/code/modules/antagonists/devil/sintouched/sintouched.dm b/code/modules/antagonists/devil/sintouched/sintouched.dm
index 9983e5f59943..4b0699d1d26c 100644
--- a/code/modules/antagonists/devil/sintouched/sintouched.dm
+++ b/code/modules/antagonists/devil/sintouched/sintouched.dm
@@ -10,6 +10,7 @@
name = "sintouched"
roundend_category = "sintouched"
antagpanel_category = "Devil"
+ antag_hud_name = "sintouched"
var/sin
var/static/list/sins = list(SIN_ACEDIA,SIN_GLUTTONY,SIN_GREED,SIN_SLOTH,SIN_WRATH,SIN_ENVY,SIN_PRIDE)
@@ -56,28 +57,10 @@
sin = chosen_sin
. = ..()
-/datum/antagonist/sintouched/apply_innate_effects(mob/living/mob_override)
- . = ..()
- add_hud()
-
-/datum/antagonist/sintouched/remove_innate_effects(mob/living/mob_override)
- remove_hud()
- . = ..()
-
-/datum/antagonist/sintouched/proc/add_hud()
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_SINTOUCHED]
- hud.join_hud(owner.current)
- set_antag_hud(owner.current, "sintouched")
-
-/datum/antagonist/sintouched/proc/remove_hud()
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_SINTOUCHED]
- hud.leave_hud(owner.current)
- set_antag_hud(owner.current, null)
-
#undef SIN_ACEDIA
#undef SIN_ENVY
#undef SIN_GLUTTONY
#undef SIN_GREED
#undef SIN_PRIDE
#undef SIN_SLOTH
-#undef SIN_WRATH
\ No newline at end of file
+#undef SIN_WRATH
diff --git a/code/modules/antagonists/devil/true_devil/_true_devil.dm b/code/modules/antagonists/devil/true_devil/_true_devil.dm
index fb64bcdf3991..9c036e2feb1e 100644
--- a/code/modules/antagonists/devil/true_devil/_true_devil.dm
+++ b/code/modules/antagonists/devil/true_devil/_true_devil.dm
@@ -45,7 +45,7 @@
health = maxHealth
icon_state = "arch_devil"
-/mob/living/carbon/true_devil/proc/set_name()
+/mob/living/carbon/true_devil/set_name()
var/datum/antagonist/devil/devilinfo = mind.has_antag_datum(/datum/antagonist/devil)
name = devilinfo.truename
real_name = name
@@ -57,7 +57,7 @@
mind.announce_objectives()
/mob/living/carbon/true_devil/death(gibbed)
- stat = DEAD
+ set_stat(DEAD)
..(gibbed)
drop_all_held_items()
INVOKE_ASYNC(mind.has_antag_datum(/datum/antagonist/devil), /datum/antagonist/devil/proc/beginResurrectionCheck, src)
@@ -107,7 +107,6 @@
/mob/living/carbon/true_devil/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0)
if(mind && has_bane(BANE_LIGHT))
- mind.disrupt_spells(-500)
return ..() //flashes don't stop devils UNLESS it's their bane.
/mob/living/carbon/true_devil/soundbang_act()
diff --git a/code/modules/antagonists/disease/disease_abilities.dm b/code/modules/antagonists/disease/disease_abilities.dm
index 2d4cecaa3fcb..3fcc27566723 100644
--- a/code/modules/antagonists/disease/disease_abilities.dm
+++ b/code/modules/antagonists/disease/disease_abilities.dm
@@ -166,7 +166,7 @@ new /datum/disease_ability/symptom/powerful/heal/youth
/datum/action/cooldown/disease_cough
name = "Cough"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "cough"
desc = "Force the host you are following to cough with extra force, spreading your infection to those within two meters of your host even if your transmissibility is low.
Cooldown: 10 seconds"
cooldown_time = 100
@@ -200,7 +200,7 @@ new /datum/disease_ability/symptom/powerful/heal/youth
/datum/action/cooldown/disease_sneeze
name = "Sneeze"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "sneeze"
desc = "Force the host you are following to sneeze with extra force, spreading your infection to any victims in a 4 meter cone in front of your host even if your transmissibility is low.
Cooldown: 20 seconds"
cooldown_time = 200
@@ -238,7 +238,7 @@ new /datum/disease_ability/symptom/powerful/heal/youth
/datum/action/cooldown/disease_infect
name = "Secrete Infection"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "infect"
desc = "Cause the host you are following to excrete an infective substance from their pores, causing all objects touching their skin to transmit your infection to anyone who touches them for the next 30 seconds.
Cooldown: 40 seconds"
cooldown_time = 400
diff --git a/code/modules/antagonists/disease/disease_mob.dm b/code/modules/antagonists/disease/disease_mob.dm
index 6ced4aeaca29..73a5e8d4a55c 100644
--- a/code/modules/antagonists/disease/disease_mob.dm
+++ b/code/modules/antagonists/disease/disease_mob.dm
@@ -63,7 +63,7 @@ the new instance inside the host to be updated to the template's stats.
SSdisease.archive_diseases[disease_template.GetDiseaseID()] = disease_template //important for stuff that uses disease IDs
var/datum/atom_hud/my_hud = GLOB.huds[DATA_HUD_SENTIENT_DISEASE]
- my_hud.add_hud_to(src)
+ my_hud.show_to(src)
browser = new /datum/browser(src, "disease_menu", "Adaptation Menu", 1000, 770, src)
@@ -229,7 +229,7 @@ the new instance inside the host to be updated to the template's stats.
MA.alpha = 200
holder.appearance = MA
var/datum/atom_hud/my_hud = GLOB.huds[DATA_HUD_SENTIENT_DISEASE]
- my_hud.add_to_hud(V.affected_mob)
+ my_hud.add_atom_to_hud(V.affected_mob)
to_chat(src, span_notice("A new host, [V.affected_mob.real_name], has been infected."))
@@ -245,7 +245,7 @@ the new instance inside the host to be updated to the template's stats.
to_chat(src, span_notice("One of your hosts, [V.affected_mob.real_name], has been purged of your infection."))
var/datum/atom_hud/my_hud = GLOB.huds[DATA_HUD_SENTIENT_DISEASE]
- my_hud.remove_from_hud(V.affected_mob)
+ my_hud.remove_atom_from_hud(V.affected_mob)
if(following_host == V.affected_mob)
follow_next()
@@ -399,7 +399,7 @@ the new instance inside the host to be updated to the template's stats.
/datum/action/innate/disease_adapt
name = "Adaptation Menu"
- icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "disease_menu"
/datum/action/innate/disease_adapt/Activate()
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm
index 40f07a8f14a2..a40d06412742 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm
@@ -4,7 +4,9 @@
antagpanel_category = "Heretic"
antag_moodlet = /datum/mood_event/heretics
job_rank = ROLE_HERETIC
+ antag_hud_name = "heretic"
can_hijack = HIJACK_HIJACKER
+ show_to_ghosts = TRUE
preview_outfit = /datum/outfit/heretic
var/give_equipment = TRUE
var/list/researched_knowledge = list()
@@ -20,11 +22,11 @@
var/tier_counter = 0
///list of knowledges available, by path. every odd tier is an exclusive upgrade, and every even one is a set of upgrades of which 3 need to be picked to move on.
var/list/knowledges = list( TIER_PATH = list(/datum/eldritch_knowledge/base_ash, /datum/eldritch_knowledge/base_flesh, /datum/eldritch_knowledge/base_rust),
- TIER_1 = list(/datum/eldritch_knowledge/ashen_shift, /datum/eldritch_knowledge/ashen_eyes, /datum/eldritch_knowledge/flesh_ghoul, /datum/eldritch_knowledge/rust_regen, /datum/eldritch_knowledge/armor, /datum/eldritch_knowledge/essence),
+ TIER_1 = list(/datum/eldritch_knowledge/spell/ashen_shift, /datum/eldritch_knowledge/ashen_eyes, /datum/eldritch_knowledge/flesh_ghoul, /datum/eldritch_knowledge/rust_regen, /datum/eldritch_knowledge/armor, /datum/eldritch_knowledge/essence),
TIER_MARK = list(/datum/eldritch_knowledge/ash_mark, /datum/eldritch_knowledge/flesh_mark, /datum/eldritch_knowledge/rust_mark),
- TIER_2 = list(/datum/eldritch_knowledge/blindness, /datum/eldritch_knowledge/corrosion, /datum/eldritch_knowledge/paralysis, /datum/eldritch_knowledge/raw_prophet, /datum/eldritch_knowledge/blood_siphon, /datum/eldritch_knowledge/area_conversion),
+ TIER_2 = list(/datum/eldritch_knowledge/blindness, /datum/eldritch_knowledge/corrosion, /datum/eldritch_knowledge/paralysis, /datum/eldritch_knowledge/raw_prophet, /datum/eldritch_knowledge/spell/blood_siphon, /datum/eldritch_knowledge/spell/area_conversion),
TIER_BLADE = list(/datum/eldritch_knowledge/ash_blade_upgrade, /datum/eldritch_knowledge/flesh_blade_upgrade, /datum/eldritch_knowledge/rust_blade_upgrade),
- TIER_3 = list(/datum/eldritch_knowledge/flame_birth, /datum/eldritch_knowledge/cleave, /datum/eldritch_knowledge/stalker, /datum/eldritch_knowledge/ashy, /datum/eldritch_knowledge/rusty, /datum/eldritch_knowledge/entropic_plume),
+ TIER_3 = list(/datum/eldritch_knowledge/spell/flame_birth, /datum/eldritch_knowledge/spell/cleave, /datum/eldritch_knowledge/stalker, /datum/eldritch_knowledge/ashy, /datum/eldritch_knowledge/rusty, /datum/eldritch_knowledge/spell/entropic_plume),
TIER_ASCEND = list(/datum/eldritch_knowledge/ash_final, /datum/eldritch_knowledge/flesh_final, /datum/eldritch_knowledge/rust_final))
/datum/antagonist/heretic/admin_add(datum/mind/new_owner,mob/admin)
@@ -65,14 +67,12 @@
return finish_preview_icon(icon)
/datum/antagonist/heretic/on_gain()
- var/mob/living/current = owner.current
- if(ishuman(current))
+ if(ishuman(owner.current))
forge_primary_objectives()
- gain_knowledge(/datum/eldritch_knowledge/basic)
- current.log_message("has been made a student of the Mansus!", LOG_ATTACK, color="#960000")
+ gain_knowledge(/datum/eldritch_knowledge/spell/basic)
+ owner.current.log_message("has been made a student of the Mansus!", LOG_ATTACK, color="#960000")
GLOB.reality_smash_track.AddMind(owner)
START_PROCESSING(SSprocessing,src)
- SSticker.mode.update_heretic_icons_added(owner)
if(give_equipment)
equip_cultist()
return ..()
@@ -89,8 +89,6 @@
GLOB.reality_smash_track.RemoveMind(owner)
STOP_PROCESSING(SSprocessing,src)
- SSticker.mode.update_heretic_icons_removed(owner)
-
return ..()
@@ -161,12 +159,7 @@
var/mob/living/current = owner.current
if(mob_override)
current = mob_override
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/traitor_mob = owner.current
- if(traitor_mob && istype(traitor_mob))
- if(!silent)
- to_chat(traitor_mob, "Your knowledge allow you to overcome your clownish nature, allowing you to wield weapons with impunity.")
- traitor_mob.dna.remove_mutation(CLOWNMUT)
+ handle_clown_mutation(current, "Ancient knowledge described to you has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
current.faction |= "heretics"
/datum/antagonist/heretic/remove_innate_effects(mob/living/mob_override)
@@ -174,9 +167,6 @@
var/mob/living/current = owner.current
if(mob_override)
current = mob_override
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/traitor_mob = owner.current
- traitor_mob.dna.add_mutation(CLOWNMUT)
current.faction -= "heretics"
/datum/antagonist/heretic/get_admin_commands()
@@ -485,10 +475,11 @@
// Knowledge //
////////////////
-/datum/antagonist/heretic/proc/gain_knowledge(datum/eldritch_knowledge/EK, forced = FALSE)
- if(get_knowledge(EK))
+/datum/antagonist/heretic/proc/gain_knowledge(datum/eldritch_knowledge/knowledge_type, forced = FALSE)
+ if(!ispath(knowledge_type))
+ stack_trace("[type] gain_knowledge was given an invalid path! (Got: [knowledge_type])")
return FALSE
- var/datum/eldritch_knowledge/initialized_knowledge = new EK
+ var/datum/eldritch_knowledge/initialized_knowledge = new knowledge_type()
researched_knowledge[initialized_knowledge.type] = initialized_knowledge
initialized_knowledge.on_gain(owner.current)
charge -= initialized_knowledge.cost
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
index cb744e908960..ef3a6cec9297 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
@@ -213,7 +213,7 @@
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker)
if(IS_HERETIC(human_user))
to_chat(human_user, span_boldwarning("You know better than to tempt forces out of your control!"))
- if(IS_BLOODSUCKER(human_user) || bloodsuckerdatum.my_clan == CLAN_LASOMBRA)
+ if(IS_BLOODSUCKER(human_user) || bloodsuckerdatum.my_clan?.get_clan() == CLAN_LASOMBRA)
to_chat(human_user, span_boldwarning("This shard has already been harvested!"))
else
var/obj/item/bodypart/arm = human_user.get_active_hand()
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
index c4a0283b4f4e..67ab9c857c27 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_items.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
@@ -60,10 +60,10 @@
/datum/action/innate/heretic_shatter
name = "Shattering Offer"
desc = "Smash your blade to release the entropic energies within it, teleporting you out of danger."
- background_icon_state = "bg_ecult"
+ background_icon_state = "bg_heretic"
button_icon_state = "shatter"
- icon_icon = 'icons/mob/actions/actions_ecult.dmi'
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE
var/mob/living/carbon/human/holder
var/obj/item/gun/magic/hook/sickly_blade/sword
@@ -73,7 +73,7 @@
//i know what im doing
return ..()
-/datum/action/innate/heretic_shatter/IsAvailable()
+/datum/action/innate/heretic_shatter/IsAvailable(feedback = FALSE)
if(IS_HERETIC(holder) || IS_HERETIC_MONSTER(holder))
return TRUE
else
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm
index 5e614b585de2..2641b568eb7d 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm
@@ -23,8 +23,6 @@
var/route = PATH_SIDE
///transmutation recipes unlocked by this knowledge
var/list/unlocked_transmutations = list()
- ///spells unlocked by this knowledge
- var/list/spells_to_add = list()
/** The Lores and their Thematic Representation
*
@@ -42,25 +40,14 @@
*
* This proc is called whenever a new eldritch knowledge is added to an antag datum
*/
-/datum/eldritch_knowledge/proc/on_gain(mob/user)
- to_chat(user, span_warning("[gain_text]"))
- for(var/S in spells_to_add)
- var/obj/effect/proc_holder/spell/spell2add = new S
- user.mind.AddSpell(spell2add)
- var/datum/antagonist/heretic/EC = user.mind?.has_antag_datum(/datum/antagonist/heretic)
- for(var/X in unlocked_transmutations)
- var/datum/eldritch_transmutation/ET = new X
- EC.transmutations |= ET
+/datum/eldritch_knowledge/proc/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
return
/**
* What happens when you lose this
*
* This proc is called whenever antagonist looses his antag datum, put cleanup code in here
*/
-/datum/eldritch_knowledge/proc/on_lose(mob/user)
- for(var/S in spells_to_add)
- var/obj/effect/proc_holder/spell/spell2remove = S
- user.mind.RemoveSpell(spell2remove)
+/datum/eldritch_knowledge/proc/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
return
/**
* What happens every tick
@@ -70,6 +57,31 @@
/datum/eldritch_knowledge/proc/on_life(mob/user)
return
+/**
+ * A knowledge subtype that grants the heretic a certain spell.
+ */
+/datum/eldritch_knowledge/spell
+ /// Spell path we add to the heretic. Type-path.
+ var/datum/action/cooldown/spell/spell_to_add
+ /// The spell we actually created.
+ var/datum/weakref/created_spell_ref
+
+/datum/eldritch_knowledge/spell/Destroy()
+ QDEL_NULL(created_spell_ref)
+ return ..()
+
+/datum/eldritch_knowledge/spell/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
+ // Added spells are tracked on the body, and not the mind,
+ // because we handle heretic mind transfers
+ // via the antag datum (on_gain and on_lose).
+ var/datum/action/cooldown/spell/created_spell = created_spell_ref?.resolve() || new spell_to_add(user)
+ created_spell.Grant(user)
+ created_spell_ref = WEAKREF(created_spell)
+
+/datum/eldritch_knowledge/spell/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
+ var/datum/action/cooldown/spell/created_spell = created_spell_ref?.resolve()
+ created_spell?.Remove(user)
+
/**
@@ -93,11 +105,11 @@
///Base lore///
///////////////
-/datum/eldritch_knowledge/basic
+/datum/eldritch_knowledge/spell/basic
name = "Break of Dawn"
desc = "Begins your journey in the Mansus. Allows you to select a target transmuting a living heart on a transmutation rune, create new living hearts by transmuting a heart, poppy, and pool of blood, and create new Codex Cicatrixes by transmuting human skin, a bible, a poppy and a pen."
gain_text = "Gates to the Mansus open in your mind's passion."
cost = 0
- spells_to_add = list(/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp)
+ spell_to_add = /datum/action/cooldown/spell/touch/mansus_grasp
unlocked_transmutations = list(/datum/eldritch_transmutation/basic, /datum/eldritch_transmutation/living_heart, /datum/eldritch_transmutation/codex_cicatrix)
route = "Start"
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
index 68fa77724364..edc80de72812 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
@@ -1,174 +1,235 @@
-/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash
name = "Ashen Passage"
- desc = "Grants a short period of incorporeality, allowing passage through walls and other obstacles."
- school = "transmutation"
- charge_max = 150
- range = -1
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "ash_shift"
- action_background_icon_state = "bg_ecult"
- jaunt_in_time = 13
- jaunt_duration = 10
+ desc = "A short range spell that allows you to pass unimpeded through walls."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "ash_shift"
+ sound = null
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 15 SECONDS
+
+ invocation = "ASH'N P'SSG'"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
+ exit_jaunt_sound = null
+ jaunt_duration = 1.1 SECONDS
+ jaunt_in_time = 1.3 SECONDS
+ jaunt_out_time = 0.6 SECONDS
jaunt_in_type = /obj/effect/temp_visual/dir_setting/ash_shift
jaunt_out_type = /obj/effect/temp_visual/dir_setting/ash_shift/out
-/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/long
- jaunt_duration = 50
-
-/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/play_sound()
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/do_steam_effects()
return
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/long
+ name = "Ashen Walk"
+ desc = "A long range spell that allows you pass unimpeded through multiple walls."
+ jaunt_duration = 5 SECONDS
+
/obj/effect/temp_visual/dir_setting/ash_shift
name = "ash_shift"
icon = 'icons/mob/mob.dmi'
icon_state = "ash_shift2"
- duration = 13
+ duration = 1.3 SECONDS
/obj/effect/temp_visual/dir_setting/ash_shift/out
icon_state = "ash_shift"
-/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp
+/datum/action/cooldown/spell/touch/mansus_grasp
name = "Mansus Grasp"
desc = "A powerful combat initiation spell that deals massive stamina damage. It may have other effects if you continue your research..."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "mansus_grasp"
+ sound = 'sound/items/welder.ogg'
+
+ school = SCHOOL_EVOCATION
+ cooldown_time = 10 SECONDS
+
+ invocation = "R'CH T'H TR'TH!"
+ invocation_type = INVOCATION_SHOUT
+ // Mimes can cast it. Chaplains can cast it. Anyone can cast it, so long as they have a hand.
+ spell_requirements = SPELL_CASTABLE_WITHOUT_INVOCATION
+
hand_path = /obj/item/melee/touch_attack/mansus_fist
- school = "evocation"
- charge_max = 150
- clothes_req = FALSE
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "mansus_grasp"
- action_background_icon_state = "bg_ecult"
+
+/datum/action/cooldown/spell/touch/mansus_grasp/is_valid_target(atom/cast_on)
+ return TRUE // This baby can hit anything
+
+/datum/action/cooldown/spell/touch/mansus_grasp/can_cast_spell(feedback = TRUE)
+ return ..() && !!IS_HERETIC(owner)
+
+
+/datum/action/cooldown/spell/touch/mansus_grasp/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
+ if(!isliving(victim))
+ return FALSE
+
+ var/mob/living/living_hit = victim
+ if(living_hit.can_block_magic(antimagic_flags))
+ victim.visible_message(
+ span_danger("The spell bounces off of [victim]!"),
+ span_danger("The spell bounces off of you!"),
+ )
+ return FALSE
+
+// if(SEND_SIGNAL(caster, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, victim) & COMPONENT_BLOCK_HAND_USE)
+// return FALSE
+
+ living_hit.apply_damage(10, BRUTE, wound_bonus = CANT_WOUND)
+ if(iscarbon(victim))
+ var/mob/living/carbon/carbon_hit = victim
+ carbon_hit.adjust_timed_status_effect(4 SECONDS, /datum/status_effect/speech/slurring/heretic)
+ carbon_hit.AdjustKnockdown(5 SECONDS)
+ carbon_hit.adjustStaminaLoss(80)
+
+ return TRUE
/obj/item/melee/touch_attack/mansus_fist
- name = "Mansus grasp"
- desc = "A sinister looking aura that distorts the flow of reality around it. Knocks the target down and deals a large amount of stamina damage alongside a small amount of brute. It may gain more interesting capabilities if you continue your research..."
- icon_state = "disintegrate"
- item_state = "disintegrate"
- catchphrase = "FEAR THE BEYOND"
+ name = "Mansus Grasp"
+ desc = "A sinister looking aura that distorts the flow of reality around it. \
+ Causes knockdown, minor bruises, and major stamina damage. \
+ It gains additional beneficial effects as you expand your knowledge of the Mansus."
+ icon_state = "mansus"
+ item_state = "mansus"
/obj/item/melee/touch_attack/mansus_fist/ignition_effect(atom/A, mob/user)
. = span_notice("[user] effortlessly snaps [user.p_their()] fingers near [A], igniting it with eldritch energies. Fucking badass!")
qdel(src)
-/obj/item/melee/touch_attack/mansus_fist/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
-
- if(!proximity_flag || target == user)
- return
- if(ishuman(target))
- var/mob/living/carbon/human/tar = target
- if(tar.anti_magic_check())
- tar.visible_message(span_danger("Strange energies from [user]'s hand fly at [target] at an impossible velocity, but vanish before making contact!"),span_danger("Strange energies from [user]'s hand begin to crash at an impossible speed towards you, but vanish before they make contact!"))
- return ..()
- var/datum/mind/M = user.mind
- var/datum/antagonist/heretic/cultie = M.has_antag_datum(/datum/antagonist/heretic)
-
- var/use_charge = FALSE
- if(iscarbon(target))
- use_charge = TRUE
- var/mob/living/carbon/C = target
- C.adjustBruteLoss(10)
- C.AdjustKnockdown(5 SECONDS)
- C.adjustStaminaLoss(80)
- C.silent += 5
- var/list/knowledge = cultie.get_all_knowledge()
-
- for(var/X in knowledge)
- var/datum/eldritch_knowledge/EK = knowledge[X]
- if(EK.on_mansus_grasp(target, user, proximity_flag, click_parameters))
- use_charge = TRUE
- if(use_charge)
- playsound(user, 'sound/items/welder.ogg', 75, TRUE)
- return ..()
-
-/obj/effect/proc_holder/spell/aoe_turf/rust_conversion
+/datum/action/cooldown/spell/aoe/rust_conversion
name = "Aggressive Spread"
desc = "Spread rust onto nearby turfs, possibly destroying rusted walls."
- school = "transmutation"
- charge_max = 300 //twice as long as mansus grasp
- clothes_req = FALSE
- invocation = "RUST TO RUST"
- invocation_type = SPELL_INVOCATION_WHISPER
- range = 3
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "corrode"
- action_background_icon_state = "bg_ecult"
-
-/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/cast(list/targets, mob/user = usr)
- playsound(user, 'sound/items/welder.ogg', 75, TRUE)
- for(var/turf/T in targets)
- ///What we want is the 3 tiles around the user and the tile under him to be rusted, so min(dist,1)-1 causes us to get 0 for these tiles, rest of the tiles are based on chance
- var/chance = 100 - (max(get_dist(T,user),1)-1)*100/(range+1)
- if(!prob(chance))
- continue
- T.rust_heretic_act()
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "corrode"
+ sound = 'sound/items/welder.ogg'
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 30 SECONDS
+
+ invocation = "A'GRSV SPR'D"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
+ aoe_radius = 3
+
-/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/small
+/datum/action/cooldown/spell/aoe/rust_conversion/get_things_to_cast_on(atom/center)
+ var/list/things = list()
+ for(var/turf/nearby_turf in range(aoe_radius, center))
+ things += nearby_turf
+
+ return things
+
+/datum/action/cooldown/spell/aoe/rust_conversion/cast_on_thing_in_aoe(turf/victim, atom/caster)
+ // We have less chance of rusting stuff that's further
+ var/distance_to_caster = get_dist(victim, caster)
+ var/chance_of_not_rusting = (max(distance_to_caster, 1) - 1) * 100 / (aoe_radius + 1)
+
+ if(prob(chance_of_not_rusting))
+ return
+
+ victim.rust_heretic_act()
+
+/datum/action/cooldown/spell/aoe/rust_conversion/small
name = "Rust Conversion"
- desc = "Spreads rust onto nearby turfs."
- range = 2
+ desc = "Spreads rust onto nearby surfaces."
+ aoe_radius = 2
-/obj/effect/proc_holder/spell/targeted/touch/blood_siphon
+/datum/action/cooldown/spell/pointed/blood_siphon
name = "Blood Siphon"
- desc = "Engulfs your arm in draining energies, absorbing the health of the first human-like being struck to heal yourself."
- hand_path = /obj/item/melee/touch_attack/blood_siphon
- school = "evocation"
- charge_max = 150
- clothes_req = FALSE
- invocation = "ETERNAL FLAMES"
- invocation_type = SPELL_INVOCATION_WHISPER
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "blood_siphon"
- action_background_icon_state = "bg_ecult"
-
-/obj/item/melee/touch_attack/blood_siphon
- name = "blood siphon"
- desc = "A sinister looking aura that distorts the flow of reality around it. It looks hungry..."
- icon_state = "disintegrate"
- item_state = "disintegrate"
- catchphrase = "REGENERATE ME"
-
-/obj/item/melee/touch_attack/blood_siphon/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
- if(!proximity_flag)
- return
- playsound(user, 'sound/magic/demon_attack1.ogg', 75, TRUE)
- if(iscarbon(target))
- var/mob/living/carbon/C1 = target
- if(C1.anti_magic_check())
- C1.visible_message(span_danger("The energies from [user]'s hand jump at [target], but are dispersed!"),span_danger("Something jumps off of [user]'s hand, but it disperses on contact with you!"))
- return ..()
- var/mob/living/carbon/C2 = user
- for(var/obj/item/bodypart/bodypart in C2.bodyparts)
- for(var/i in bodypart.wounds)
- var/datum/wound/iter_wound = i
- if(prob(50))
- continue
- var/obj/item/bodypart/target_bodypart = locate(bodypart.type) in C1.bodyparts
- if(!target_bodypart)
- continue
- iter_wound.remove_wound()
- iter_wound.apply_wound(target_bodypart)
- if(isliving(target))
- var/mob/living/L = target
- L.adjustBruteLoss(20)
- C2.adjustBruteLoss(-20)
- C1.blood_volume -= 20
- if(C2.blood_volume < BLOOD_VOLUME_MAXIMUM(C2)) //we dont want to explode after all
- C2.blood_volume += 20
- return ..()
-
-/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave
+ desc = "A touch spell that heals your wounds while damaging the enemy. \
+ It has a chance to transfer wounds between you and your enemy."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "blood_siphon"
+ ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 15 SECONDS
+
+ invocation = "FL'MS O'ET'RN'ITY"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
+ cast_range = 6
+
+/datum/action/cooldown/spell/pointed/blood_siphon/can_cast_spell(feedback = TRUE)
+ return ..() && isliving(owner)
+
+/datum/action/cooldown/spell/pointed/blood_siphon/is_valid_target(atom/cast_on)
+ return ..() && isliving(cast_on)
+
+/datum/action/cooldown/spell/pointed/blood_siphon/cast(mob/living/cast_on)
+ . = ..()
+ playsound(owner, 'sound/magic/demon_attack1.ogg', 75, TRUE)
+ if(cast_on.can_block_magic())
+ owner.balloon_alert(owner, "spell blocked!")
+ cast_on.visible_message(
+ span_danger("The spell bounces off of [cast_on]!"),
+ span_danger("The spell bounces off of you!"),
+ )
+ return FALSE
+
+ cast_on.visible_message(
+ span_danger("[cast_on] turns pale as a red glow envelops [cast_on.p_them()]!"),
+ span_danger("You pale as a red glow enevelops you!"),
+ )
+
+ var/mob/living/living_owner = owner
+ cast_on.adjustBruteLoss(20)
+ living_owner.adjustBruteLoss(-20)
+
+ if(!cast_on.blood_volume || !living_owner.blood_volume)
+ return TRUE
+
+ cast_on.blood_volume -= 20
+ if(living_owner.blood_volume < BLOOD_VOLUME_MAXIMUM(living_owner)) // we dont want to explode from casting
+ living_owner.blood_volume += 20
+
+ if(!iscarbon(cast_on) || !iscarbon(owner))
+ return TRUE
+
+ var/mob/living/carbon/carbon_target = cast_on
+ var/mob/living/carbon/carbon_user = owner
+ for(var/obj/item/bodypart/bodypart as anything in carbon_user.bodyparts)
+ for(var/datum/wound/iter_wound as anything in bodypart.wounds)
+ if(prob(50))
+ continue
+ var/obj/item/bodypart/target_bodypart = locate(bodypart.type) in carbon_target.bodyparts
+ if(!target_bodypart)
+ continue
+ iter_wound.remove_wound()
+ iter_wound.apply_wound(target_bodypart)
+
+ return TRUE
+
+// Shoots a straight line of rusty stuff ahead of the caster, what rust monsters get
+/datum/action/cooldown/spell/basic_projectile/rust_wave
name = "Patron's Reach"
desc = "Fire a rust spreading projectile in front of you, dealing toxin damage to whatever it hits."
- proj_type = /obj/item/projectile/magic/spell/rust_wave
- charge_max = 350
- clothes_req = FALSE
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "rust_wave"
- action_background_icon_state = "bg_ecult"
- invocation = "FACE INEVITABILITY"
- invocation_type = SPELL_INVOCATION_WHISPER
-
-/obj/item/projectile/magic/spell/rust_wave
- name = "patron's reach"
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "rust_wave"
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 35 SECONDS
+
+ invocation = "SPR'D TH' WO'D"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
+ projectile_type = /obj/item/projectile/magic/aoe/rust_wave
+
+/obj/item/projectile/magic/aoe/rust_wave
+ name = "Patron's Reach"
icon_state = "eldritch_projectile"
alpha = 180
damage = 30
@@ -179,7 +240,7 @@
range = 15
speed = 1
-/obj/item/projectile/magic/spell/rust_wave/Moved(atom/OldLoc, Dir)
+/obj/item/projectile/magic/aoe/rust_wave/Moved(atom/OldLoc, Dir)
. = ..()
playsound(src, 'sound/items/welder.ogg', 75, TRUE)
var/list/turflist = list()
@@ -197,132 +258,138 @@
var/turf/T = X
T.rust_heretic_act()
-/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave/short
- name = "Small Patron's Reach"
- proj_type = /obj/item/projectile/magic/spell/rust_wave/short
+/datum/action/cooldown/spell/basic_projectile/rust_wave/short
+ name = "Lesser Patron's Reach"
+ projectile_type = /obj/item/projectile/magic/aoe/rust_wave/short
-/obj/item/projectile/magic/spell/rust_wave/short
+/obj/item/projectile/magic/aoe/rust_wave/short
range = 7
speed = 2
-/obj/effect/proc_holder/spell/pointed/cleave
+/datum/action/cooldown/spell/pointed/cleave
name = "Cleave"
- desc = "Causes a target and those around them to be inflicted with severe bleeding"
- school = "transmutation"
- charge_max = 350
- clothes_req = FALSE
- invocation = "RIP AND TEAR"
- invocation_type = SPELL_INVOCATION_WHISPER
- range = 9
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "cleave"
- action_background_icon_state = "bg_ecult"
-
-/obj/effect/proc_holder/spell/pointed/cleave/cast(list/targets, mob/user)
- if(!targets.len)
- to_chat(user, span_warning("No target found in range!"))
- return FALSE
- if(!can_target(targets[1], user))
- return FALSE
+ desc = "Causes severe bleeding on a target and people around them"
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "cleave"
+ ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 35 SECONDS
- for(var/mob/living/carbon/human/C in range(1,targets[1]))
- targets |= C
+ invocation = "CL'VE"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
- for(var/X in targets)
- var/mob/living/carbon/human/target = X
- if(target == user)
+ cast_range = 4
+
+ /// The radius of the cleave effect
+ var/cleave_radius = 1
+ /// What type of wound we apply
+ var/wound_type = /datum/wound/slash/critical/cleave
+
+/datum/action/cooldown/spell/pointed/cleave/is_valid_target(atom/cast_on)
+ return ..() && ishuman(cast_on)
+
+/datum/action/cooldown/spell/pointed/cleave/cast(mob/living/carbon/human/cast_on)
+ . = ..()
+ for(var/mob/living/carbon/human/victim in range(cleave_radius, cast_on))
+ if(victim == owner || IS_HERETIC(victim) || IS_HERETIC_MONSTER(victim))
continue
- if(target.anti_magic_check())
- to_chat(user, span_warning("The spell had no effect!"))
- target.visible_message(span_danger("[target]'s veins emit a dull glow, but their magic protection repulses the blaze!"), \
- span_danger("You see a dull glow and feel a faint prickling sensation in your veins, but your magic protection prevents ignition!"))
+ if(victim.can_block_magic(antimagic_flags))
+ victim.visible_message(
+ span_danger("[victim]'s flashes in a firey glow, but repels the blaze!"),
+ span_danger("Your body begins to flash a firey glow, but you are protected!!")
+ )
continue
- target.visible_message(span_danger("[target]'s veins are shredded from within as an unholy blaze erupts from their blood!"), \
- span_danger("You feel your skin scald as superheated blood bursts from your veins!"))
- var/obj/item/bodypart/bodypart = pick(target.bodyparts)
- var/datum/wound/slash/critical/crit_wound = new
+ if(!victim.blood_volume)
+ continue
+
+ victim.visible_message(
+ span_danger("[victim]'s veins are shredded from within as an unholy blaze erupts from [victim.p_their()] blood!"),
+ span_danger("Your veins burst from within and unholy flame erupts from your blood!")
+ )
+
+ var/obj/item/bodypart/bodypart = pick(victim.bodyparts)
+ var/datum/wound/slash/crit_wound = new wound_type()
crit_wound.apply_wound(bodypart)
- target.adjustFireLoss(20)
- new /obj/effect/temp_visual/cleave(target.drop_location())
+ victim.apply_damage(20, BURN, wound_bonus = CANT_WOUND)
+
+ new /obj/effect/temp_visual/cleave(get_turf(victim))
-/obj/effect/proc_holder/spell/pointed/cleave/can_target(atom/target, mob/user, silent)
- . = ..()
- if(!.)
- return FALSE
- if(!istype(target,/mob/living/carbon/human))
- if(!silent)
- to_chat(user, span_warning("You are unable to cleave [target]!"))
- return FALSE
return TRUE
-/obj/effect/proc_holder/spell/pointed/cleave/long
- charge_max = 650
+/datum/action/cooldown/spell/pointed/cleave/long
+ name = "Deeper Cleave"
+ cooldown_time = 65 SECONDS
-/obj/effect/proc_holder/spell/pointed/touch/mad_touch
+// Currently unused.
+/datum/action/cooldown/spell/touch/mad_touch
name = "Touch of Madness"
- desc = "Strange energies engulf your hand; you feel even the sight of them would cause a headache if you didn't understand them."
- school = "transmutation"
- charge_max = 150
- clothes_req = FALSE
- invocation_type = SPELL_INVOCATION_NONE
- range = 2
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "mad_touch"
- action_background_icon_state = "bg_ecult"
-
-/obj/effect/proc_holder/spell/pointed/touch/mad_touch/can_target(atom/target, mob/user, silent)
- . = ..()
- if(!.)
+ desc = "Strange energies engulf your hand, you feel even the sight of them would cause a headache if you didn't understand them."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "mad_touch"
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 15 SECONDS
+ invocation_type = INVOCATION_NONE
+ spell_requirements = NONE
+ antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
+
+/datum/action/cooldown/spell/touch/mad_touch/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
+ if(!ishuman(victim))
+ return FALSE
+
+ var/mob/living/carbon/human/human_victim = victim
+ if(!human_victim.mind || IS_HERETIC(human_victim))
return FALSE
- if(!istype(target,/mob/living/carbon/human))
- if(!silent)
- to_chat(user, span_warning("You are unable to touch [target]!"))
+
+ if(human_victim.can_block_magic(antimagic_flags))
+ victim.visible_message(
+ span_danger("The spell bounces off of [victim]!"),
+ span_danger("The spell bounces off of you!"),
+ )
return FALSE
+
+ to_chat(caster, span_warning("[human_victim.name] has been cursed!"))
+ SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus)
return TRUE
-/obj/effect/proc_holder/spell/pointed/touch/mad_touch/cast(list/targets, mob/user)
- . = ..()
- for(var/mob/living/carbon/target in targets)
- if(ishuman(targets))
- var/mob/living/carbon/human/tar = target
- if(tar.anti_magic_check())
- tar.visible_message(span_danger("The energies from [user]'s hand slide onto [target], but quickly fall off and vanish!"),span_danger("Something slides onto you from [user]'s hand, but it can't maintain contact and vanishes as it falls away!"))
- return
- if(target.mind && !target.mind.has_antag_datum(/datum/antagonist/heretic))
- to_chat(user,span_warning("[target.name] has been cursed!"))
- SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus)
-
-/obj/effect/proc_holder/spell/pointed/ash_final
+// Currently unused - releases streams of fire around the caster.
+/datum/action/cooldown/spell/pointed/ash_beams
name = "Nightwatcher's Rite"
- desc = "Fires five blasts of fire in angles away from you, dealing heavy damage to anything they hit."
- school = "transmutation"
- invocation = "IGNITE"
- invocation_type = SPELL_INVOCATION_WHISPER
- charge_max = 300
- range = 15
- clothes_req = FALSE
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "flames"
- action_background_icon_state = "bg_ecult"
-
-/obj/effect/proc_holder/spell/pointed/ash_final/cast(list/targets, mob/user)
- for(var/X in targets)
- var/T
- T = line_target(-25, range, X, user)
- INVOKE_ASYNC(src, PROC_REF(fire_line), user,T)
- T = line_target(10, range, X, user)
- INVOKE_ASYNC(src, PROC_REF(fire_line), user,T)
- T = line_target(0, range, X, user)
- INVOKE_ASYNC(src, PROC_REF(fire_line), user,T)
- T = line_target(-10, range, X, user)
- INVOKE_ASYNC(src, PROC_REF(fire_line), user,T)
- T = line_target(25, range, X, user)
- INVOKE_ASYNC(src, PROC_REF(fire_line), user,T)
- return ..()
+ desc = "A powerful spell that releases five streams of eldritch fire towards the target."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "flames"
+ ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
+
+ school = SCHOOL_FORBIDDEN
+ invocation = "F'RE"
+ invocation_type = INVOCATION_WHISPER
+
+ cooldown_time = 30 SECONDS
+ spell_requirements = NONE
+
+ /// The length of the flame line spit out.
+ var/flame_line_length = 15
+
+/datum/action/cooldown/spell/pointed/ash_beams/is_valid_target(atom/cast_on)
+ return TRUE
+
+/datum/action/cooldown/spell/pointed/ash_beams/cast(atom/target)
+ . = ..()
+ var/static/list/offsets = list(-25, -10, 0, 10, 25)
+ for(var/offset in offsets)
+ INVOKE_ASYNC(src, PROC_REF(fire_line), owner, line_target(offset, flame_line_length, target, owner))
-/obj/effect/proc_holder/spell/pointed/ash_final/proc/line_target(offset, range, atom/at , atom/user)
+/datum/action/cooldown/spell/pointed/ash_beams/proc/line_target(offset, range, atom/at, atom/user)
if(!at)
return
var/angle = ATAN2(at.x - user.x, at.y - user.y) + offset
@@ -334,7 +401,7 @@
T = check
return (getline(user, T) - get_turf(user))
-/obj/effect/proc_holder/spell/pointed/ash_final/proc/fire_line(atom/source, list/turfs)
+/datum/action/cooldown/spell/pointed/ash_beams/proc/fire_line(atom/source, list/turfs)
var/list/hit_list = list()
for(var/turf/T in turfs)
if(istype(T, /turf/closed))
@@ -360,120 +427,166 @@
M.take_damage(45, BURN, MELEE, 1)
sleep(0.15 SECONDS)
-/obj/effect/proc_holder/spell/targeted/shapeshift/eldritch
- invocation = "BEND MY FORM"
- invocation_type = SPELL_INVOCATION_WHISPER
- clothes_req = FALSE
- action_background_icon_state = "bg_ecult"
- possible_shapes = list(/mob/living/simple_animal/mouse,\
+/datum/action/cooldown/spell/shapeshift/eldritch
+ name = "Shapechange"
+ desc = "A spell that allows you to take on the form of another creature, gaining their abilities. \
+ After making your choice, you will be unable to change to another."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+
+ school = SCHOOL_FORBIDDEN
+ invocation = "SH'PE"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
+ possible_shapes = list(
+ /mob/living/simple_animal/mouse,\
/mob/living/simple_animal/pet/dog/corgi,\
/mob/living/simple_animal/hostile/carp/megacarp,\
/mob/living/simple_animal/pet/fox,\
/mob/living/simple_animal/hostile/netherworld/migo,\
/mob/living/simple_animal/bot/medbot,\
- /mob/living/simple_animal/pet/cat )
+ /mob/living/simple_animal/pet/cat
+ )
-/obj/effect/proc_holder/spell/targeted/emplosion/eldritch
+/datum/action/cooldown/spell/emp/eldritch
name = "Entropic Pulse"
- invocation = "ENTROPIC PULSE"
- invocation_type = SPELL_INVOCATION_WHISPER
- clothes_req = FALSE
- action_background_icon_state = "bg_ecult"
- range = -1
- include_user = TRUE
- charge_max = 300
+ desc = "A spell that causes a large EMP around you, disabling electronics."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 30 SECONDS
+
+ invocation = "E'P"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
emp_heavy = 6
emp_light = 10
-/obj/effect/proc_holder/spell/aoe_turf/fire_cascade
- name = "Fire Cascade"
- desc = "Creates a large cascading burst of flames around you."
- school = "transmutation"
- charge_max = 300 //twice as long as mansus grasp
- clothes_req = FALSE
- invocation = "CONFLAGRATE"
- invocation_type = SPELL_INVOCATION_SAY
- range = 4
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "fire_ring"
- action_background_icon_state = "bg_ecult"
-
-/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/cast(list/targets, mob/user = usr)
- INVOKE_ASYNC(src, PROC_REF(fire_cascade), user,range)
-
-/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/proc/fire_cascade(atom/centre,max_range)
- playsound(get_turf(centre), 'sound/items/welder.ogg', 75, TRUE)
- var/_range = 1
- for(var/i = 0, i <= max_range,i++)
- for(var/turf/T in spiral_range_turfs(_range,centre))
- new /obj/effect/hotspot(T)
- T.hotspot_expose(700,50,1)
- for(var/mob/living/livies in T.contents - centre)
- livies.adjustFireLoss(5)
- _range++
- sleep(0.3 SECONDS)
-
-/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big
- range = 6
-
-/obj/effect/proc_holder/spell/targeted/telepathy/eldritch
- invocation = ""
- invocation_type = SPELL_INVOCATION_WHISPER
- clothes_req = FALSE
- action_background_icon_state = "bg_ecult"
-
-/obj/effect/proc_holder/spell/targeted/fire_sworn
+/// Creates one, large, expanding ring of fire around the caster, which does not follow them.
+/datum/action/cooldown/spell/fire_cascade
+ name = "Lesser Fire Cascade"
+ desc = "Heats the air around you."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "fire_ring"
+ sound = 'sound/items/welder.ogg'
+
+ school = SCHOOL_FORBIDDEN
+ invocation = "C'SC'DE"
+ invocation_type = INVOCATION_WHISPER
+
+ cooldown_time = 30 SECONDS
+ spell_requirements = NONE
+
+ /// The radius the flames will go around the caster.
+ var/flame_radius = 4
+
+/datum/action/cooldown/spell/fire_cascade/cast(atom/cast_on)
+ . = ..()
+ INVOKE_ASYNC(src, PROC_REF(fire_cascade), get_turf(cast_on), flame_radius)
+
+/// Spreads a huge wave of fire in a radius around us, staggered between levels
+/datum/action/cooldown/spell/fire_cascade/proc/fire_cascade(atom/centre, flame_radius = 1)
+ for(var/i in 0 to flame_radius)
+ for(var/turf/nearby_turf as anything in spiral_range_turfs(i + 1, centre))
+ new /obj/effect/hotspot(nearby_turf)
+ nearby_turf.hotspot_expose(750, 50, 1)
+ for(var/mob/living/fried_living in nearby_turf.contents - centre)
+ fried_living.apply_damage(5, BURN)
+ stoplag(0.3 SECONDS)
+
+/datum/action/cooldown/spell/fire_cascade/big
+ name = "Greater Fire Cascade"
+ flame_radius = 6
+
+/datum/action/cooldown/spell/list_target/telepathy/eldritch
+ name = "Eldritch Telepathy"
+ school = SCHOOL_FORBIDDEN
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ invocation_type = INVOCATION_NONE
+ antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
+
+/// Creates a constant Ring of Fire around the caster for a set duration of time, which follows them.
+/datum/action/cooldown/spell/fire_sworn
name = "Oath of Fire"
desc = "Engulf yourself in a cloak of flames for a minute. The flames are harmless to you, but dangerous to anyone else."
- invocation = "FUEL FOR THE FIRE"
- invocation_type = SPELL_INVOCATION_SAY
- clothes_req = FALSE
- action_background_icon_state = "bg_ecult"
- range = -1
- include_user = TRUE
- charge_max = 700
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "fire_ring"
- ///how long it lasts
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "fire_ring"
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 70 SECONDS
+
+ invocation = "FL'MS"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
+ /// The radius of the fire ring
+ var/fire_radius = 1
+ /// How long it the ring lasts
var/duration = 1 MINUTES
- ///who casted it right now
- var/mob/current_user
- ///Determines if you get the fire ring effect
- var/has_fire_ring = FALSE
-/obj/effect/proc_holder/spell/targeted/fire_sworn/cast(list/targets, mob/user)
- . = ..()
- current_user = user
- has_fire_ring = TRUE
- addtimer(CALLBACK(src, PROC_REF(remove), user), duration, TIMER_OVERRIDE|TIMER_UNIQUE)
+/datum/action/cooldown/spell/fire_sworn/Remove(mob/living/remove_from)
+ remove_from.remove_status_effect(/datum/status_effect/fire_ring)
+ return ..()
-/obj/effect/proc_holder/spell/targeted/fire_sworn/proc/remove()
- has_fire_ring = FALSE
+/datum/action/cooldown/spell/fire_sworn/is_valid_target(atom/cast_on)
+ return isliving(cast_on)
-/obj/effect/proc_holder/spell/targeted/fire_sworn/process(delta_time)
+/datum/action/cooldown/spell/fire_sworn/cast(mob/living/cast_on)
. = ..()
- if(!has_fire_ring)
+ cast_on.apply_status_effect(/datum/status_effect/fire_ring, duration, fire_radius)
+
+/// Simple status effect for adding a ring of fire around a mob.
+/datum/status_effect/fire_ring
+ id = "fire_ring"
+ tick_interval = 0.1 SECONDS
+ status_type = STATUS_EFFECT_REFRESH
+ alert_type = null
+ /// The radius of the ring around us.
+ var/ring_radius = 1
+
+/datum/status_effect/fire_ring/on_creation(mob/living/new_owner, duration = 1 MINUTES, radius = 1)
+ src.duration = duration
+ src.ring_radius = radius
+ return ..()
+
+/datum/status_effect/fire_ring/tick(delta_time, times_fired)
+ if(QDELETED(owner) || owner.stat == DEAD)
+ qdel(src)
return
- for(var/turf/T in range(1,current_user))
- new /obj/effect/hotspot(T)
- T.hotspot_expose(700,250 * delta_time,1)
- for(var/mob/living/L in T.contents - current_user)
- L.adjustFireLoss(25 * delta_time)
+
+ if(!isturf(owner.loc))
+ return
+
+ for(var/turf/nearby_turf as anything in RANGE_TURFS(1, owner))
+ new /obj/effect/hotspot(nearby_turf)
+ nearby_turf.hotspot_expose(750, 25 * delta_time, 1)
+ for(var/mob/living/fried_living in nearby_turf.contents - owner)
+ fried_living.apply_damage(2.5 * delta_time, BURN)
-/obj/effect/proc_holder/spell/targeted/worm_contract
+/datum/action/cooldown/spell/worm_contract
name = "Force Contract"
desc = "Forces all the worm parts to collapse onto a single turf"
- invocation_type = SPELL_INVOCATION_NONE
- clothes_req = FALSE
- action_background_icon_state = "bg_ecult"
- range = -1
- include_user = TRUE
- charge_max = 300
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "worm_contract"
-
-/obj/effect/proc_holder/spell/targeted/worm_contract/cast(list/targets, mob/user)
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "worm_contract"
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 30 SECONDS
+
+ invocation_type = INVOCATION_NONE
+ spell_requirements = NONE
+
+/datum/action/cooldown/spell/worm_contract/cast(mob/living/user)
. = ..()
if(!istype(user,/mob/living/simple_animal/hostile/eldritch/armsy))
to_chat(user, span_userdanger("You try to contract your muscles but nothing happens..."))
@@ -489,113 +602,136 @@
/obj/effect/temp_visual/eldritch_smoke
icon = 'icons/effects/eldritch.dmi'
icon_state = "smoke"
- duration = 10
+ duration = 1 SECONDS
-/obj/effect/proc_holder/spell/targeted/fiery_rebirth
+/datum/action/cooldown/spell/aoe/fiery_rebirth
name = "Nightwatcher's Rebirth"
- desc = "Drains the health of nearby combusting individuals, healing you 10 of each damage type for every victim. If a victim is in critical condition, they will be finished off."
- invocation = "ASHES TO ASHES"
- invocation_type = SPELL_INVOCATION_WHISPER
- clothes_req = FALSE
- action_background_icon_state = "bg_ecult"
- range = -1
- include_user = TRUE
- charge_max = 600
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "smoke"
-
-/obj/effect/proc_holder/spell/targeted/fiery_rebirth/cast(list/targets, mob/user)
- if(!ishuman(user))
- return
- var/mob/living/carbon/human/human_user = user
- for(var/mob/living/carbon/target in view(7,user))
- if(target.stat == DEAD || !target.on_fire)
+ desc = "A spell that extinguishes you drains nearby heathens engulfed in flames of their life force, \
+ healing you for each victim drained. Those in critical condition \
+ will have the last of their vitality drained, killing them."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "smoke"
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 1 MINUTES
+
+ invocation = "GL'RY T' TH' N'GHT'W'TCH'ER"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = SPELL_REQUIRES_HUMAN
+
+/datum/action/cooldown/spell/aoe/fiery_rebirth/cast(mob/living/carbon/human/cast_on)
+ cast_on.extinguish_mob()
+ return ..()
+
+/datum/action/cooldown/spell/aoe/fiery_rebirth/get_things_to_cast_on(atom/center)
+ var/list/things = list()
+ for(var/mob/living/carbon/nearby_mob in range(aoe_radius, center))
+ if(nearby_mob == owner || nearby_mob == center)
+ continue
+ if(!nearby_mob.mind || !nearby_mob.client)
+ continue
+ if(IS_HERETIC(nearby_mob) || IS_HERETIC_MONSTER(nearby_mob))
+ continue
+ if(nearby_mob.stat == DEAD || !nearby_mob.on_fire)
continue
- //This is essentially a death mark, use this to finish your opponent quicker.
- if(target.InCritical())
- target.death()
- target.adjustFireLoss(20)
- new /obj/effect/temp_visual/eldritch_smoke(target.drop_location())
- human_user.ExtinguishMob()
- human_user.adjustBruteLoss(-10, FALSE)
- human_user.adjustFireLoss(-10, FALSE)
- human_user.adjustStaminaLoss(-10, FALSE)
- human_user.adjustToxLoss(-10, FALSE)
- human_user.adjustOxyLoss(-10)
-
-/obj/effect/proc_holder/spell/pointed/manse_link
- name = "Mansus Link"
- desc = "Pierce through reality and connect minds. Hitting someone with this spell will add them to your Mansus link if uninterrupted, allowing for silent communication."
- school = "transmutation"
- charge_max = 300
- clothes_req = FALSE
- invocation = "HEAR MY VOICE"
- invocation_type = SPELL_INVOCATION_WHISPER
- range = 10
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "mansus_link"
- action_background_icon_state = "bg_ecult"
-
-/obj/effect/proc_holder/spell/pointed/manse_link/can_target(atom/target, mob/user, silent)
- if(!isliving(target))
- return FALSE
- return TRUE
-/obj/effect/proc_holder/spell/pointed/manse_link/cast(list/targets, mob/user)
- var/mob/living/simple_animal/hostile/eldritch/raw_prophet/originator = user
+ things += nearby_mob
- var/mob/living/target = targets[1]
+ return things
- to_chat(originator, span_notice("You begin linking [target]'s mind to yours..."))
- to_chat(target, span_warning("You feel your mind being pulled... connected... to something unreal..."))
- if(!do_after(originator, 6 SECONDS, target))
- return
- if(!originator.link_mob(target))
- to_chat(originator, span_warning("You can't seem to link [target]'s mind..."))
- to_chat(target, span_warning("The foreign presence leaves your mind."))
- return
- to_chat(originator, span_notice("You connect [target]'s mind to your mansus link!"))
+/datum/action/cooldown/spell/aoe/fiery_rebirth/cast_on_thing_in_aoe(mob/living/carbon/victim, mob/living/carbon/human/caster)
+ new /obj/effect/temp_visual/eldritch_smoke(victim.drop_location())
+ //This is essentially a death mark, use this to finish your opponent quicker.
+ if(victim.stat == UNCONSCIOUS && !HAS_TRAIT(victim, TRAIT_NODEATH))
+ victim.death()
+ victim.apply_damage(20, BURN)
-/datum/action/innate/mansus_speech
- name = "Mansus Link"
- desc = "Send a psychic message to everyone connected to your Mansus link."
- button_icon_state = "link_speech"
- icon_icon = 'icons/mob/actions/actions_slime.dmi'
- background_icon_state = "bg_ecult"
- var/mob/living/simple_animal/hostile/eldritch/raw_prophet/originator
+ // Heal the caster for every victim damaged
+ caster.adjustBruteLoss(-10, FALSE)
+ caster.adjustFireLoss(-10, FALSE)
+ caster.adjustToxLoss(-10, FALSE)
+ caster.adjustOxyLoss(-10, FALSE)
+ caster.adjustStaminaLoss(-10)
-/datum/action/innate/mansus_speech/New(_originator)
- . = ..()
- originator = _originator
-/datum/action/innate/mansus_speech/Activate()
- var/mob/living/living_owner = owner
- if(!originator?.linked_mobs[living_owner])
- CRASH("Uh oh the mansus link got somehow activated without it being linked to a raw prophet or the mob not being in a list of mobs that should be able to do it.")
+/datum/action/cooldown/spell/pointed/manse_link
+ name = "Manse Link"
+ desc = "This spell allows you to pierce through reality and connect minds to one another \
+ via your Mansus Link. All minds connected to your Mansus Link will be able to communicate discreetly across great distances."
+ background_icon_state = "bg_heretic"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "mansus_link"
+ ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
- var/message = sanitize(input("Message:", "Telepathy from the Manse") as text|null)
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 20 SECONDS
- if(QDELETED(living_owner))
- return
+ invocation = "PI'RC' TH' M'ND."
+ invocation_type = INVOCATION_SHOUT
+ spell_requirements = NONE
+
+ cast_range = 7
- if(!originator?.linked_mobs[living_owner])
- to_chat(living_owner, span_warning("The link seems to have been severed..."))
- Remove(living_owner)
+ /// The time it takes to link to a mob.
+ var/link_time = 6 SECONDS
+
+/datum/action/cooldown/spell/pointed/manse_link/New(Target)
+ . = ..()
+ if(!istype(Target, /datum/component/mind_linker))
+ stack_trace("[name] ([type]) was instantiated on a non-mind_linker target, this doesn't work.")
+ qdel(src)
+
+/datum/action/cooldown/spell/pointed/manse_link/is_valid_target(atom/cast_on)
+ . = ..()
+ if(!.)
+ return FALSE
+ return isliving(cast_on)
+
+/datum/action/cooldown/spell/pointed/manse_link/before_cast(mob/living/cast_on)
+ . = ..()
+ if(. & SPELL_CANCEL_CAST)
return
- if(message)
- var/msg = "\[Mansus Link\] [living_owner]: [message]"
- log_directed_talk(living_owner, originator, msg, LOG_SAY, "Mansus Link")
- to_chat(originator.linked_mobs, msg)
- for(var/dead_mob in GLOB.dead_mob_list)
- var/link = FOLLOW_LINK(dead_mob, living_owner)
- to_chat(dead_mob, "[link] [msg]")
+ // If we fail to link, cancel the spell.
+ if(!do_linking(cast_on))
+ return . | SPELL_CANCEL_CAST
+
+/**
+* The actual process of linking [linkee] to our network.
+*/
+/datum/action/cooldown/spell/pointed/manse_link/proc/do_linking(mob/living/linkee)
+ var/datum/component/mind_linker/linker = target
+ if(linkee.stat == DEAD)
+ to_chat(owner, span_warning("They're dead!"))
+ return FALSE
+ to_chat(owner, span_notice("You begin linking [linkee]'s mind to yours..."))
+ to_chat(linkee, span_warning("You feel your mind being pulled somewhere... connected... intertwined with the very fabric of reality..."))
+ if(!do_after(owner, link_time, linkee))
+ to_chat(owner, span_warning("You fail to link to [linkee]'s mind."))
+ to_chat(linkee, span_warning("The foreign presence leaves your mind."))
+ return FALSE
+ if(QDELETED(src) || QDELETED(owner) || QDELETED(linkee))
+ return FALSE
+ if(!linker.link_mob(linkee))
+ to_chat(owner, span_warning("You can't seem to link to [linkee]'s mind."))
+ to_chat(linkee, span_warning("The foreign presence leaves your mind."))
+ return FALSE
+ return TRUE
-/obj/effect/proc_holder/spell/pointed/trigger/blind/eldritch
- range = 10
- invocation = "SEE NO EVIL"
- action_background_icon_state = "bg_ecult"
+// Given to heretic monsters.
+/datum/action/cooldown/spell/pointed/blind/eldritch
+ name = "Eldritch Blind"
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+
+ school = SCHOOL_FORBIDDEN
+ invocation = "E'E'S"
+ spell_requirements = NONE
+
+ cast_range = 10
/obj/effect/temp_visual/dir_setting/entropic
icon = 'icons/effects/160x160.dmi'
@@ -628,42 +764,74 @@
icon_state = "small_rune_[rand(12)]"
update_icon()
-/obj/effect/proc_holder/spell/cone/staggered/entropic_plume
+// Shoots out in a wave-like, what rust heretics themselves get
+/datum/action/cooldown/spell/cone/staggered/entropic_plume
name = "Entropic Plume"
- desc = "Spews forth a disorienting plume that causes enemies to strike each other, briefly blinding them (increasing with range) and poisoning them (decreasing with range), while also spreading rust in the path of the plume."
- school = "illusion"
- invocation = "GUST OF RUST"
- invocation_type = SPELL_INVOCATION_WHISPER
- clothes_req = FALSE
- action_background_icon_state = "bg_ecult"
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "entropic_plume"
- charge_max = 300
+ desc = "Spews forth a disorienting plume that causes enemies to strike each other, briefly blinds them(increasing with range) and poisons them(decreasing with range), while also spreading rust in the path of the plume."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "entropic_plume"
+ sound = 'sound/magic/forcewall.ogg'
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 30 SECONDS
+
+ invocation = "'NTR'P'C PL'M'"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
cone_levels = 5
respect_density = TRUE
-/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/cast(list/targets,mob/user = usr)
+/datum/action/cooldown/spell/cone/staggered/entropic_plume/cast(atom/cast_on)
. = ..()
- new /obj/effect/temp_visual/dir_setting/entropic(get_step(user,user.dir), user.dir)
+ new /obj/effect/temp_visual/dir_setting/entropic(get_step(cast_on, cast_on.dir), cast_on.dir)
-/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/do_turf_cone_effect(turf/target_turf, level)
- . = ..()
+/datum/action/cooldown/spell/cone/staggered/entropic_plume/do_turf_cone_effect(turf/target_turf, atom/caster, level)
target_turf.rust_heretic_act()
-/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/do_mob_cone_effect(mob/living/victim, level)
+/datum/action/cooldown/spell/cone/staggered/entropic_plume/do_mob_cone_effect(mob/living/victim, atom/caster, level)
. = ..()
if(victim.anti_magic_check() || IS_HERETIC(victim) || IS_HERETIC_MONSTER(victim))
return
victim.apply_status_effect(STATUS_EFFECT_AMOK)
- victim.apply_status_effect(STATUS_EFFECT_CLOUDSTRUCK, (level*10))
+ victim.apply_status_effect(STATUS_EFFECT_CLOUDSTRUCK, (level * 1 SECONDS))
if(iscarbon(victim))
var/mob/living/carbon/carbon_victim = victim
- carbon_victim.reagents.add_reagent(/datum/reagent/eldritch, min(1, 6-level))
+ carbon_victim.reagents?.add_reagent(/datum/reagent/eldritch, min(1, 6 - level))
-/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/calculate_cone_shape(current_level)
+/datum/action/cooldown/spell/cone/staggered/entropic_plume/calculate_cone_shape(current_level)
if(current_level == cone_levels)
return 5
- else if(current_level == cone_levels-1)
+ else if(current_level == cone_levels - 1)
return 3
else
return 2
+
+// Action for Raw Prophets that boosts up or shrinks down their sight range.
+/datum/action/innate/expand_sight
+ name = "Expand Sight"
+ desc = "Boosts your sight range considerably, allowing you to see enemies from much further away."
+ background_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "eye"
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ /// How far we expand the range to.
+ var/boost_to = 5
+ /// A cooldown for the last time we toggled it, to prevent spam.
+ COOLDOWN_DECLARE(last_toggle)
+
+/datum/action/innate/expand_sight/IsAvailable(feedback = FALSE)
+ return ..() && COOLDOWN_FINISHED(src, last_toggle)
+
+/datum/action/innate/expand_sight/Activate()
+ active = TRUE
+ owner.client?.view_size.setTo(boost_to)
+ playsound(owner, pick('sound/hallucinations/i_see_you1.ogg', 'sound/hallucinations/i_see_you2.ogg'), 50, TRUE, ignore_walls = FALSE)
+ COOLDOWN_START(src, last_toggle, 8 SECONDS)
+
+/datum/action/innate/expand_sight/Deactivate()
+ active = FALSE
+ owner.client?.view_size.resetToDefault()
+ COOLDOWN_START(src, last_toggle, 4 SECONDS)
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm
index 431b88dc9fb7..bf923793c85c 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm
@@ -17,14 +17,12 @@
to_chat(owner, span_boldannounce("You became an Eldritch Horror!"))
/datum/antagonist/heretic_monster/on_gain()
- SSticker.mode.update_heretic_icons_added(owner)
return ..()
/datum/antagonist/heretic_monster/on_removal()
if(owner)
to_chat(owner, span_boldannounce("Your master is no longer [master.owner.current.real_name]"))
owner = null
- SSticker.mode.update_heretic_icons_removed(owner)
return ..()
/datum/antagonist/heretic_monster/proc/set_owner(datum/antagonist/_master)
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm b/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm
index b3a9293d2c50..4b152549bdee 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm
@@ -217,4 +217,4 @@
name = "Codex Cicatrix"
required_atoms = list(/obj/item/organ/eyes,/obj/item/stack/sheet/animalhide/human,/obj/item/storage/book/bible,/obj/item/pen)
result_atoms = list(/obj/item/forbidden_book)
- required_shit_list = "A bible, a sheet of human skin, a pen, and a pair of eyes."
\ No newline at end of file
+ required_shit_list = "A bible, a sheet of human skin, a pen, and a pair of eyes."
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm
index aef50aedf98d..7cb0d7c96452 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm
@@ -34,19 +34,18 @@
var/mob/living/carbon/C = target
var/datum/status_effect/eldritch/E = C.has_status_effect(/datum/status_effect/eldritch/rust) || C.has_status_effect(/datum/status_effect/eldritch/ash) || C.has_status_effect(/datum/status_effect/eldritch/flesh)
if(E)
- E.on_effect()
- for(var/X in user.mind.spell_list)
- if(!istype(X,/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp))
- continue
- var/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp/MG = X
- MG.charge_counter = min(round(MG.charge_counter + MG.charge_max * 0.75),MG.charge_max) // refunds 75% of charge.
+ // Also refunds 75% of charge!
+ var/datum/action/cooldown/spell/touch/mansus_grasp/grasp = locate() in user.actions
+ if(grasp)
+ grasp.next_use_time = min(round(grasp.next_use_time - grasp.cooldown_time * 0.75, 0), 0)
+ grasp.build_all_button_icons()
-/datum/eldritch_knowledge/ashen_shift
+/datum/eldritch_knowledge/spell/ashen_shift
name = "Ashen Shift"
gain_text = "Essence is versatile, flexible. It is so easy for grains to blow into all sorts of small crevices."
desc = "A very short range jaunt that can help you escape from bad situations or navigate past obstacles."
cost = 1
- spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash)
+ spell_to_add = /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash
route = PATH_ASH
tier = TIER_1
@@ -72,7 +71,7 @@
if(isliving(target))
. = TRUE
var/mob/living/living_target = target
- living_target.apply_status_effect(/datum/status_effect/eldritch/ash,5)
+ living_target.apply_status_effect(/datum/status_effect/eldritch/ash, 5)
/datum/eldritch_knowledge/blindness
name = "Curse of Blindness"
@@ -113,23 +112,23 @@
if(iscarbon(target))
var/mob/living/carbon/C = target
C.adjust_fire_stacks(2)
- C.IgniteMob()
+ C.ignite_mob()
-/datum/eldritch_knowledge/flame_birth
+/datum/eldritch_knowledge/spell/flame_birth
name = "Flame Birth"
gain_text = "The Nightwatcher was a man of principles, yet he arose from the chaos he vowed to protect from. This incantation sealed the fate of Amgala."
desc = "A healing-damage spell that saps the life from those on fire nearby, killing any who are in a critical condition."
cost = 1
- spells_to_add = list(/obj/effect/proc_holder/spell/targeted/fiery_rebirth)
+ spell_to_add = /datum/action/cooldown/spell/aoe/fiery_rebirth
route = PATH_ASH
tier = TIER_3
-/datum/eldritch_knowledge/cleave
+/datum/eldritch_knowledge/spell/cleave
name = "Blood Cleave"
gain_text = "The Shrouded One connects all. This technique, a particular favorite of theirs, rips at the bodies of those who hunch too close to permit casuality."
desc = "A powerful ranged spell that causes heavy bleeding and blood loss in an area around your target."
cost = 1
- spells_to_add = list(/obj/effect/proc_holder/spell/pointed/cleave)
+ spell_to_add = /datum/action/cooldown/spell/pointed/cleave
tier = TIER_3
/datum/eldritch_knowledge/ash_final
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
index 080579b9ad0d..576689a6c78f 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
@@ -111,12 +111,12 @@
route = PATH_FLESH
tier = TIER_2
-/datum/eldritch_knowledge/blood_siphon
+/datum/eldritch_knowledge/spell/blood_siphon
name = "Blood Siphon"
gain_text = "The meat of another being is a delicacy that many enjoy. The Gravekeeper's hunger may be decadent, but you will come to know the strength it yields."
desc = "A touch spell that drains a target's health and restores yours."
cost = 1
- spells_to_add = list(/obj/effect/proc_holder/spell/targeted/touch/blood_siphon)
+ spell_to_add = /datum/action/cooldown/spell/pointed/blood_siphon
tier = TIER_2
/datum/eldritch_knowledge/flesh_blade_upgrade
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
index 4b7425830acb..310d5a45cea4 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
@@ -79,12 +79,12 @@
var/mob/living/living_target = target
living_target.apply_status_effect(/datum/status_effect/eldritch/rust)
-/datum/eldritch_knowledge/area_conversion
+/datum/eldritch_knowledge/spell/area_conversion
name = "Aggressive Spread"
gain_text = "It never succumbs in a day. Always, an infection takes hold at the base, and spreads. Rot and filth collapse bodies and structures alike."
desc = "An instant spell that spreads rust onto nearby tiles, destroying any already rusted."
cost = 1
- spells_to_add = list(/obj/effect/proc_holder/spell/aoe_turf/rust_conversion)
+ spell_to_add = /datum/action/cooldown/spell/aoe/rust_conversion
route = PATH_RUST
tier = TIER_2
@@ -103,12 +103,12 @@
var/mob/living/carbon/carbon_target = target
carbon_target.reagents.add_reagent(/datum/reagent/eldritch, 2)
-/datum/eldritch_knowledge/entropic_plume
+/datum/eldritch_knowledge/spell/entropic_plume
name = "Entropic Plume"
gain_text = "The fumes that began to flow from the Corroded Sewers choked the River Krym dead. Legends still say the Vermin Duke is within its fogged tunnels, his form nearly petrified from age."
desc = "A cone spell that expels a befuddling plume that rusts tiles, then blinds, poisons, and forces targets to strike each other."
cost = 1
- spells_to_add = list(/obj/effect/proc_holder/spell/cone/staggered/entropic_plume)
+ spell_to_add = /datum/action/cooldown/spell/cone/staggered/entropic_plume
route = PATH_RUST
tier = TIER_3
diff --git a/code/modules/antagonists/eldritch_cult/transmutations/ash_transmutations.dm b/code/modules/antagonists/eldritch_cult/transmutations/ash_transmutations.dm
index 429653d9dc52..2ff53f2706b7 100644
--- a/code/modules/antagonists/eldritch_cult/transmutations/ash_transmutations.dm
+++ b/code/modules/antagonists/eldritch_cult/transmutations/ash_transmutations.dm
@@ -65,8 +65,13 @@
/datum/eldritch_transmutation/final/ash_final/on_finished_recipe(mob/living/user, list/atoms, loc)
priority_announce("Immense destabilization of the bluespace veil has been observed. Our scanners report a fiery entity of unknown power is quickly escalating the station temperature to unhabitable levels. Immediate evacuation is advised.", "Anomaly Alert", ANNOUNCER_SPANOMALIES)
set_security_level(SEC_LEVEL_GAMMA)
- user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big)
- user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/fire_sworn)
+
+ var/datum/action/cooldown/spell/fire_sworn/circle_spell = new(user.mind)
+ circle_spell.Grant(user)
+
+ var/datum/action/cooldown/spell/fire_cascade/big/screen_wide_fire_spell = new(user.mind)
+ screen_wide_fire_spell.Grant(user)
+
var/mob/living/carbon/human/H = user
H.physiology.brute_mod *= 0.5
H.physiology.burn_mod *= 0.5
diff --git a/code/modules/antagonists/eldritch_cult/transmutations/flesh_transmutations.dm b/code/modules/antagonists/eldritch_cult/transmutations/flesh_transmutations.dm
index 836900820852..41db7a7a04e5 100644
--- a/code/modules/antagonists/eldritch_cult/transmutations/flesh_transmutations.dm
+++ b/code/modules/antagonists/eldritch_cult/transmutations/flesh_transmutations.dm
@@ -123,11 +123,11 @@
var/mob/living/summoned = new /mob/living/simple_animal/hostile/eldritch/armsy/prime(loc,TRUE,10)
summoned.ghostize(0)
user.SetImmobilized(0)
- for(var/obj/effect/proc_holder/spell/S in user.mind.spell_list)
- if(istype(S, /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash)) //vitally important since ashen passage breaks the shit out of armsy
- user.mind.spell_list.Remove(S)
- qdel(S)
- priority_announce("Immense destabilization of the bluespace veil has been observed. Our scanners report a singular entity of immeasurable power that is quickly growing in volume. Immediate evacuation is advised.", "Anomaly Alert", ANNOUNCER_SPANOMALIES)
+ for(var/datum/action/cooldown/spell/spells in user.actions)
+ if(istype(spells, /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash)) //vitally important since ashen passage breaks the shit out of armsy
+ spells.Remove(user)
+ qdel(spells)
+ priority_announce("$^@*$^@(#&$(@^$^@# Fear the dark, for King of Arms has ascended! Our Lord of the Night has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", ANNOUNCER_SPANOMALIES)
set_security_level(SEC_LEVEL_GAMMA)
log_game("[user.real_name] ascended as [summoned.real_name].")
var/mob/living/carbon/carbon_user = user
diff --git a/code/modules/antagonists/fugitive/fugitive.dm b/code/modules/antagonists/fugitive/fugitive.dm
index b228bd53dca9..b7c850e33cdd 100644
--- a/code/modules/antagonists/fugitive/fugitive.dm
+++ b/code/modules/antagonists/fugitive/fugitive.dm
@@ -4,18 +4,14 @@
silent = TRUE //greet called by the event
show_in_antagpanel = FALSE
prevent_roundtype_conversion = FALSE
+ antag_hud_name = "fugitive"
var/datum/team/fugitive/fugitive_team
var/is_captured = FALSE
var/backstory = "error"
preview_outfit = /datum/outfit/spacepol
/datum/antagonist/fugitive/apply_innate_effects(mob/living/mob_override)
- var/mob/living/M = mob_override || owner.current
- update_fugitive_icons_added(M)
-
-/datum/antagonist/fugitive/remove_innate_effects(mob/living/mob_override)
- var/mob/living/M = mob_override || owner.current
- update_fugitive_icons_removed(M)
+ add_team_hud(mob_override || owner.current)
/datum/antagonist/fugitive/on_gain()
forge_objectives()
@@ -90,26 +86,16 @@
return result.Join("
")
-/datum/antagonist/fugitive/proc/update_fugitive_icons_added(var/mob/living/carbon/human/fugitive)
- var/datum/atom_hud/antag/fughud = GLOB.huds[ANTAG_HUD_FUGITIVE]
- fughud.join_hud(fugitive)
- set_antag_hud(fugitive, "fugitive")
-
-/datum/antagonist/fugitive/proc/update_fugitive_icons_removed(var/mob/living/carbon/human/fugitive)
- var/datum/atom_hud/antag/fughud = GLOB.huds[ANTAG_HUD_FUGITIVE]
- fughud.leave_hud(fugitive)
- set_antag_hud(fugitive, null)
-
/datum/action/innate/yalpcomms
name = "Yalp Elor Communion"
desc = "Allows talking with the brothers of Yalp Elor."
- icon_icon = 'icons/mob/actions/actions_cult.dmi'
+ button_icon = 'icons/mob/actions/actions_cult.dmi'
button_icon_state = "yalp_comms"
background_icon_state = "bg_tech"
/datum/action/innate/yalpcomms/Activate()
var/input = stripped_input(usr, "Input a message to send to your brothers.", "Yalp Elor Communion", "")
- if(!input || !IsAvailable())
+ if(!input || !IsAvailable(feedback = FALSE))
return
yalp_speech(usr, input)
diff --git a/code/modules/antagonists/fugitive/fugitive_outfits.dm b/code/modules/antagonists/fugitive/fugitive_outfits.dm
index e536c40f25d4..b5484b734d0c 100644
--- a/code/modules/antagonists/fugitive/fugitive_outfits.dm
+++ b/code/modules/antagonists/fugitive/fugitive_outfits.dm
@@ -52,17 +52,18 @@
H.facial_hair_style = "Shaved"
H.hair_color = "000"
H.facial_hair_color = H.hair_color
- if(H.mind)
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
+
var/list/no_drops = list()
no_drops += H.get_item_by_slot(SLOT_SHOES)
no_drops += H.get_item_by_slot(SLOT_W_UNIFORM)
no_drops += H.get_item_by_slot(SLOT_WEAR_SUIT)
no_drops += H.get_item_by_slot(SLOT_HEAD)
no_drops += H.get_item_by_slot(SLOT_GLASSES)
- for(var/i in no_drops)
- var/obj/item/I = i
- ADD_TRAIT(I, TRAIT_NODROP, CURSED_ITEM_TRAIT)
+ for(var/obj/item/trait_needed as anything in no_drops)
+ ADD_TRAIT(trait_needed, TRAIT_NODROP, CURSED_ITEM_TRAIT(trait_needed.type))
+
+ var/datum/action/cooldown/spell/aoe/knock/waldos_key = new(H.mind || H)
+ waldos_key.Grant(H)
/datum/outfit/synthetic
name = "Factory Error Synth"
diff --git a/code/modules/antagonists/fugitive/hunter.dm b/code/modules/antagonists/fugitive/hunter.dm
index c5c2e7aaa461..f7893dd879e9 100644
--- a/code/modules/antagonists/fugitive/hunter.dm
+++ b/code/modules/antagonists/fugitive/hunter.dm
@@ -5,17 +5,10 @@
silent = TRUE //greet called by the spawn
show_in_antagpanel = FALSE
prevent_roundtype_conversion = FALSE
+ antag_hud_name = "fugitive_hunter"
var/datum/team/fugitive_hunters/hunter_team
var/backstory = "error"
-/datum/antagonist/fugitive_hunter/apply_innate_effects(mob/living/mob_override)
- var/mob/living/M = mob_override || owner.current
- update_fugitive_icons_added(M)
-
-/datum/antagonist/fugitive_hunter/remove_innate_effects(mob/living/mob_override)
- var/mob/living/M = mob_override || owner.current
- update_fugitive_icons_removed(M)
-
/datum/antagonist/fugitive_hunter/on_gain()
forge_objectives()
. = ..()
@@ -168,13 +161,3 @@
result += ""
return result.Join("
")
-
-/datum/antagonist/fugitive_hunter/proc/update_fugitive_icons_added(var/mob/living/carbon/human/fugitive)
- var/datum/atom_hud/antag/fughud = GLOB.huds[ANTAG_HUD_FUGITIVE]
- fughud.join_hud(fugitive)
- set_antag_hud(fugitive, "fugitive")
-
-/datum/antagonist/fugitive_hunter/proc/update_fugitive_icons_removed(var/mob/living/carbon/human/fugitive)
- var/datum/atom_hud/antag/fughud = GLOB.huds[ANTAG_HUD_FUGITIVE]
- fughud.leave_hud(fugitive)
- set_antag_hud(fugitive, null)
diff --git a/code/modules/antagonists/fugitive/old_god.dm b/code/modules/antagonists/fugitive/old_god.dm
index fd86994cf25b..21ae7aaa957a 100644
--- a/code/modules/antagonists/fugitive/old_god.dm
+++ b/code/modules/antagonists/fugitive/old_god.dm
@@ -104,7 +104,7 @@
/datum/action/innate/yalp_transmit
name = "Divine Oration"
desc = "Transmits a message to the target."
- icon_icon = 'icons/mob/actions/actions_animal.dmi'
+ button_icon = 'icons/mob/actions/actions_animal.dmi'
background_icon_state = "bg_spell"
button_icon_state = "god_transmit"
@@ -124,7 +124,7 @@
target = input("Who do you wish to transmit to?", "Targeting") as null|mob in possible_targets
var/input = stripped_input(owner, "What do you wish to tell [target]?", null, "")
- if(QDELETED(src) || !input || !IsAvailable())
+ if(QDELETED(src) || !input || !IsAvailable(feedback = FALSE))
return FALSE
if(isnotpretty(input)) // Yogs -- Pretty filter
to_chat(owner,span_warning("That's not a very nice thing to tell [target.p_them()]."))
@@ -152,7 +152,7 @@
/datum/action/innate/yalp_transport
name = "Guidance"
desc = "Transports you to a follower."
- icon_icon = 'icons/mob/actions/actions_animal.dmi'
+ button_icon = 'icons/mob/actions/actions_animal.dmi'
background_icon_state = "bg_spell"
button_icon_state = "god_transport"
@@ -185,7 +185,7 @@
/datum/action/cooldown/yalp_heal
name = "Purification"
desc = "Heals all followers a bit."
- icon_icon = 'icons/mob/actions/actions_animal.dmi'
+ button_icon = 'icons/mob/actions/actions_animal.dmi'
background_icon_state = "bg_spell"
button_icon_state = "god_heal"
cooldown_time = 600
diff --git a/code/modules/antagonists/golems/golem.dm b/code/modules/antagonists/golems/golem.dm
index 0b7cb823d8d8..1b38a7041c45 100644
--- a/code/modules/antagonists/golems/golem.dm
+++ b/code/modules/antagonists/golems/golem.dm
@@ -5,7 +5,6 @@
var/old_species
var/golem_species
var/antag_hud
- var/antag_hud_name
var/removing = FALSE //whether we're already in the process of removing this antag datum (needed for remove_innate_effects() on admin_remove())
/datum/antagonist/golem/admin_add(datum/mind/new_owner,mob/admin)
@@ -19,29 +18,14 @@
/datum/antagonist/golem/apply_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
- update_icons(M, "join")
-
-/datum/antagonist/golem/remove_innate_effects(mob/living/mob_override)
- var/mob/living/M = mob_override || owner.current
- update_icons(M, "leave")
-
-/datum/antagonist/golem/proc/update_icons(var/mob/living/carbon/human/golem, leave_or_join)
- var/datum/atom_hud/antag/antagHud = GLOB.huds[antag_hud]
- if(leave_or_join == "join")
- antagHud.join_hud(golem)
- set_antag_hud(golem, antag_hud_name)
- else if(leave_or_join == "leave")
- antagHud.leave_hud(golem)
- set_antag_hud(golem, null)
+ add_team_hud(M, antag_hud)
/datum/antagonist/golem/capitalist
name = "Capitalist Golem"
golem_species = /datum/species/golem/capitalist
- antag_hud = ANTAG_HUD_CAPITALIST
antag_hud_name = "capitalist"
/datum/antagonist/golem/communist
name = "Communist Golem"
golem_species = /datum/species/golem/soviet
- antag_hud = ANTAG_HUD_COMMUNIST
antag_hud_name = "communist"
diff --git a/code/modules/antagonists/hivemind/hivemind.dm b/code/modules/antagonists/hivemind/hivemind.dm
index e56cabe03a1f..89ed8c2378d5 100644
--- a/code/modules/antagonists/hivemind/hivemind.dm
+++ b/code/modules/antagonists/hivemind/hivemind.dm
@@ -3,6 +3,7 @@
roundend_category = "hiveminds"
antagpanel_category = "Hivemind Host"
job_rank = ROLE_HIVE
+ antag_hud_name = "hivemind"
antag_moodlet = /datum/mood_event/focused
var/special_role = ROLE_HIVE
var/list/hivemembers = list()
@@ -17,26 +18,26 @@
var/list/upgrade_tiers = list(
//Tier 1 - Roundstart powers
- /obj/effect/proc_holder/spell/target_hive/hive_add = 0,
- /obj/effect/proc_holder/spell/target_hive/hive_remove = 0,
- /obj/effect/proc_holder/spell/target_hive/hive_see = 0,
- /obj/effect/proc_holder/spell/targeted/hive_shock = 0,
- /obj/effect/proc_holder/spell/self/telekinetic_hand = 0,
+ /datum/action/cooldown/spell/aoe/target_hive/hive_add = 0,
+ /datum/action/cooldown/spell/aoe/target_hive/hive_remove = 0,
+ /datum/action/cooldown/spell/aoe/target_hive/hive_see = 0,
+ /datum/action/cooldown/spell/pointed/hive_shock = 0,
+ /datum/action/cooldown/spell/telekinetic_hand = 0,
//Tier 2 - Tracking related powers
- /obj/effect/proc_holder/spell/self/hive_scan = 5,
- /obj/effect/proc_holder/spell/targeted/hive_reclaim = 5,
- /obj/effect/proc_holder/spell/targeted/hive_hack = 5,
+ /datum/action/cooldown/spell/hive_scan = 5,
+ /datum/action/cooldown/spell/pointed/hive_reclaim = 5,
+ /datum/action/cooldown/spell/pointed/hive_hack = 5,
//Tier 3 - Combat related powers
- /obj/effect/proc_holder/spell/self/hive_drain = 10,
- /obj/effect/proc_holder/spell/targeted/induce_panic = 10,
- /obj/effect/proc_holder/spell/targeted/forcewall/hive = 10,
+ /datum/action/cooldown/spell/hive_drain = 10,
+ /datum/action/cooldown/spell/pointed/induce_panic = 10,
+ /datum/action/cooldown/spell/forcewall/hive = 10,
//Tier 4 - Chaos-spreading powers
- /obj/effect/proc_holder/spell/self/hive_wake = 15,
- /obj/effect/proc_holder/spell/self/hive_loyal = 15,
- /obj/effect/proc_holder/spell/target_hive/hive_control = 15,
+ /datum/action/cooldown/spell/hive_wake = 15,
+ /datum/action/cooldown/spell/hive_loyal = 15,
+ /datum/action/cooldown/spell/aoe/target_hive/hive_control = 15,
//Tier 5 - Deadly powers
- /obj/effect/proc_holder/spell/targeted/pin = 20,
- /obj/effect/proc_holder/spell/target_hive/nightmare = 20
+ /datum/action/cooldown/spell/pointed/pin = 20,
+ /datum/action/cooldown/spell/aoe/target_hive/nightmare = 20
)
@@ -65,9 +66,9 @@
/datum/antagonist/hivemind/proc/check_powers()
for(var/power in upgrade_tiers)
var/level = upgrade_tiers[power]
- if(hive_size+size_mod >= level && !(locate(power) in owner.spell_list))
- var/obj/effect/proc_holder/spell/the_spell = new power(null)
- owner.AddSpell(the_spell)
+ if(hive_size+size_mod >= level && !(locate(power) in owner.current.actions))
+ var/datum/action/cooldown/spell/the_spell = new power(owner.current)
+ the_spell.Grant(owner.current)
if(hive_size > 0)
to_chat(owner, "[span_assimilator("We have unlocked [the_spell.name].")][span_bold(" [the_spell.desc]")]")
@@ -82,7 +83,9 @@
break
if(lead)
unlocked_one_mind = TRUE
- owner.AddSpell(new/obj/effect/proc_holder/spell/self/one_mind)
+ var/datum/action/cooldown/spell/one_mind/one_mind = new(owner.current)
+ one_mind.Grant(owner.current)
+
to_chat(owner, "[span_assimilator("Our true power, the One Mind, is finally within reach.")]")
/datum/antagonist/hivemind/proc/add_track_bonus(datum/antagonist/hivemind/enemy, bonus)
@@ -109,7 +112,7 @@
var/user_warning = span_userdanger("We have detected an enemy hivemind using our physical form as a vessel and have begun ejecting their mind! They will be alerted of our disappearance once we succeed!")
if(C.is_real_hivehost())
var/eject_time = rand(1400,1600) //2.5 minutes +- 10 seconds
- addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, C, user_warning), rand(500,1300)) // If the host has assimilated an enemy hive host, alert the enemy before booting them from the hive after a short while
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), C, user_warning), rand(500,1300)) // If the host has assimilated an enemy hive host, alert the enemy before booting them from the hive after a short while
addtimer(CALLBACK(src, PROC_REF(handle_ejection), C), eject_time)
else if(active_one_mind)
C.hive_awaken(final_form=active_one_mind)
@@ -164,10 +167,10 @@
go_back_to_sleep()
hivemembers = list()
calc_size()
- for(var/power in upgrade_tiers)
+ for(var/datum/action/power in upgrade_tiers)
if(!upgrade_tiers[power])
continue
- owner.RemoveSpell(power)
+ power.Remove(owner.current)
/datum/antagonist/hivemind/antag_panel_data()
return "Vessels Assimilated: [hive_size] (+[size_mod])"
@@ -178,7 +181,8 @@
var/mob/living/carbon/C = owner.current.get_real_hivehost()
if(!C)
return
- owner.AddSpell(new/obj/effect/proc_holder/spell/self/hive_comms)
+ var/datum/action/cooldown/spell/hive_comms/comms = new(owner.current)
+ comms.Grant(owner.current)
ADD_TRAIT(C, TRAIT_STUNIMMUNE, HIVEMIND_ONE_MIND_TRAIT)
ADD_TRAIT(C, TRAIT_SLEEPIMMUNE, HIVEMIND_ONE_MIND_TRAIT)
ADD_TRAIT(C, TRAIT_VIRUSIMMUNE, HIVEMIND_ONE_MIND_TRAIT)
@@ -198,7 +202,8 @@
var/mob/living/carbon/C = owner.current.get_real_hivehost()
if(!C)
return
- owner.RemoveSpell(new/obj/effect/proc_holder/spell/self/hive_comms)
+ for(var/datum/action/cooldown/spell/hive_comms/comms in owner.current.actions)
+ comms.Remove(owner.current)
REMOVE_TRAIT(C, TRAIT_STUNIMMUNE, HIVEMIND_ONE_MIND_TRAIT)
REMOVE_TRAIT(C, TRAIT_SLEEPIMMUNE, HIVEMIND_ONE_MIND_TRAIT)
REMOVE_TRAIT(C, TRAIT_VIRUSIMMUNE, HIVEMIND_ONE_MIND_TRAIT)
@@ -214,29 +219,12 @@
..()
/datum/antagonist/hivemind/apply_innate_effects()
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/traitor_mob = owner.current
- if(traitor_mob && istype(traitor_mob))
- if(!silent)
- to_chat(traitor_mob, "The great psionic powers of the Hive lets you overcome your clownish nature, allowing you to wield weapons with impunity.")
- traitor_mob.dna.remove_mutation(CLOWNMUT)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HIVE]
- hud.join_hud(owner.current)
- set_antag_hud(owner.current, "hivemind")
-
-/datum/antagonist/hivemind/remove_innate_effects()
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/traitor_mob = owner.current
- if(traitor_mob && istype(traitor_mob))
- traitor_mob.dna.add_mutation(CLOWNMUT)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HIVE]
- hud.leave_hud(owner.current)
- set_antag_hud(owner.current, null)
+ handle_clown_mutation(owner.current, "The great psionic powers of the Hive lets you overcome your clownish nature, allowing you to wield weapons with impunity.")
/datum/antagonist/hivemind/on_removal()
//Remove all hive powers here
- for(var/power in upgrade_tiers)
- owner.RemoveSpell(power)
+ for(var/datum/action/power in upgrade_tiers)
+ power.Remove(owner.current)
if(!silent && owner.current)
to_chat(owner.current,span_userdanger(" Your psionic powers fade, you are no longer the hivemind's host! "))
diff --git a/code/modules/antagonists/hivemind/vessel.dm b/code/modules/antagonists/hivemind/vessel.dm
index d1519a003615..4d60e268c04d 100644
--- a/code/modules/antagonists/hivemind/vessel.dm
+++ b/code/modules/antagonists/hivemind/vessel.dm
@@ -4,6 +4,7 @@
/datum/antagonist/hivevessel
name = "Awoken Vessel"
job_rank = ROLE_BRAINWASHED
+ antag_hud_name = "hivevessel"
roundend_category = "awoken vessels"
show_in_antagpanel = TRUE
antagpanel_category = "Other"
@@ -26,7 +27,8 @@
if(ishuman(M.current))
vessel.glow = mutable_appearance('icons/effects/hivemind.dmi', "awoken", -BODY_BEHIND_LAYER)
M.current.add_overlay(vessel.glow)
- M.AddSpell(new/obj/effect/proc_holder/spell/self/hive_comms)
+ var/datum/action/cooldown/spell/hive_comms/comms = new(src)
+ comms.Grant(src)
vessel.one_mind = final_form
vessel.one_mind.add_member(M)
vessel.objectives |= vessel.one_mind.objectives
@@ -40,24 +42,7 @@
M.add_antag_datum(vessel)
/datum/antagonist/hivevessel/apply_innate_effects()
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/traitor_mob = owner.current
- if(traitor_mob && istype(traitor_mob))
- if(!silent)
- to_chat(traitor_mob, "Our newfound powers allow us to overcome our clownish nature, allowing us to wield weapons with impunity.")
- traitor_mob.dna.remove_mutation(CLOWNMUT)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HIVE]
- hud.join_hud(owner.current)
- set_antag_hud(owner.current, "hivevessel")
-
-/datum/antagonist/hivevessel/remove_innate_effects()
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/traitor_mob = owner.current
- if(traitor_mob && istype(traitor_mob))
- traitor_mob.dna.add_mutation(CLOWNMUT)
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HIVE]
- hud.leave_hud(owner.current)
- set_antag_hud(owner.current, null)
+ handle_clown_mutation(owner.current, "Our newfound powers allow us to overcome our clownish nature, allowing us to wield weapons with impunity.")
/datum/antagonist/hivevessel/greet()
to_chat(owner, span_assimilator("Your mind is suddenly opened, as you see the pinnacle of evolution..."))
diff --git a/code/modules/antagonists/horror/horror.dm b/code/modules/antagonists/horror/horror.dm
index 7b8c7bfcbde4..7b2b95aa12ca 100644
--- a/code/modules/antagonists/horror/horror.dm
+++ b/code/modules/antagonists/horror/horror.dm
@@ -76,7 +76,7 @@
RefreshAbilities()
var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
- hud.add_hud_to(src)
+ hud.show_to(src)
update_horror_hud()
@@ -139,7 +139,7 @@
return
var/datum/hud/chemical_counter/H = hud_used
var/atom/movable/screen/counter = H.chemical_counter
- counter.maptext = "[chemicals]
"
+ counter.maptext = ANTAG_MAPTEXT(chemicals, COLOR_DARKSPAWN_PSI)
/mob/living/simple_animal/horror/proc/can_use_ability()
if(stat != CONSCIOUS)
@@ -159,7 +159,8 @@
for(var/datum/mind/M in SSticker.minds)
if(M.current && M.current.stat != DEAD)
if(ishuman(M.current))
- if(M.hasSoul && (mind.enslaved_to != M.current))
+ var/mob/living/master = mind.enslaved_to?.resolve()
+ if(M.hasSoul && (master != M.current))
possible_targets[M] = M
var/list/selected_targets = list()
@@ -201,7 +202,8 @@
to_chat(src, "This host doesn't have a soul!")
return
- if(victim == mind.enslaved_to)
+ var/mob/living/master = mind.enslaved_to?.resolve()
+ if(victim == master)
to_chat(src, span_userdanger("No, not yet... We still need them..."))
return
@@ -345,7 +347,8 @@
return
if(isliving(A))
- if(victim || A == src.mind.enslaved_to)
+ var/mob/living/master = mind.enslaved_to?.resolve()
+ if(victim || A == master)
healthscan(usr, A)
chemscan(usr, A)
else
@@ -670,7 +673,7 @@
if(nlog_type & LOG_SAY)
var/list/reversed = log_source[log_type]
if(islist(reversed))
- say_log = reverseRange(reversed.Copy())
+ say_log = reverse_range(reversed.Copy())
break
if(LAZYLEN(say_log))
for(var/spoken_memory in say_log)
@@ -820,7 +823,7 @@
var/datum/action/innate/horror/action = has_ability(/datum/action/innate/horror/chameleon)
if(action)
action.button_icon_state = "horror_sneak_[invisible ? "true" : "false"]"
- action.UpdateButtonIcon()
+ action.build_all_button_icons()
/mob/living/simple_animal/horror/proc/GrantHorrorActions()
for(var/datum/action/innate/horror/ability in horrorabilities)
diff --git a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
index 671b30f2ea09..f2e57d9000b2 100644
--- a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
+++ b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
@@ -1,15 +1,15 @@
//ABILITIES
/datum/action/innate/horror
- background_icon_state = "bg_ecult"
- icon_icon = 'icons/mob/actions/actions_horror.dmi'
+ background_icon_state = "bg_heretic"
+ button_icon = 'icons/mob/actions/actions_horror.dmi'
var/blacklisted = FALSE //If the ability can't be mutated
var/soul_price = 0 //How much souls the ability costs to buy; if this is 0, it isn't listed on the catalog
var/chemical_cost = 0 //How much chemicals the ability costs to use
var/mob/living/simple_animal/horror/B //Horror holding the ability
var/category //category for when the ability is active, "horror" is for creature, "infest" is during infestation, "controlling" is when a horror is controlling a body
-/datum/action/innate/horror/IsAvailable()
+/datum/action/innate/horror/IsAvailable(feedback = FALSE)
if(!B)
return
if(!B.has_chemicals(chemical_cost))
@@ -68,7 +68,7 @@
/datum/action/innate/horror/toggle_hide/Activate()
B.hide()
button_icon_state = "horror_hiding_[B.hiding ? "true" : "false"]"
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/horror/talk_to_horror
name = "Converse with Horror"
@@ -77,7 +77,7 @@
blacklisted = TRUE
var/mob/living/O
-/datum/action/innate/horror/talk_to_horror/IsAvailable()
+/datum/action/innate/horror/talk_to_horror/IsAvailable(feedback = FALSE)
if(owner.stat == DEAD)
return
return TRUE
@@ -129,7 +129,7 @@
/datum/action/innate/horror/make_chems
name = "Secrete chemicals"
desc = "Push some chemicals into your host's bloodstream."
- icon_icon = 'icons/obj/chemical.dmi'
+ button_icon = 'icons/obj/chemical.dmi'
button_icon_state = "minidispenser"
blacklisted = TRUE
category = list("infest")
@@ -146,10 +146,10 @@
/datum/action/innate/horror/freeze_victim/Activate()
B.freeze_victim()
- UpdateButtonIcon()
- addtimer(CALLBACK(src, PROC_REF(UpdateButtonIcon)), 150)
+ build_all_button_icons()
+ addtimer(CALLBACK(src, PROC_REF(build_all_button_icons)), 15 SECONDS)
-/datum/action/innate/horror/freeze_victim/IsAvailable()
+/datum/action/innate/horror/freeze_victim/IsAvailable(feedback = FALSE)
if(world.time - B.used_freeze < 150)
return FALSE
else
@@ -165,7 +165,7 @@
category = list("infest", "control")
soul_price = 2
-/datum/action/innate/horror/tentacle/IsAvailable()
+/datum/action/innate/horror/tentacle/IsAvailable(feedback = FALSE)
if(!active && !B.has_chemicals(chemical_cost))
return
return ..()
@@ -181,7 +181,7 @@
/datum/action/innate/horror/tentacle/process()
..()
active = locate(/obj/item/horrortentacle) in B.victim
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/horror/tentacle/Activate()
@@ -308,7 +308,7 @@
/datum/action/innate/horror/chameleon/Activate()
B.go_invisible()
button_icon_state = "horror_sneak_[B.invisible ? "true" : "false"]"
- UpdateButtonIcon()
+ build_all_button_icons()
/datum/action/innate/horror/lube_spill
name = "Lube spill"
@@ -319,7 +319,7 @@
soul_price = 2
var/cooldown = 0
-/datum/action/innate/horror/lube_spill/IsAvailable()
+/datum/action/innate/horror/lube_spill/IsAvailable(feedback = FALSE)
if(cooldown > world.time || !B.has_chemicals(chemical_cost) || !B.can_use_ability())
return
return ..()
@@ -327,8 +327,8 @@
/datum/action/innate/horror/lube_spill/Activate()
B.use_chemicals(chemical_cost)
cooldown = world.time + 10 SECONDS
- UpdateButtonIcon()
- addtimer(CALLBACK(src, PROC_REF(UpdateButtonIcon)), 10 SECONDS)
+ build_all_button_icons()
+ addtimer(CALLBACK(src, PROC_REF(build_all_button_icons)), 10 SECONDS)
B.visible_message(span_warning("[B] spins and throws some sort of substance!"), span_notice("Your flail oily substance around you!"))
flick("horror_spin", B)
playsound(B, 'sound/effects/blobattack.ogg', 25, 1)
diff --git a/code/modules/antagonists/horror/horror_datums.dm b/code/modules/antagonists/horror/horror_datums.dm
index 12dea4b17199..865d7fc7aaf6 100644
--- a/code/modules/antagonists/horror/horror_datums.dm
+++ b/code/modules/antagonists/horror/horror_datums.dm
@@ -314,8 +314,8 @@
/datum/action/innate/resist_control
name = "Resist control"
desc = "Try to take back control over your brain. A strong nerve impulse should do it."
- background_icon_state = "bg_ecult"
- icon_icon = 'icons/mob/actions/actions_horror.dmi'
+ background_icon_state = "bg_heretic"
+ button_icon = 'icons/mob/actions/actions_horror.dmi'
button_icon_state = "resist_control"
/datum/action/innate/resist_control/Activate()
diff --git a/code/modules/antagonists/monsterhunter/monsterhunter.dm b/code/modules/antagonists/monsterhunter/monsterhunter.dm
index e73529574a35..d47b230e0023 100644
--- a/code/modules/antagonists/monsterhunter/monsterhunter.dm
+++ b/code/modules/antagonists/monsterhunter/monsterhunter.dm
@@ -8,14 +8,16 @@
roundend_category = "Monster Hunters"
antagpanel_category = "Monster Hunter"
job_rank = ROLE_MONSTERHUNTER
+ antag_hud_name = "monsterhunter"
preview_outfit = /datum/outfit/monsterhunter
var/list/datum/action/powers = list()
var/datum/martial_art/hunterfu/my_kungfu = new
var/give_objectives = TRUE
- var/datum/action/bloodsucker/trackvamp = new /datum/action/bloodsucker/trackvamp()
- var/datum/action/bloodsucker/fortitude = new /datum/action/bloodsucker/fortitude/hunter()
+ var/datum/action/cooldown/bloodsucker/trackvamp = new /datum/action/cooldown/bloodsucker/trackvamp()
+ var/datum/action/cooldown/bloodsucker/fortitude = new /datum/action/cooldown/bloodsucker/fortitude/hunter()
/datum/antagonist/monsterhunter/apply_innate_effects(mob/living/mob_override)
+ . = ..()
var/mob/living/current_mob = mob_override || owner.current
ADD_TRAIT(current_mob, TRAIT_NOSOFTCRIT, BLOODSUCKER_TRAIT)
ADD_TRAIT(current_mob, TRAIT_NOCRITDAMAGE, BLOODSUCKER_TRAIT)
@@ -23,6 +25,7 @@
my_kungfu.teach(current_mob, make_temporary = FALSE)
/datum/antagonist/monsterhunter/remove_innate_effects(mob/living/mob_override)
+ . = ..()
var/mob/living/current_mob = mob_override || owner.current
REMOVE_TRAIT(current_mob, TRAIT_NOSOFTCRIT, BLOODSUCKER_TRAIT)
REMOVE_TRAIT(current_mob, TRAIT_NOCRITDAMAGE, BLOODSUCKER_TRAIT)
@@ -33,11 +36,8 @@
/datum/antagonist/monsterhunter/on_gain()
//Give Monster Hunter powers
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_MHUNTER]
trackvamp.Grant(owner.current)
fortitude.Grant(owner.current)
- hud.join_hud(owner.current)
- set_antag_hud(owner.current, "monsterhunter")
if(give_objectives)
//Give Hunter Objective
var/datum/objective/bloodsucker/monsterhunter/monsterhunter_objective = new
@@ -56,21 +56,17 @@
/datum/antagonist/monsterhunter/on_removal()
//Remove Monster Hunter powers
- var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_MHUNTER]
trackvamp.Remove(owner.current)
fortitude.Remove(owner.current)
- hud.leave_hud(owner.current)
- set_antag_hud(owner.current, null)
to_chat(owner.current, span_userdanger("Your hunt has ended: You enter retirement once again, and are no longer a Monster Hunter."))
return ..()
/datum/antagonist/monsterhunter/on_body_transfer(mob/living/old_body, mob/living/new_body)
. = ..()
- for(var/datum/action/bloodsucker/all_powers as anything in powers)
+ for(var/datum/action/cooldown/bloodsucker/all_powers as anything in powers)
all_powers.Remove(old_body)
all_powers.Grant(new_body)
-
/// Mind version
/datum/mind/proc/make_monsterhunter()
var/datum/antagonist/monsterhunter/monsterhunterdatum = has_antag_datum(/datum/antagonist/monsterhunter)
diff --git a/code/modules/antagonists/monsterhunter/monstertrack.dm b/code/modules/antagonists/monsterhunter/monstertrack.dm
index 29bfe81a973b..e4a3c1ca2ad1 100644
--- a/code/modules/antagonists/monsterhunter/monstertrack.dm
+++ b/code/modules/antagonists/monsterhunter/monstertrack.dm
@@ -1,20 +1,20 @@
/// From 'Cellular Emporium'... somehow?
-/datum/action/bloodsucker/trackvamp
+/datum/action/cooldown/bloodsucker/trackvamp
name = "Track Monster"
desc = "Take a moment to look for clues of any nearby monsters.
These creatures are slippery, and often look like the crew."
+ background_icon = 'icons/mob/actions/actions_bloodsucker.dmi'
button_icon = 'icons/mob/actions/actions_bloodsucker.dmi'
- icon_icon = 'icons/mob/actions/actions_bloodsucker.dmi'
background_icon_state = "vamp_power_off"
button_icon_state = "power_hunter"
power_flags = BP_AM_STATIC_COOLDOWN
check_flags = BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS
purchase_flags = NONE
- cooldown = 30 SECONDS
+ cooldown_time = 30 SECONDS
bloodcost = 0
/// Removed, set to TRUE to re-add, either here to be a default function, or in-game through VV for neat Admin stuff -Willard
var/give_pinpointer = FALSE
-/datum/action/bloodsucker/trackvamp/ActivatePower()
+/datum/action/cooldown/bloodsucker/trackvamp/ActivatePower()
. = ..()
/// Return text indicating direction
to_chat(owner, span_notice("You look around, scanning your environment and discerning signs of any filthy, wretched affronts to the natural order..."))
@@ -28,7 +28,7 @@
display_proximity()
DeactivatePower()
-/datum/action/bloodsucker/trackvamp/proc/display_proximity()
+/datum/action/cooldown/bloodsucker/trackvamp/proc/display_proximity()
/// Pick target
var/turf/my_loc = get_turf(owner)
var/closest_dist = 9999
diff --git a/code/modules/antagonists/ninja/ninja.dm b/code/modules/antagonists/ninja/ninja.dm
index 5cf384e83609..af27273d99ad 100644
--- a/code/modules/antagonists/ninja/ninja.dm
+++ b/code/modules/antagonists/ninja/ninja.dm
@@ -4,6 +4,7 @@ GLOBAL_LIST_EMPTY(ninja_capture)
name = "Ninja"
antagpanel_category = "Ninja"
job_rank = ROLE_NINJA
+ antag_hud_name = "ninja"
show_name_in_check_antagonists = TRUE
show_to_ghosts = TRUE
antag_moodlet = /datum/mood_event/focused
@@ -22,14 +23,12 @@ GLOBAL_LIST_EMPTY(ninja_capture)
for(var/obj/item/implant/explosive/E in M.implants)
if(E)
RegisterSignal(E, COMSIG_IMPLANT_ACTIVATED, PROC_REF(on_death))
- update_ninja_icons_added(M)
/datum/antagonist/ninja/remove_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
for(var/obj/item/implant/explosive/E in M.implants)
if(E)
UnregisterSignal(M, COMSIG_IMPLANT_ACTIVATED)
- update_ninja_icons_removed(M)
/datum/antagonist/ninja/proc/equip_space_ninja(mob/living/carbon/human/H = owner.current)
return H.equipOutfit(/datum/outfit/ninja)
@@ -187,13 +186,3 @@ GLOBAL_LIST_EMPTY(ninja_capture)
new_owner.add_antag_datum(src)
message_admins("[key_name_admin(admin)] has [adj] ninja'ed [key_name_admin(new_owner)].")
log_admin("[key_name(admin)] has [adj] ninja'ed [key_name(new_owner)].")
-
-/datum/antagonist/ninja/proc/update_ninja_icons_added(var/mob/living/carbon/human/ninja)
- var/datum/atom_hud/antag/ninjahud = GLOB.huds[ANTAG_HUD_NINJA]
- ninjahud.join_hud(ninja)
- set_antag_hud(ninja, "ninja")
-
-/datum/antagonist/ninja/proc/update_ninja_icons_removed(var/mob/living/carbon/human/ninja)
- var/datum/atom_hud/antag/ninjahud = GLOB.huds[ANTAG_HUD_NINJA]
- ninjahud.leave_hud(ninja)
- set_antag_hud(ninja, null)
diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm
index 2e2ff0399f35..5ba81ca9faa7 100644
--- a/code/modules/antagonists/nukeop/nukeop.dm
+++ b/code/modules/antagonists/nukeop/nukeop.dm
@@ -3,6 +3,7 @@
roundend_category = "syndicate operatives" //just in case
antagpanel_category = "NukeOp"
job_rank = ROLE_OPERATIVE
+ antag_hud_name = "synd"
antag_moodlet = /datum/mood_event/focused
show_to_ghosts = TRUE
var/datum/team/nuclear/nuke_team
@@ -16,24 +17,11 @@
/// In the preview icon, the nukies who are behind the leader
var/preview_outfit_behind = /datum/outfit/nuclear_operative
-/datum/antagonist/nukeop/proc/update_synd_icons_added(mob/living/M)
- var/datum/atom_hud/antag/opshud = GLOB.huds[ANTAG_HUD_OPS]
- opshud.join_hud(M)
- set_antag_hud(M, "synd")
-
-/datum/antagonist/nukeop/proc/update_synd_icons_removed(mob/living/M)
- var/datum/atom_hud/antag/opshud = GLOB.huds[ANTAG_HUD_OPS]
- opshud.leave_hud(M)
- set_antag_hud(M, null)
-
/datum/antagonist/nukeop/apply_innate_effects(mob/living/mob_override)
- var/mob/living/M = mob_override || owner.current
- update_synd_icons_added(M)
+ add_team_hud(mob_override || owner.current)
ADD_TRAIT(owner, TRAIT_DISK_VERIFIER, NUKEOP_TRAIT)
/datum/antagonist/nukeop/remove_innate_effects(mob/living/mob_override)
- var/mob/living/M = mob_override || owner.current
- update_synd_icons_removed(M)
REMOVE_TRAIT(owner, TRAIT_DISK_VERIFIER, NUKEOP_TRAIT)
/datum/antagonist/nukeop/proc/equip_op()
diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm
index 13cc2250ed53..bcb67a008451 100644
--- a/code/modules/antagonists/revenant/revenant.dm
+++ b/code/modules/antagonists/revenant/revenant.dm
@@ -71,12 +71,27 @@
. = ..()
flags_1 |= RAD_NO_CONTAMINATE_1
ADD_TRAIT(src, TRAIT_SIXTHSENSE, INNATE_TRAIT)
- AddSpell(new /obj/effect/proc_holder/spell/targeted/night_vision/revenant(null))
- AddSpell(new /obj/effect/proc_holder/spell/targeted/telepathy/revenant(null))
- AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/defile(null))
- AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/overload(null))
- AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/blight(null))
- AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction(null))
+
+ // Starting spells
+ var/datum/action/cooldown/spell/night_vision/revenant/vision = new(src)
+ vision.Grant(src)
+
+ var/datum/action/cooldown/spell/list_target/telepathy/revenant/telepathy = new(src)
+ telepathy.Grant(src)
+
+ // Starting spells that start locked
+ var/datum/action/cooldown/spell/aoe/revenant/overload/lights_go_zap = new(src)
+ lights_go_zap.Grant(src)
+
+ var/datum/action/cooldown/spell/aoe/revenant/defile/windows_go_smash = new(src)
+ windows_go_smash.Grant(src)
+
+ var/datum/action/cooldown/spell/aoe/revenant/blight/botany_go_mad = new(src)
+ botany_go_mad.Grant(src)
+
+ var/datum/action/cooldown/spell/aoe/revenant/malfunction/shuttle_go_emag = new(src)
+ shuttle_go_emag.Grant(src)
+
random_revenant_name()
LoadComponent(/datum/component/walk/jaunt) //yogs
@@ -126,7 +141,7 @@
to_chat(src, span_revenboldnotice("You can move again!"))
if(essence_regenerating && !inhibited && essence < essence_regen_cap) //While inhibited, essence will not regenerate
essence = min(essence_regen_cap, essence+essence_regen_amount)
- update_action_buttons_icon() //because we update something required by our spells in life, we need to update our buttons
+ update_mob_action_buttons() //because we update something required by our spells in life, we need to update our buttons
update_spooky_icon()
update_health_hud()
..()
@@ -197,12 +212,12 @@
span_revendanger("As \the [W] passes through you, you feel your essence draining away!"))
adjustBruteLoss(25) //hella effective
inhibited = TRUE
- update_action_buttons_icon()
+ update_mob_action_buttons()
addtimer(CALLBACK(src, PROC_REF(reset_inhibit)), 30)
/mob/living/simple_animal/revenant/proc/reset_inhibit()
inhibited = FALSE
- update_action_buttons_icon()
+ update_mob_action_buttons()
/mob/living/simple_animal/revenant/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
if(!forced && !revealed)
@@ -312,7 +327,7 @@
if(essence_excess < essence_cost)
return FALSE
essence_excess -= essence_cost
- update_action_buttons_icon()
+ update_mob_action_buttons()
return TRUE
/mob/living/simple_animal/revenant/proc/change_essence_amount(essence_amt, silent = FALSE, source = null)
@@ -325,7 +340,7 @@
if(essence_amt > 0)
essence_accumulated = max(0, essence_accumulated+essence_amt)
essence_excess = max(0, essence_excess+essence_amt)
- update_action_buttons_icon()
+ update_mob_action_buttons()
if(!silent)
if(essence_amt > 0)
to_chat(src, span_revennotice("Gained [essence_amt]E[source ? " from [source]":""]."))
diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/antagonists/revenant/revenant_abilities.dm
index 28c52136e6d3..c32ebe515243 100644
--- a/code/modules/antagonists/revenant/revenant_abilities.dm
+++ b/code/modules/antagonists/revenant/revenant_abilities.dm
@@ -98,250 +98,264 @@
essence_drained = 0
//Toggle night vision: lets the revenant toggle its night vision
-/obj/effect/proc_holder/spell/targeted/night_vision/revenant
- charge_max = 0
+/datum/action/cooldown/spell/night_vision/revenant
+ name = "Toggle Darkvision"
panel = "Revenant Abilities"
- message = span_revennotice("You toggle your night vision.")
- action_icon = 'icons/mob/actions/actions_revenant.dmi'
- action_icon_state = "r_nightvision"
- action_background_icon_state = "bg_revenant"
+ background_icon_state = "bg_revenant"
+ button_icon = 'icons/mob/actions/actions_revenant.dmi'
+ button_icon_state = "r_nightvision"
+ toggle_span = "revennotice"
//Transmit: the revemant's only direct way to communicate. Sends a single message silently to a single mob
-/obj/effect/proc_holder/spell/targeted/telepathy/revenant
+/datum/action/cooldown/spell/list_target/telepathy/revenant
name = "Revenant Transmit"
panel = "Revenant Abilities"
- action_icon = 'icons/mob/actions/actions_revenant.dmi'
- action_icon_state = "r_transmit"
- action_background_icon_state = "bg_revenant"
- notice = "revennotice"
- boldnotice = "revenboldnotice"
- holy_check = TRUE
- tinfoil_check = FALSE
-
-/obj/effect/proc_holder/spell/aoe_turf/revenant
- clothes_req = 0
- action_icon = 'icons/mob/actions/actions_revenant.dmi'
- action_background_icon_state = "bg_revenant"
+ background_icon_state = "bg_revenant"
+ overlay_icon_state = "bg_revenant_border"
+
+ telepathy_span = "revennotice"
+ bold_telepathy_span = "revenboldnotice"
+
+ antimagic_flags = MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE_MIND
+
+/datum/action/cooldown/spell/aoe/revenant
panel = "Revenant Abilities (Locked)"
- name = "Report this to a coder"
- var/reveal = 80 //How long it reveals the revenant in deciseconds
- var/stun = 20 //How long it stuns the revenant in deciseconds
- var/locked = TRUE //If it's locked and needs to be unlocked before use
- var/unlock_amount = 100 //How much essence it costs to unlock
- var/cast_amount = 50 //How much essence it costs to use
-
-/obj/effect/proc_holder/spell/aoe_turf/revenant/Initialize()
+ background_icon_state = "bg_revenant"
+ overlay_icon_state = "bg_revenant_border"
+ button_icon = 'icons/mob/actions/actions_revenant.dmi'
+
+ antimagic_flags = MAGIC_RESISTANCE_HOLY
+ spell_requirements = NONE
+
+ /// If it's locked, and needs to be unlocked before use
+ var/locked = TRUE
+ /// How much essence it costs to unlock
+ var/unlock_amount = 100
+ /// How much essence it costs to use
+ var/cast_amount = 50
+
+ /// How long it reveals the revenant
+ var/reveal_duration = 8 SECONDS
+ // How long it stuns the revenant
+ var/stun_duration = 2 SECONDS
+
+/datum/action/cooldown/spell/aoe/revenant/New(Target)
. = ..()
+ if(!isrevenant(target))
+ stack_trace("[type] was given to a non-revenant mob, please don't.")
+ qdel(src)
+ return
+
if(locked)
name = "[initial(name)] ([unlock_amount]SE)"
else
name = "[initial(name)] ([cast_amount]E)"
-/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr)
- if(charge_counter < charge_max)
+/datum/action/cooldown/spell/aoe/revenant/can_cast_spell(feedback = TRUE)
+ . = ..()
+ if(!.)
return FALSE
- if(!istype(user)) //Badmins, no. Badmins, don't do it.
- return TRUE
- if(user.inhibited)
+ if(!isrevenant(owner))
+ stack_trace("[type] was owned by a non-revenant mob, please don't.")
return FALSE
- if(locked)
- if(user.essence_excess <= unlock_amount)
- return FALSE
- if(user.essence <= cast_amount)
+
+ var/mob/living/simple_animal/revenant/ghost = owner
+ if(ghost.inhibited)
+ return FALSE
+ if(locked && ghost.essence_excess <= unlock_amount)
+ return FALSE
+ if(ghost.essence <= cast_amount)
return FALSE
+
return TRUE
-/obj/effect/proc_holder/spell/aoe_turf/revenant/proc/attempt_cast(mob/living/simple_animal/revenant/user = usr)
- if(!istype(user)) //If you're not a revenant, it works. Please, please, please don't give this to a non-revenant.
- name = "[initial(name)]"
- if(locked)
- panel = "Revenant Abilities"
- locked = FALSE
- return TRUE
+/datum/action/cooldown/spell/aoe/revenant/get_things_to_cast_on(atom/center)
+ var/list/things = list()
+ for(var/turf/nearby_turf in range(aoe_radius, center))
+ things += nearby_turf
+
+ return things
+
+/datum/action/cooldown/spell/aoe/revenant/before_cast(mob/living/simple_animal/revenant/cast_on)
+ . = ..()
+ if(. & SPELL_CANCEL_CAST)
+ return FALSE
+
if(locked)
- if (!user.unlock(unlock_amount))
- charge_counter = charge_max
- return FALSE
+ if(!cast_on.unlock(unlock_amount))
+ to_chat(cast_on, span_revenwarning("You don't have enough essence to unlock [initial(name)]!"))
+ reset_spell_cooldown()
+ return . | SPELL_CANCEL_CAST
+
name = "[initial(name)] ([cast_amount]E)"
- to_chat(user, span_revennotice("You have unlocked [initial(name)]!"))
+ to_chat(owner, span_revennotice("You have unlocked [initial(name)]!"))
+ to_chat(cast_on, span_revennotice("You have unlocked [initial(name)]!"))
panel = "Revenant Abilities"
locked = FALSE
- charge_counter = charge_max
- return FALSE
- if(!user.castcheck(-cast_amount))
- charge_counter = charge_max
- return FALSE
- name = "[initial(name)] ([cast_amount]E)"
- user.reveal(reveal)
- user.stun(stun)
- if(action)
- action.UpdateButtonIcon()
- return TRUE
+ reset_spell_cooldown()
+ return . | SPELL_CANCEL_CAST
+
+ if(!cast_on.castcheck(-cast_amount))
+ reset_spell_cooldown()
+ return . | SPELL_CANCEL_CAST
+
+/datum/action/cooldown/spell/aoe/revenant/after_cast(mob/living/simple_animal/revenant/cast_on)
+ . = ..()
+ if(reveal_duration)
+ cast_on.reveal(reveal_duration)
+ if(stun_duration)
+ cast_on.stun(stun_duration)
//Overload Light: Breaks a light that's online and sends out lightning bolts to all nearby people.
-/obj/effect/proc_holder/spell/aoe_turf/revenant/overload
+/datum/action/cooldown/spell/aoe/revenant/overload
name = "Overload Lights"
desc = "Directs a large amount of essence into nearby electrical lights, causing lights to shock those nearby."
- charge_max = 200
- range = 5
- stun = 30
+ button_icon_state = "overload_lights"
+ cooldown_time = 20 SECONDS
+
+ aoe_radius = 5
unlock_amount = 25
cast_amount = 40
+ unlock_amount = 25
+ cast_amount = 40
+ stun_duration = 3 SECONDS
+
+ /// The range the shocks from the lights go
var/shock_range = 2
+ /// The damage the shcoskf rom the lgihts do
var/shock_damage = 15
- action_icon_state = "overload_lights"
-
-/obj/effect/proc_holder/spell/aoe_turf/revenant/overload/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
- if(attempt_cast(user))
- for(var/turf/T in targets)
- INVOKE_ASYNC(src, PROC_REF(overload), T, user)
-
-/obj/effect/proc_holder/spell/aoe_turf/revenant/overload/proc/overload(turf/T, mob/user)
- for(var/obj/machinery/light/L in T)
- if(!L.on)
- return
- L.visible_message(span_warning("\The [L] suddenly flares brightly and begins to spark!"))
- var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
- s.set_up(4, 0, L)
- s.start()
- new /obj/effect/temp_visual/revenant(get_turf(L))
- addtimer(CALLBACK(src, PROC_REF(overload_shock), L, user), 20)
-
-/obj/effect/proc_holder/spell/aoe_turf/revenant/overload/proc/overload_shock(obj/machinery/light/L, mob/user)
- if(!L.on) //wait, wait, don't shock me
- return
- flick("[L.base_state]2", L)
- for(var/mob/living/carbon/human/M in view(shock_range, L))
- if(M == user)
+
+/datum/action/cooldown/spell/aoe/revenant/overload/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster)
+ for(var/obj/machinery/light/light in victim)
+ if(!light.on)
continue
- L.Beam(M,icon_state="purple_lightning",time=5)
- if(!M.anti_magic_check(FALSE, TRUE))
- M.electrocute_act(shock_damage, L, safety=TRUE)
- do_sparks(4, FALSE, M)
- playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1)
+ light.visible_message(span_boldwarning("[light] suddenly flares brightly and begins to spark!"))
+ var/datum/effect_system/spark_spread/light_sparks = new /datum/effect_system/spark_spread()
+ light_sparks.set_up(4, 0, light)
+ light_sparks.start()
+ new /obj/effect/temp_visual/revenant(get_turf(light))
+ addtimer(CALLBACK(src, PROC_REF(overload_shock), light, caster), 2 SECONDS)
+
+/datum/action/cooldown/spell/aoe/revenant/overload/proc/overload_shock(obj/machinery/light/to_shock, mob/living/simple_animal/revenant/caster)
+ flick("[to_shock.base_state]2", to_shock)
+ for(var/mob/living/carbon/human/human_mob in view(shock_range, to_shock))
+ if(human_mob == caster)
+ continue
+ to_shock.Beam(human_mob, icon_state = "purple_lightning", time = 0.5 SECONDS)
+ if(!human_mob.can_block_magic(antimagic_flags))
+ human_mob.electrocute_act(shock_damage, to_shock)
+
+ do_sparks(4, FALSE, human_mob)
+ playsound(human_mob, 'sound/machines/defib_zap.ogg', 50, TRUE, -1)
//Defile: Corrupts nearby stuff, unblesses floor tiles.
-/obj/effect/proc_holder/spell/aoe_turf/revenant/defile
+/datum/action/cooldown/spell/aoe/revenant/defile
name = "Defile"
desc = "Twists and corrupts the nearby area as well as dispelling holy auras on floors."
- charge_max = 150
- range = 4
- stun = 20
- reveal = 40
+ button_icon_state = "defile"
+ cooldown_time = 15 SECONDS
+
+ aoe_radius = 4
unlock_amount = 10
cast_amount = 30
- action_icon_state = "defile"
-
-/obj/effect/proc_holder/spell/aoe_turf/revenant/defile/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
- if(attempt_cast(user))
- for(var/turf/T in targets)
- INVOKE_ASYNC(src, PROC_REF(defile), T)
+ reveal_duration = 4 SECONDS
+ stun_duration = 2 SECONDS
-/obj/effect/proc_holder/spell/aoe_turf/revenant/defile/proc/defile(turf/T)
- for(var/obj/effect/blessing/B in T)
- qdel(B)
- new /obj/effect/temp_visual/revenant(T)
+/datum/action/cooldown/spell/aoe/revenant/defile/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster)
+ for(var/obj/effect/blessing/blessing in victim)
+ qdel(blessing)
+ new /obj/effect/temp_visual/revenant(victim)
- if(!isplatingturf(T) && !istype(T, /turf/open/floor/engine/cult) && isfloorturf(T) && prob(15))
- var/turf/open/floor/floor = T
- if(floor.intact && floor.floor_tile)
+ if(!isplatingturf(victim) && !istype(victim, /turf/open/floor/engine/cult) && isfloorturf(victim) && prob(15))
+ var/turf/open/floor/floor = victim
+ if(floor.floor_tile)
new floor.floor_tile(floor)
floor.broken = 0
floor.burnt = 0
- floor.make_plating(1)
- if(T.type == /turf/closed/wall && prob(15))
- new /obj/effect/temp_visual/revenant(T)
- T.ChangeTurf(/turf/closed/wall/rust)
- if(T.type == /turf/closed/wall/r_wall && prob(10))
- new /obj/effect/temp_visual/revenant(T)
- T.ChangeTurf(/turf/closed/wall/r_wall/rust)
- for(var/obj/effect/decal/cleanable/food/salt/salt in T)
- new /obj/effect/temp_visual/revenant(T)
+ floor.make_plating(TRUE)
+
+ if(victim.type == /turf/closed/wall && prob(15))
+ new /obj/effect/temp_visual/revenant(victim)
+ victim.ChangeTurf(/turf/closed/wall/rust)
+ if(victim.type == /turf/closed/wall/r_wall && prob(10))
+ new /obj/effect/temp_visual/revenant(victim)
+ victim.ChangeTurf(/turf/closed/wall/r_wall/rust)
+ for(var/obj/effect/decal/cleanable/food/salt/salt in victim)
+ new /obj/effect/temp_visual/revenant(victim)
qdel(salt)
- for(var/obj/structure/closet/closet in T.contents)
+ for(var/obj/structure/closet/closet in victim.contents)
closet.open()
- for(var/obj/structure/bodycontainer/corpseholder in T)
+ for(var/obj/structure/bodycontainer/corpseholder in victim)
if(corpseholder.connected.loc == corpseholder)
corpseholder.open()
- for(var/obj/machinery/dna_scannernew/dna in T)
+ for(var/obj/machinery/dna_scannernew/dna in victim)
dna.open_machine()
- for(var/obj/structure/window/window in T)
- if(window && window.fulltile)
+ for(var/obj/structure/window/window in victim)
+ window.take_damage(rand(30, 80))
+ if(window?.fulltile)
new /obj/effect/temp_visual/revenant/cracks(window.loc)
- window.take_damage(rand(30,80))
- for(var/obj/machinery/light/light in T)
+ for(var/obj/machinery/light/light in victim)
light.flicker(20) //spooky
//Malfunction: Makes bad stuff happen to robots and machines.
-/obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction
+/datum/action/cooldown/spell/aoe/revenant/malfunction
name = "Malfunction"
desc = "Corrupts and damages nearby machines and mechanical objects."
- charge_max = 200
- range = 4
+ button_icon_state = "malfunction"
+ cooldown_time = 20 SECONDS
+
+ aoe_radius = 4
cast_amount = 60
unlock_amount = 125
- action_icon_state = "malfunction"
//A note to future coders: do not replace this with an EMP because it will wreck malf AIs and everyone will hate you.
-/obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
- if(attempt_cast(user))
- for(var/turf/T in targets)
- INVOKE_ASYNC(src, PROC_REF(malfunction), T, user)
-
-/obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction/proc/malfunction(turf/T, mob/user)
- for(var/mob/living/simple_animal/bot/bot in T)
+/datum/action/cooldown/spell/aoe/revenant/malfunction/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster)
+ for(var/mob/living/simple_animal/bot/bot in victim)
if(!bot.emagged)
new /obj/effect/temp_visual/revenant(bot.loc)
bot.locked = FALSE
bot.open = TRUE
- bot.emag_act()
- for(var/mob/living/carbon/human/human in T)
- if(human == user)
+ bot.emag_act(caster)
+ for(var/mob/living/carbon/human/human in victim)
+ if(human == caster)
continue
- if(human.anti_magic_check(FALSE, TRUE))
+ if(human.can_block_magic(antimagic_flags))
continue
to_chat(human, span_revenwarning("You feel [pick("your sense of direction flicker out", "a stabbing pain in your head", "your mind fill with static")]."))
new /obj/effect/temp_visual/revenant(human.loc)
human.emp_act(EMP_HEAVY)
- for(var/obj/thing in T)
- if(istype(thing,/obj/machinery/airalarm) || istype(thing, /obj/machinery/power/apc) || istype(thing, /obj/machinery/power/smes) || istype(thing, /obj/machinery/particle_accelerator/control_box)) //Doesn't work on SMES and APCs or the PA control box, to prevent kekkery. Yogstation change: Air alarms included.
+ for(var/obj/thing in victim)
+ //Doesn't work on SMES and APCs, to prevent kekkery.
+ if(istype(thing,/obj/machinery/airalarm) || istype(thing, /obj/machinery/power/apc) || istype(thing, /obj/machinery/power/smes || istype(thing, /obj/machinery/particle_accelerator/control_box)))
continue
if(prob(20))
if(prob(50))
new /obj/effect/temp_visual/revenant(thing.loc)
- thing.emag_act(null)
- else
- if(!istype(thing, /obj/machinery/clonepod)) //I hate everything but mostly the fact there's no better way to do this without just not affecting it at all
- thing.emp_act(EMP_HEAVY)
- for(var/mob/living/silicon/robot/S in T) //Only works on cyborgs, not AI
- playsound(S, 'sound/machines/warning-buzzer.ogg', 50, 1)
- new /obj/effect/temp_visual/revenant(S.loc)
- S.spark_system.start()
- S.emp_act(EMP_HEAVY)
- for(var/obj/machinery/door/firedoor/window/W in T) // Fuck you atmos-locks
- W.take_damage(rand(5,20))
- to_chat(user,span_notice("The shutter mechanism starts to grind its gears."))
+ thing.emag_act(caster)
+ // Only works on cyborgs, not AI!
+ for(var/mob/living/silicon/robot/cyborg in victim)
+ playsound(cyborg, 'sound/machines/warning-buzzer.ogg', 50, TRUE)
+ new /obj/effect/temp_visual/revenant(cyborg.loc)
+ cyborg.spark_system.start()
+ cyborg.emp_act(EMP_HEAVY)
//Blight: Infects nearby humans and in general messes living stuff up.
-/obj/effect/proc_holder/spell/aoe_turf/revenant/blight
+/datum/action/cooldown/spell/aoe/revenant/blight
name = "Blight"
desc = "Causes nearby living things to waste away."
- charge_max = 200
- range = 3
+ button_icon_state = "blight"
+ cooldown_time = 20 SECONDS
+
+ aoe_radius = 3
cast_amount = 50
unlock_amount = 75
- action_icon_state = "blight"
-
-/obj/effect/proc_holder/spell/aoe_turf/revenant/blight/cast(list/targets, mob/living/simple_animal/revenant/user = usr)
- if(attempt_cast(user))
- for(var/turf/T in targets)
- INVOKE_ASYNC(src, PROC_REF(blight), T, user)
-/obj/effect/proc_holder/spell/aoe_turf/revenant/blight/proc/blight(turf/T, mob/user)
- for(var/mob/living/mob in T)
- if(mob == user)
+/datum/action/cooldown/spell/aoe/revenant/blight/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster)
+ for(var/mob/living/mob in victim)
+ if(mob == caster)
continue
- if(mob.anti_magic_check(FALSE, TRUE))
+ if(mob.can_block_magic(antimagic_flags))
+ to_chat(caster, span_warning("The spell had no effect on [mob]!"))
continue
new /obj/effect/temp_visual/revenant(mob.loc)
if(iscarbon(mob))
@@ -362,15 +376,15 @@
mob.reagents.add_reagent(/datum/reagent/toxin/plasma, 5)
else
mob.adjustToxLoss(5)
- for(var/obj/structure/spacevine/vine in T) //Fucking with botanists, the ability.
+ for(var/obj/structure/spacevine/vine in victim) //Fucking with botanists, the ability.
vine.add_atom_colour("#823abb", TEMPORARY_COLOUR_PRIORITY)
new /obj/effect/temp_visual/revenant(vine.loc)
- QDEL_IN(vine, 10)
- for(var/obj/structure/glowshroom/shroom in T)
+ QDEL_IN(vine, 1 SECONDS)
+ for(var/obj/structure/glowshroom/shroom in victim)
shroom.add_atom_colour("#823abb", TEMPORARY_COLOUR_PRIORITY)
new /obj/effect/temp_visual/revenant(shroom.loc)
- QDEL_IN(shroom, 10)
- for(var/obj/machinery/hydroponics/tray in T)
+ QDEL_IN(shroom, 1 SECONDS)
+ for(var/obj/machinery/hydroponics/tray in victim)
new /obj/effect/temp_visual/revenant(tray.loc)
tray.pestlevel = rand(8, 10)
tray.weedlevel = rand(8, 10)
diff --git a/code/modules/antagonists/revenant/revenant_blight.dm b/code/modules/antagonists/revenant/revenant_blight.dm
index 2c0958b8ea3d..2823c380588e 100644
--- a/code/modules/antagonists/revenant/revenant_blight.dm
+++ b/code/modules/antagonists/revenant/revenant_blight.dm
@@ -31,7 +31,7 @@
return
if(prob(stage*3))
to_chat(affected_mob, span_revennotice("You suddenly feel [pick("sick and tired", "disoriented", "tired and confused", "nauseated", "faint", "dizzy")]..."))
- affected_mob.confused += 8
+ affected_mob.adjust_confusion(8 SECONDS)
affected_mob.adjustStaminaLoss(20)
new /obj/effect/temp_visual/revenant(affected_mob.loc)
if(stagedamage < stage)
diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm
index b6dc05757fac..077a4d539684 100644
--- a/code/modules/antagonists/revolution/revolution.dm
+++ b/code/modules/antagonists/revolution/revolution.dm
@@ -7,7 +7,7 @@
antagpanel_category = "Revolution"
job_rank = ROLE_REV
antag_moodlet = /datum/mood_event/revolution
- var/hud_type = "rev"
+ antag_hud_name = "rev"
var/datum/team/revolution/rev_team
/datum/antagonist/rev/can_be_owned(datum/mind/new_owner)
@@ -33,12 +33,11 @@
/datum/antagonist/rev/apply_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
M.grant_language(/datum/language/french, TRUE, TRUE, LANGUAGE_REVOLUTIONARY)
- update_rev_icons_added(M)
+ add_team_hud(M, /datum/antagonist/rev)
/datum/antagonist/rev/remove_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
M.remove_language(/datum/language/french, TRUE, TRUE, LANGUAGE_REVOLUTIONARY)
- update_rev_icons_removed(M)
/datum/antagonist/rev/proc/equip_rev()
return
@@ -159,7 +158,7 @@
/datum/antagonist/rev/head
name = "Head Revolutionary"
- hud_type = "rev_head"
+ antag_hud_name = "rev_head"
var/remove_clumsy = FALSE
var/give_flash = TRUE
var/give_hud = TRUE
@@ -183,7 +182,7 @@
// Otherwise, the R gets cut off.
final_icon.Scale(64, 64)
- var/icon/rev_head_icon = icon('icons/mob/hud.dmi', "rev_head")
+ var/icon/rev_head_icon = icon('yogstation/icons/mob/antag_hud.dmi', "rev_head")
rev_head_icon.Scale(48, 48)
rev_head_icon.Crop(1, 1, 64, 64)
rev_head_icon.Shift(EAST, 10)
@@ -209,16 +208,6 @@
W.on_reading_finished(owner.current)
qdel(W)
-/datum/antagonist/rev/proc/update_rev_icons_added(mob/living/M)
- var/datum/atom_hud/antag/revhud = GLOB.huds[ANTAG_HUD_REV]
- revhud.join_hud(M)
- set_antag_hud(M,hud_type)
-
-/datum/antagonist/rev/proc/update_rev_icons_removed(mob/living/M)
- var/datum/atom_hud/antag/revhud = GLOB.huds[ANTAG_HUD_REV]
- revhud.leave_hud(M)
- set_antag_hud(M, null)
-
/datum/antagonist/rev/proc/can_be_converted(mob/living/candidate)
if(!candidate.mind)
return FALSE
@@ -281,9 +270,7 @@
if(!ishuman(H) && !ismonkey(H))
return
- if(remove_clumsy && owner.assigned_role == "Clown")
- to_chat(owner, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
- H.dna.remove_mutation(CLOWNMUT)
+ handle_clown_mutation(owner.current, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
if(give_flash)
var/obj/item/organ/cyberimp/arm/flash/rev/T = new
diff --git a/code/modules/antagonists/santa/santa.dm b/code/modules/antagonists/santa/santa.dm
index 673c9a974373..b49873a6db76 100644
--- a/code/modules/antagonists/santa/santa.dm
+++ b/code/modules/antagonists/santa/santa.dm
@@ -22,7 +22,8 @@
H.equipOutfit(/datum/outfit/santa)
H.dna.update_dna_identity()
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport/santa)
+ var/datum/action/cooldown/spell/teleport/area_teleport/wizard/santa/teleport = new(owner)
+ teleport.Grant(H)
/datum/antagonist/santa/proc/give_objective()
var/datum/objective/santa_objective = new()
diff --git a/code/modules/antagonists/slaughter/slaughter_antag.dm b/code/modules/antagonists/slaughter/slaughter_antag.dm
index c52e17c7a4e0..c8debbc01dd6 100644
--- a/code/modules/antagonists/slaughter/slaughter_antag.dm
+++ b/code/modules/antagonists/slaughter/slaughter_antag.dm
@@ -1,11 +1,13 @@
/datum/antagonist/slaughter
name = "Slaughter demon"
show_name_in_check_antagonists = TRUE
- var/objective_verb = "Kill"
- var/datum/mind/summoner
+ ui_name = "AntagInfoDemon"
job_rank = ROLE_ALIEN
show_in_antagpanel = FALSE
show_to_ghosts = TRUE
+ var/fluff = "You're a Demon of Wrath, often dragged into reality by wizards to terrorize their enemies."
+ var/objective_verb = "Kill"
+ var/datum/mind/summoner
/datum/antagonist/slaughter/on_gain()
forge_objectives()
@@ -14,7 +16,7 @@
/datum/antagonist/slaughter/greet()
. = ..()
owner.announce_objectives()
- to_chat(owner, span_warning("You have a powerful alt-attack that slams people backwards that you can activate by shift+ctrl+clicking your target!"))
+ to_chat(owner, span_warning("You have a powerful alt-attack that slams people backwards that you can activate by Alt clicking your target!"))
/datum/antagonist/slaughter/proc/forge_objectives()
if(summoner)
@@ -28,6 +30,13 @@
new_objective2.explanation_text = "[objective_verb] everyone[summoner ? " else while you're at it":""]."
objectives += new_objective2
+/datum/antagonist/slaughter/ui_static_data(mob/user)
+ var/list/data = list()
+ data["fluff"] = fluff
+ data["objectives"] = get_objectives()
+ data["explain_attack"] = TRUE
+ return data
+
/datum/antagonist/slaughter/laughter
name = "Laughter demon"
objective_verb = "Hug and Tickle"
diff --git a/code/modules/antagonists/slaughter/slaughterevent.dm b/code/modules/antagonists/slaughter/slaughterevent.dm
index 2881eacbe151..1266cb04f7f6 100644
--- a/code/modules/antagonists/slaughter/slaughterevent.dm
+++ b/code/modules/antagonists/slaughter/slaughterevent.dm
@@ -15,13 +15,14 @@
/datum/round_event/ghost_role/slaughter/spawn_role()
var/list/candidates = get_candidates(ROLE_ALIEN, null, ROLE_ALIEN)
+
if(!candidates.len)
return NOT_ENOUGH_PLAYERS
var/mob/dead/selected = pick_n_take(candidates)
var/datum/mind/player_mind = new /datum/mind(selected.key)
- player_mind.active = 1
+ player_mind.active = TRUE
var/list/spawn_locs = list()
for(var/obj/effect/landmark/carpspawn/L in GLOB.landmarks_list)
@@ -32,19 +33,18 @@
message_admins("No valid spawn locations found, aborting...")
return MAP_ERROR
- /*var/obj/effect/dummy/phased_mob/holder = new /obj/effect/dummy/phased_mob((pick(spawn_locs))) //yogs start - Bloodcrawl refactor
- var/mob/living/simple_animal/slaughter/S = new (holder)
- S.holder = holder*/
- var/obj/effect/dummy/crawling/holder = new(pick(spawn_locs))
- var/mob/living/simple_animal/slaughter/S = new(holder) //yogs end
+ var/turf/chosen = pick(spawn_locs)
+ var/mob/living/simple_animal/slaughter/S = new(chosen)
+ new /obj/effect/dummy/phased_mob(chosen, S)
+
player_mind.transfer_to(S)
player_mind.assigned_role = "Slaughter Demon"
player_mind.special_role = "Slaughter Demon"
player_mind.add_antag_datum(/datum/antagonist/slaughter)
- to_chat(S, S.playstyle_string)
- to_chat(S, "You are currently not currently in the same plane of existence as the station. Blood Crawl near a blood pool to manifest.")
+ to_chat(S, span_bold("You are currently not currently in the same plane of existence as the station. \
+ Use your Blood Crawl ability near a pool of blood to manifest and wreak havoc."))
SEND_SOUND(S, 'sound/magic/demon_dies.ogg')
message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a slaughter demon by an event.")
- log_game("[key_name(S)] was spawned as a slaughter demon by an event.")
+ S.log_message("was spawned as a slaughter demon by an event.", LOG_GAME)
spawned_mobs += S
return SUCCESSFUL_SPAWN
diff --git a/code/modules/antagonists/survivalist/survivalist.dm b/code/modules/antagonists/survivalist/survivalist.dm
new file mode 100644
index 000000000000..d1a2afed2665
--- /dev/null
+++ b/code/modules/antagonists/survivalist/survivalist.dm
@@ -0,0 +1,43 @@
+/datum/antagonist/survivalist
+ name = "\improper Survivalist"
+ show_in_antagpanel = FALSE
+ show_name_in_check_antagonists = TRUE
+ var/greet_message = ""
+
+/datum/antagonist/survivalist/proc/forge_objectives()
+ var/datum/objective/survive/survive = new
+ survive.owner = owner
+ objectives += survive
+
+/datum/antagonist/survivalist/on_gain()
+ owner.special_role = "survivalist"
+ forge_objectives()
+ . = ..()
+
+/datum/antagonist/survivalist/greet()
+ . = ..()
+ to_chat(owner, "[greet_message]")
+ owner.announce_objectives()
+
+/datum/antagonist/survivalist/guns
+ greet_message = "Your own safety matters above all else, and the only way to ensure your safety is to stockpile weapons! Grab as many guns as possible, by any means necessary. Kill anyone who gets in your way."
+
+/datum/antagonist/survivalist/guns/forge_objectives()
+ var/datum/objective/steal_n_of_type/summon_guns/guns = new
+ guns.owner = owner
+ objectives += guns
+ return ..()
+
+/datum/antagonist/survivalist/magic
+ name = "Amateur Magician"
+ greet_message = "Grow your newfound talent! Grab as many magical artefacts as possible, by any means necessary. Kill anyone who gets in your way."
+
+/datum/antagonist/survivalist/magic/greet()
+ . = ..()
+ to_chat(owner, span_notice("As a wonderful magician, you should remember that spellbooks don't mean anything if they are used up."))
+
+/datum/antagonist/survivalist/magic/forge_objectives()
+ var/datum/objective/steal_n_of_type/summon_magic/magic = new
+ magic.owner = owner
+ objectives += magic
+ return ..()
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 53cc90912ce9..a162bf8079fa 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -6,6 +6,7 @@
roundend_category = "traitors"
antagpanel_category = "Traitor"
job_rank = ROLE_TRAITOR
+ antag_hud_name = "traitor"
antag_moodlet = /datum/mood_event/focused
preview_outfit = /datum/outfit/traitor
var/special_role = ROLE_TRAITOR
@@ -42,19 +43,18 @@
..()
-/datum/antagonist/traitor/apply_innate_effects()
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/traitor_mob = owner.current
- if(traitor_mob && istype(traitor_mob))
- if(!silent)
- to_chat(traitor_mob, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
- traitor_mob.dna.remove_mutation(CLOWNMUT)
+/datum/antagonist/traitor/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/silicon/ai/A = mob_override || owner.current
+ if(istype(A) && traitor_kind == TRAITOR_AI)
+ A.hack_software = TRUE
+ handle_clown_mutation(owner.current, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
-/datum/antagonist/traitor/remove_innate_effects()
- if(owner.assigned_role == "Clown")
- var/mob/living/carbon/human/traitor_mob = owner.current
- if(traitor_mob && istype(traitor_mob))
- traitor_mob.dna.add_mutation(CLOWNMUT)
+/datum/antagonist/traitor/remove_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/silicon/ai/A = mob_override || owner.current
+ if(istype(A) && traitor_kind == TRAITOR_AI)
+ A.hack_software = FALSE
/datum/antagonist/traitor/on_removal()
//Remove malf powers.
@@ -248,16 +248,6 @@
give_codewords()
to_chat(owner.current, span_notice("Your employer [initial(company.name)] will be paying you an extra [initial(company.paymodifier)]x your nanotrasen paycheck."))
-/datum/antagonist/traitor/proc/update_traitor_icons_added(datum/mind/traitor_mind)
- var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR]
- traitorhud.join_hud(owner.current)
- set_antag_hud(owner.current, "traitor")
-
-/datum/antagonist/traitor/proc/update_traitor_icons_removed(datum/mind/traitor_mind)
- var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR]
- traitorhud.leave_hud(owner.current)
- set_antag_hud(owner.current, null)
-
/datum/antagonist/traitor/proc/finalize_traitor()
switch(traitor_kind)
if(TRAITOR_AI)
@@ -269,20 +259,6 @@
equip(silent)
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE)
-/datum/antagonist/traitor/apply_innate_effects(mob/living/mob_override)
- . = ..()
- update_traitor_icons_added()
- var/mob/living/silicon/ai/A = mob_override || owner.current
- if(istype(A) && traitor_kind == TRAITOR_AI)
- A.hack_software = TRUE
-
-/datum/antagonist/traitor/remove_innate_effects(mob/living/mob_override)
- . = ..()
- update_traitor_icons_removed()
- var/mob/living/silicon/ai/A = mob_override || owner.current
- if(istype(A) && traitor_kind == TRAITOR_AI)
- A.hack_software = FALSE
-
/datum/antagonist/traitor/proc/give_codewords()
if(!owner.current)
return
diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
index 16560d1fdc93..8d1dd9363c25 100644
--- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
+++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
@@ -24,7 +24,8 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
name = "AI Action"
desc = "You aren't entirely sure what this does, but it's very beepy and boopy."
background_icon_state = "bg_tech_blue"
- icon_icon = 'icons/mob/actions/actions_AI.dmi'
+ overlay_icon_state = "bg_tech_blue_border"
+ button_icon = 'icons/mob/actions/actions_AI.dmi'
/// The owner AI, so we don't have to typecast every time
var/mob/living/silicon/ai/owner_AI
/// If we have multiple uses of the same power
@@ -50,7 +51,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
else
owner_AI = owner
-/datum/action/innate/ai/IsAvailable()
+/datum/action/innate/ai/IsAvailable(feedback = FALSE)
. = ..()
if(owner_AI && owner_AI.malf_cooldown > world.time)
return
@@ -79,19 +80,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
/datum/action/innate/ai/ranged
name = "Ranged AI Action"
auto_use_uses = FALSE //This is so we can do the thing and disable/enable freely without having to constantly add uses
- /// The linked proc holder that contains the actual ability code
- var/obj/effect/proc_holder/ranged_ai/linked_ability
- /// The path of our linked ability
- var/linked_ability_type
-
-/datum/action/innate/ai/ranged/New()
- if(!linked_ability_type)
- WARNING("Ranged AI action [name] attempted to spawn without a linked ability!")
- qdel(src) //uh oh!
- return
- linked_ability = new linked_ability_type()
- linked_ability.attached_action = src
- ..()
+ click_action = TRUE
/datum/action/innate/ai/ranged/adjust_uses(amt, silent)
uses += amt
@@ -102,33 +91,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
to_chat(owner, span_warning("[name] has run out of uses!"))
if(delete_on_empty)
Remove(owner)
- QDEL_IN(src, 100) //let any active timers on us finish up
-
-/datum/action/innate/ai/ranged/Destroy()
- QDEL_NULL(linked_ability)
- return ..()
-
-/datum/action/innate/ai/ranged/Activate()
- linked_ability.toggle(owner)
- return TRUE
-
-/// The actual ranged proc holder.
-/obj/effect/proc_holder/ranged_ai
- /// Appears when the user activates the ability
- var/enable_text = span_notice("Hello World!")
- /// Appears when the user deactivates the ability
- var/disable_text = span_danger("Goodbye Cruel World!")
- var/datum/action/innate/ai/ranged/attached_action
-
-/obj/effect/proc_holder/ranged_ai/Destroy()
- attached_action = null
- return ..()
-
-/obj/effect/proc_holder/ranged_ai/proc/toggle(mob/user)
- if(active)
- remove_ranged_ability(disable_text)
- else
- add_ranged_ability(user, enable_text)
+ QDEL_IN(src, 10 SECONDS) //let any active timers on us finish up
/// The base module type, which holds info about each ability.
@@ -415,45 +378,44 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
desc = "Animates a targeted machine, causing it to attack anyone nearby."
button_icon_state = "override_machine"
uses = 4
- linked_ability_type = /obj/effect/proc_holder/ranged_ai/override_machine
+ ranged_mousepointer = 'icons/effects/mouse_pointers/override_machine_target.dmi'
+ enable_text = span_notice("You tap into the station's powernet. Click on a machine to animate it, or use the ability again to cancel.")
+ disable_text = span_notice("You release your hold on the powernet.")
/datum/action/innate/ai/ranged/override_machine/New()
- ..()
+ . = ..()
desc = "[desc] It has [uses] use\s remaining."
- button.desc = desc
-/datum/action/innate/ai/ranged/override_machine/proc/animate_machine(obj/machinery/M)
- if(M && !QDELETED(M))
- new/mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(M), M, owner, 1)
+/datum/action/innate/ai/ranged/override_machine/do_ability(mob/living/caller, atom/clicked_on)
+ if(caller.incapacitated())
+ unset_ranged_ability(caller)
+ return FALSE
+ if(!istype(clicked_on, /obj/machinery))
+ to_chat(caller, span_warning("You can only animate machines!"))
+ return FALSE
+ var/obj/machinery/clicked_machine = clicked_on
+ if(!clicked_machine.can_be_overridden() || is_type_in_typecache(clicked_machine, GLOB.blacklisted_malf_machines))
+ to_chat(caller, span_warning("That machine can't be overridden!"))
+ return FALSE
-/obj/effect/proc_holder/ranged_ai/override_machine
- active = FALSE
- ranged_mousepointer = 'icons/effects/override_machine_target.dmi'
- enable_text = span_notice("You tap into the station's powernet. Click on a machine to animate it, or use the ability again to cancel.")
- disable_text = span_notice("You release your hold on the powernet.")
+ caller.playsound_local(caller, 'sound/misc/interference.ogg', 50, FALSE)
+ adjust_uses(-1)
-/obj/effect/proc_holder/ranged_ai/override_machine/InterceptClickOn(mob/living/caller, params, obj/machinery/target)
- if(..())
- return
- if(ranged_ability_user.incapacitated())
- remove_ranged_ability()
- return
- if(!istype(target))
- to_chat(ranged_ability_user, span_warning("You can only animate machines!"))
- return
- if(!target.can_be_overridden() || is_type_in_typecache(target, GLOB.blacklisted_malf_machines))
- to_chat(ranged_ability_user, span_warning("That machine can't be overridden!"))
- return
- ranged_ability_user.playsound_local(ranged_ability_user, 'sound/misc/interference.ogg', 50, 0)
- attached_action.adjust_uses(-1)
- if(attached_action && attached_action.uses)
- attached_action.desc = "[initial(attached_action.desc)] It has [attached_action.uses] use\s remaining."
- attached_action.UpdateButtonIcon()
- target.audible_message(span_userdanger("You hear a loud electrical buzzing sound coming from [target]!"))
- addtimer(CALLBACK(attached_action, TYPE_PROC_REF(/datum/action/innate/ai/ranged/override_machine, animate_machine), target), 50) //kabeep!
- remove_ranged_ability(span_danger("Sending override signal..."))
+ if(uses)
+ desc = "[initial(desc)] It has [uses] use\s remaining."
+ build_all_button_icons()
+
+ clicked_machine.audible_message(span_userdanger("You hear a loud electrical buzzing sound coming from [clicked_machine]!"))
+ addtimer(CALLBACK(src, PROC_REF(animate_machine), caller, clicked_machine), 5 SECONDS) //kabeep!
+ unset_ranged_ability(caller, span_danger("Sending override signal..."))
return TRUE
+/datum/action/innate/ai/ranged/override_machine/proc/animate_machine(mob/living/caller, obj/machinery/to_animate)
+ if(QDELETED(to_animate))
+ return
+
+ new /mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(to_animate), to_animate, caller, TRUE)
+
/// Destroy RCDs: Detonates all non-cyborg RCDs on the station.
/datum/AI_Module/destructive/destroy_rcd
name = "Destroy RCDs"
@@ -493,43 +455,46 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
desc = "Overheats a machine, causing a small explosion after a short time."
button_icon_state = "overload_machine"
uses = 2
- linked_ability_type = /obj/effect/proc_holder/ranged_ai/overload_machine
-
-/datum/action/innate/ai/ranged/overload_machine/proc/detonate_machine(obj/machinery/M)
- if(M && !QDELETED(M))
- var/turf/T = get_turf(M)
- message_admins("[ADMIN_LOOKUPFLW(usr)] overloaded [M.name] at [ADMIN_VERBOSEJMP(T)].")
- log_game("[key_name(usr)] overloaded [M.name] at [AREACOORD(T)].")
- explosion(get_turf(M), 0, 2, 3, 0)
- if(M) //to check if the explosion killed it before we try to delete it
- qdel(M)
-
-/obj/effect/proc_holder/ranged_ai/overload_machine
- active = FALSE
- ranged_mousepointer = 'icons/effects/overload_machine_target.dmi'
+ ranged_mousepointer = 'icons/effects/mouse_pointers/overload_machine_target.dmi'
enable_text = span_notice("You tap into the station's powernet. Click on a machine to detonate it, or use the ability again to cancel.")
disable_text = span_notice("You release your hold on the powernet.")
-/obj/effect/proc_holder/ranged_ai/overload_machine/InterceptClickOn(mob/living/caller, params, obj/machinery/target)
- if(..())
- return
- if(ranged_ability_user.incapacitated())
- remove_ranged_ability()
- return
- if(!istype(target))
- to_chat(ranged_ability_user, span_warning("You can only overload machines!"))
- return
- if(is_type_in_typecache(target, GLOB.blacklisted_malf_machines))
- to_chat(ranged_ability_user, span_warning("You cannot overload that device!"))
+/datum/action/innate/ai/ranged/overload_machine/New()
+ ..()
+ desc = "[desc] It has [uses] use\s remaining."
+
+/datum/action/innate/ai/ranged/overload_machine/proc/detonate_machine(mob/living/caller, obj/machinery/to_explode)
+ if(QDELETED(to_explode))
return
- ranged_ability_user.playsound_local(ranged_ability_user, "sparks", 4 SECONDS, 0)
- attached_action.adjust_uses(-1)
- target.audible_message(span_userdanger("You hear a loud electrical buzzing sound coming from [target]!"))
- var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
- spark_system.attach(target)
- spark_system.set_up(1, 0, target)
- addtimer(CALLBACK(attached_action, TYPE_PROC_REF(/datum/action/innate/ai/ranged/overload_machine, detonate_machine), target), 4 SECONDS) //kaboom!
- remove_ranged_ability(span_danger("Overcharging machine..."))
+
+ var/turf/machine_turf = get_turf(to_explode)
+ message_admins("[ADMIN_LOOKUPFLW(caller)] overloaded [to_explode.name] ([to_explode.type]) at [ADMIN_VERBOSEJMP(machine_turf)].")
+ log_game("[key_name(caller)] overloaded [to_explode.name] ([to_explode.type]) at [AREACOORD(machine_turf)].")
+ explosion(to_explode, heavy_impact_range = 2, light_impact_range = 3)
+ if(!QDELETED(to_explode)) //to check if the explosion killed it before we try to delete it
+ qdel(to_explode)
+
+/datum/action/innate/ai/ranged/overload_machine/do_ability(mob/living/caller, atom/clicked_on)
+ if(caller.incapacitated())
+ unset_ranged_ability(caller)
+ return FALSE
+ if(!ismachinery(clicked_on))
+ to_chat(caller, span_warning("You can only overload machines!"))
+ return FALSE
+ var/obj/machinery/clicked_machine = clicked_on
+ if(is_type_in_typecache(clicked_machine, GLOB.blacklisted_malf_machines))
+ to_chat(caller, span_warning("You cannot overload that device!"))
+ return FALSE
+
+ caller.playsound_local(caller, "sparks", 50, 0)
+ adjust_uses(-1)
+ if(uses)
+ desc = "[initial(desc)] It has [uses] use\s remaining."
+ build_all_button_icons()
+
+ clicked_machine.audible_message(span_userdanger("You hear a loud electrical buzzing sound coming from [clicked_machine]!"))
+ addtimer(CALLBACK(src, PROC_REF(detonate_machine), caller, clicked_machine), 5 SECONDS) //kaboom!
+ unset_ranged_ability(caller, span_danger("Overcharging machine..."))
return TRUE
/// Blackout: Overloads a random number of lights across the station. Three uses.
@@ -551,7 +516,6 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
/datum/action/innate/ai/blackout/New()
..()
desc = "[desc] It has [uses] use\s remaining."
- button.desc = desc
/datum/action/innate/ai/blackout/Activate()
for(var/obj/machinery/power/apc/apc in GLOB.apcs_list)
@@ -562,9 +526,10 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
to_chat(owner, span_notice("Overcurrent applied to the powernet."))
owner.playsound_local(owner, "sparks", 50, 0)
adjust_uses(-1)
- if(src && uses) //Not sure if not having src here would cause a runtime, so it's here to be safe
- desc = "[initial(desc)] It has [uses] use\s remaining."
- UpdateButtonIcon()
+ if(QDELETED(src) || uses) //Not sure if not having src here would cause a runtime, so it's here to be safe
+ return
+ desc = "[initial(desc)] It has [uses] use\s remaining."
+ build_all_button_icons()
/// Robotic Factory: Places a large machine that converts humans that go through it into cyborgs. Unlocking this ability removes shunting.
/datum/AI_Module/utility/place_cyborg_transformer
@@ -739,7 +704,6 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
/datum/action/innate/ai/reactivate_cameras/New()
..()
desc = "[desc] There are 30 reactivations remaining."
- button.desc = desc
/datum/action/innate/ai/reactivate_cameras/Activate()
var/fixed_cameras = 0
@@ -755,8 +719,10 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
to_chat(owner, span_notice("Diagnostic complete! Cameras reactivated: [fixed_cameras]. Reactivations remaining: [uses]."))
owner.playsound_local(owner, 'sound/items/wirecutter.ogg', 50, 0)
adjust_uses(0, TRUE) //Checks the uses remaining
- if(src && uses) //Not sure if not having src here would cause a runtime, so it's here to be safe
- desc = "[initial(desc)] There are [uses] reactivations remaining."
+ if(QDELETED(src) || !uses) //Not sure if not having src here would cause a runtime, so it's here to be safe
+ return
+ desc = "[initial(desc)] It has [uses] use\s remaining."
+ build_all_button_icons()
/// Upgrade Camera Network: EMP-proofs all cameras, in addition to giving them X-ray vision.
diff --git a/code/modules/antagonists/traitor/equipment/module_picker.dm b/code/modules/antagonists/traitor/equipment/module_picker.dm
index acea65cde881..c90f3406add0 100644
--- a/code/modules/antagonists/traitor/equipment/module_picker.dm
+++ b/code/modules/antagonists/traitor/equipment/module_picker.dm
@@ -126,5 +126,5 @@
else //Adding uses to an existing module
action.uses += initial(action.uses)
action.desc = "[initial(action.desc)] It has [action.uses] use\s remaining."
- action.UpdateButtonIcon()
+ action.build_all_button_icons()
processing_time -= AM.cost
diff --git a/code/modules/antagonists/traitor/syndicate_contract.dm b/code/modules/antagonists/traitor/syndicate_contract.dm
index bdc334e8e9d3..e8c6242cb0d5 100644
--- a/code/modules/antagonists/traitor/syndicate_contract.dm
+++ b/code/modules/antagonists/traitor/syndicate_contract.dm
@@ -162,16 +162,16 @@
M.reagents.add_reagent(/datum/reagent/medicine/omnizine, 20)
M.flash_act()
- M.confused += 10
+ M.adjust_confusion(10 SECONDS)
M.blur_eyes(0.5 SECONDS)
to_chat(M, span_warning("You feel strange..."))
sleep(6 SECONDS)
to_chat(M, span_warning("That pod did something to you..."))
- M.Dizzy(3.5 SECONDS)
+ M.adjust_dizzy(3.5 SECONDS)
sleep(6.5 SECONDS)
to_chat(M, span_warning("Your head pounds... It feels like it's going to burst out your skull!"))
M.flash_act()
- M.confused += 20
+ M.adjust_confusion(20 SECONDS)
M.blur_eyes(3)
sleep(3 SECONDS)
to_chat(M, span_warning("Your head pounds..."))
@@ -182,8 +182,8 @@
we thank you for providing them. Your value is expended, and you will be ransomed back to your station. We always get paid, \
so it's only a matter of time before we ship you back...\"")
M.blur_eyes(1 SECONDS)
- M.Dizzy(1.5 SECONDS)
- M.confused += 20
+ M.adjust_dizzy(1.5 SECONDS)
+ M.adjust_confusion(20 SECONDS)
// We're returning the victim
/datum/syndicate_contract/proc/returnVictim(var/mob/living/M)
@@ -221,8 +221,8 @@
M.flash_act()
M.blur_eyes(30)
- M.Dizzy(35)
- M.confused += 20
+ M.adjust_dizzy(35 SECONDS)
+ M.adjust_confusion(20 SECONDS)
new /obj/effect/DPtarget(possible_drop_loc[pod_rand_loc], return_pod)
else
diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm
index b42a469e9157..8d793034aa63 100644
--- a/code/modules/antagonists/wizard/equipment/artefact.dm
+++ b/code/modules/antagonists/wizard/equipment/artefact.dm
@@ -356,7 +356,7 @@
GiveHint(target)
if(BODY_ZONE_HEAD)
to_chat(user, span_notice("You smack the doll's head with your hand."))
- target.Dizzy(10)
+ target.adjust_dizzy(10)
to_chat(target, span_warning("You suddenly feel as if your head was hit with a hammer!"))
GiveHint(target,user)
cooldown = world.time + cooldown_time
@@ -388,7 +388,7 @@
/obj/item/voodoo/fire_act(exposed_temperature, exposed_volume)
if(target)
target.adjust_fire_stacks(20)
- target.IgniteMob()
+ target.ignite_mob()
GiveHint(target,1)
return ..()
@@ -401,7 +401,7 @@
//Warp Whistle: Provides uncontrolled long distance teleportation.
-/obj/item/warpwhistle
+/obj/item/warp_whistle
name = "warp whistle"
desc = "One toot on this whistle will send you to a far away land!"
icon = 'icons/obj/wizard.dmi'
@@ -409,18 +409,18 @@
var/on_cooldown = 0 //0: usable, 1: in use, 2: on cooldown
var/mob/living/carbon/last_user
-/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user)
+/obj/item/warp_whistle/proc/interrupted(mob/living/carbon/user)
if(!user || QDELETED(src) || user.notransform)
on_cooldown = FALSE
return TRUE
return FALSE
-/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user)
+/obj/item/warp_whistle/proc/end_effect(mob/living/carbon/user)
user.invisibility = initial(user.invisibility)
user.status_flags &= ~GODMODE
user.update_mobility()
-/obj/item/warpwhistle/attack_self(mob/living/carbon/user)
+/obj/item/warp_whistle/attack_self(mob/living/carbon/user)
if(!istype(user) || on_cooldown)
return
on_cooldown = TRUE
@@ -456,7 +456,7 @@
sleep(4 SECONDS)
on_cooldown = 0
-/obj/item/warpwhistle/Destroy()
+/obj/item/warp_whistle/Destroy()
if(on_cooldown == 1 && last_user) //Flute got dunked somewhere in the teleport
end_effect(last_user)
return ..()
diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm
index a43d1bc97079..391e551cb076 100644
--- a/code/modules/antagonists/wizard/equipment/soulstone.dm
+++ b/code/modules/antagonists/wizard/equipment/soulstone.dm
@@ -61,7 +61,7 @@
/obj/item/soulstone/attack(mob/living/carbon/human/M, mob/living/user)
if(!iscultist(user) && !iswizard(user) && !usability)
- user.Unconscious(100)
+ user.Unconscious(10 SECONDS)
to_chat(user, span_userdanger("Your body is wracked with debilitating pain!"))
return
if(spent)
@@ -69,13 +69,18 @@
return
if(!ishuman(M))//If target is not a human.
return ..()
- if(iscultist(M))
- if(iscultist(user))
- to_chat(user, span_cultlarge("\"Come now, do not capture your bretheren's soul.\""))
- return
+ if(iscultist(M) && iscultist(user))
+ to_chat(user, span_cultlarge("\"Come now, do not capture your bretheren's soul.\""))
+ return
if(purified && iscultist(user))
to_chat(user, span_warning("Holy magic resides within the stone, you cannot use it."))
return
+ /*if(theme == THEME_HOLY && IS_CULTIST(user))
+ hot_potato(user)
+ return*/
+ if(HAS_TRAIT(M, TRAIT_NO_SOUL))
+ to_chat(user, span_warning("This body does not possess a soul to capture."))
+ return
log_combat(user, M, "captured [M.name]'s soul", src)
transfer_soul("VICTIM", M, user)
@@ -131,8 +136,9 @@
var/obj/item/soulstone/SS = O
if(!iscultist(user) && !iswizard(user) && !SS.purified)
to_chat(user, span_danger("An overwhelming feeling of dread comes over you as you attempt to place the soulstone into the shell. It would be wise to be rid of this quickly."))
- user.Dizzy(30)
- return
+ if(isliving(user))
+ var/mob/living/living_user = user
+ living_user.set_dizzy_if_lower(1 MINUTES)
if(SS.purified && iscultist(user))
to_chat(user, span_warning("Holy magic resides within the stone, you cannot use it."))
return
@@ -238,7 +244,6 @@
for(var/datum/mind/B in SSticker.mode.cult)
if(B == A.mind)
SSticker.mode.cult -= A.mind
- SSticker.mode.update_cult_icons_removed(A.mind)
qdel(T)
qdel(src)
else
diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm
deleted file mode 100644
index 14d35ceded49..000000000000
--- a/code/modules/antagonists/wizard/equipment/spellbook.dm
+++ /dev/null
@@ -1,756 +0,0 @@
-/datum/spellbook_entry
- var/name = "Entry Name"
-
- var/spell_type = null
- var/desc = ""
- var/category = "Offensive"
- var/cost = 2
- var/refundable = TRUE
- var/surplus = -1 // -1 for infinite, not used by anything atm
- var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell
- var/buy_word = "Learn"
- var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook
- var/list/no_coexistance_typecache //Used so you can't have specific spells together
-
-/datum/spellbook_entry/New()
- ..()
- no_coexistance_typecache = typecacheof(no_coexistance_typecache)
-
-/datum/spellbook_entry/proc/IsAvailible() // For config prefs / gamemode restrictions - these are round applied
- return TRUE
-
-/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) // Specific circumstances
- if(book.uses= aspell.level_max)
- to_chat(user, span_warning("This spell cannot be improved further."))
- return FALSE
- else
- aspell.name = initial(aspell.name)
- aspell.spell_level++
- aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max)
- if(aspell.charge_max < aspell.charge_counter)
- aspell.charge_counter = aspell.charge_max
- switch(aspell.spell_level)
- if(1)
- to_chat(user, span_notice("You have improved [aspell.name] into Efficient [aspell.name]."))
- aspell.name = "Efficient [aspell.name]"
- if(2)
- to_chat(user, span_notice("You have further improved [aspell.name] into Quickened [aspell.name]."))
- aspell.name = "Quickened [aspell.name]"
- if(3)
- to_chat(user, span_notice("You have further improved [aspell.name] into Free [aspell.name]."))
- aspell.name = "Free [aspell.name]"
- if(4)
- to_chat(user, span_notice("You have further improved [aspell.name] into Instant [aspell.name]."))
- aspell.name = "Instant [aspell.name]"
- if(aspell.spell_level >= aspell.level_max)
- to_chat(user, span_notice("This spell cannot be strengthened any further."))
- SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[aspell.spell_level]"))
- return TRUE
- //No same spell found - just learn it
- SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
- user.mind.AddSpell(S)
- to_chat(user, span_notice("You have learned [S.name]."))
- return TRUE
-
-/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user,obj/item/spellbook/book)
- if(!refundable)
- return FALSE
- if(!S)
- S = new spell_type()
- for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list)
- if(initial(S.name) == initial(aspell.name))
- return TRUE
- return FALSE
-
-/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user,obj/item/spellbook/book) //return point value or -1 for failure
- var/area/wizard_station/A = GLOB.areas_by_type[/area/wizard_station]
- if(!(user in A.contents))
- to_chat(user, span_warning("You can only refund spells at the wizard lair"))
- return -1
- if(!S)
- S = new spell_type()
- var/spell_levels = 0
- for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list)
- if(initial(S.name) == initial(aspell.name))
- spell_levels = aspell.spell_level
- user.mind.spell_list.Remove(aspell)
- qdel(S)
- return cost * (spell_levels+1)
- return -1
-/datum/spellbook_entry/proc/GetInfo()
- if(!S)
- S = new spell_type()
- var/dat =""
- dat += "[initial(S.name)]"
- if(S.charge_type == SPELL_CHARGE_TYPE_RECHARGE)
- dat += " Cooldown:[S.charge_max/10]"
- dat += " Cost:[cost]
"
- dat += "[S.desc][desc]
"
- dat += "[S.clothes_req?"Requires wizard garb.":"Can be cast without wizard garb."]
"
- return dat
-
-/datum/spellbook_entry/fireball
- name = "Fireball"
- spell_type = /obj/effect/proc_holder/spell/aimed/fireball
-
-/datum/spellbook_entry/spell_cards
- name = "Spell Cards"
- spell_type = /obj/effect/proc_holder/spell/aimed/spell_cards
-
-/datum/spellbook_entry/rod_form
- name = "Rod Form"
- spell_type = /obj/effect/proc_holder/spell/targeted/rod_form
-
-/datum/spellbook_entry/magicm
- name = "Magic Missile"
- spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile
- category = "Defensive"
-
-/datum/spellbook_entry/pacify
- name = "Pacify"
- spell_type = /obj/effect/proc_holder/spell/targeted/touch/pacify
-
-/datum/spellbook_entry/disabletech
- name = "Disable Tech"
- spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech
- category = "Defensive"
- cost = 1
-
-/datum/spellbook_entry/repulse
- name = "Repulse"
- spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse
- category = "Defensive"
-
-/datum/spellbook_entry/lightningPacket
- name = "Lightning bolt! Lightning bolt!"
- spell_type = /obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket
- category = "Defensive"
-
-/datum/spellbook_entry/timestop
- name = "Time Stop"
- spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/timestop
- category = "Defensive"
-
-/datum/spellbook_entry/smoke
- name = "Smoke"
- spell_type = /obj/effect/proc_holder/spell/targeted/smoke
- category = "Defensive"
- cost = 1
-
-/datum/spellbook_entry/blind
- name = "Blind"
- spell_type = /obj/effect/proc_holder/spell/pointed/trigger/blind
- cost = 1
-
-/datum/spellbook_entry/forcewall
- name = "Force Wall"
- spell_type = /obj/effect/proc_holder/spell/targeted/forcewall
- category = "Defensive"
- cost = 1
-
-/datum/spellbook_entry/blink
- name = "Blink"
- spell_type = /obj/effect/proc_holder/spell/targeted/turf_teleport/blink
- category = "Mobility"
-
-/datum/spellbook_entry/teleport
- name = "Teleport"
- spell_type = /obj/effect/proc_holder/spell/targeted/area_teleport/teleport
- category = "Mobility"
-
-/datum/spellbook_entry/mutate
- name = "Mutate"
- spell_type = /obj/effect/proc_holder/spell/targeted/genetic/mutate
-
-/datum/spellbook_entry/jaunt
- name = "Ethereal Jaunt"
- spell_type = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt
- category = "Mobility"
-
-/datum/spellbook_entry/knock
- name = "Knock"
- spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock
- category = "Mobility"
- cost = 1
-
-/datum/spellbook_entry/fleshtostone
- name = "Flesh to Stone"
- spell_type = /obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone
-
-/datum/spellbook_entry/touchofdeath //yogs start
- name = "Touch of Death"
- spell_type = /obj/effect/proc_holder/spell/targeted/touch/touch_of_death //yogs end
-
-/datum/spellbook_entry/summonitem
- name = "Summon Item"
- spell_type = /obj/effect/proc_holder/spell/targeted/summonitem
- category = "Assistance"
- cost = 1
-
-/datum/spellbook_entry/lichdom
- name = "Bind Soul"
- spell_type = /obj/effect/proc_holder/spell/targeted/lichdom
- category = "Defensive"
- cost = 4
-
-/datum/spellbook_entry/teslablast
- name = "Tesla Blast"
- spell_type = /obj/effect/proc_holder/spell/targeted/tesla
-
-/datum/spellbook_entry/lightningbolt
- name = "Lightning Bolt"
- spell_type = /obj/effect/proc_holder/spell/aimed/lightningbolt
-
-/datum/spellbook_entry/lightningbolt/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return TRUE on success
- . = ..()
- user.flags_1 |= TESLA_IGNORE_1
-
-/datum/spellbook_entry/infinite_guns
- name = "Lesser Summon Guns"
- spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun
- cost = 3
- no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage
-
-/datum/spellbook_entry/magic_arrows
- name = "Summon Magic Arrows"
- spell_type = /obj/effect/proc_holder/spell/targeted/conjure_item/arrow/magic
- cost = 1
-
-/datum/spellbook_entry/arcane_barrage
- name = "Arcane Barrage"
- spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage
- cost = 3
- no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun
-
-/datum/spellbook_entry/barnyard
- name = "Barnyard Curse"
- spell_type = /obj/effect/proc_holder/spell/targeted/barnyardcurse
-
-/datum/spellbook_entry/charge
- name = "Charge"
- spell_type = /obj/effect/proc_holder/spell/targeted/charge
- category = "Assistance"
- cost = 1
-
-/datum/spellbook_entry/shapeshift
- name = "Wild Shapeshift"
- spell_type = /obj/effect/proc_holder/spell/targeted/shapeshift
- category = "Assistance"
- cost = 1
-
-/datum/spellbook_entry/tap
- name = "Soul Tap"
- spell_type = /obj/effect/proc_holder/spell/self/tap
- category = "Assistance"
- cost = 1
-
-/datum/spellbook_entry/spacetime_dist
- name = "Spacetime Distortion"
- spell_type = /obj/effect/proc_holder/spell/spacetime_dist
- category = "Defensive"
- cost = 1
-
-/datum/spellbook_entry/the_traps
- name = "The Traps!"
- spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps
- category = "Defensive"
- cost = 1
-
-
-/datum/spellbook_entry/item
- name = "Buy Item"
- refundable = FALSE
- buy_word = "Summon"
- var/item_path= null
-
-
-/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
- new item_path(get_turf(user))
- SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
- return TRUE
-
-/datum/spellbook_entry/item/GetInfo()
- var/dat =""
- dat += "[name]"
- dat += " Cost:[cost]
"
- dat += "[desc]
"
- if(surplus>=0)
- dat += "[surplus] left.
"
- return dat
-
-/datum/spellbook_entry/item/staffchange
- name = "Staff of Change"
- desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself."
- item_path = /obj/item/gun/magic/staff/change
-
-/datum/spellbook_entry/item/staffanimation
- name = "Staff of Animation"
- desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines."
- item_path = /obj/item/gun/magic/staff/animate
- category = "Assistance"
-
-/datum/spellbook_entry/item/staffchaos
- name = "Staff of Chaos"
- desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended."
- item_path = /obj/item/gun/magic/staff/chaos
-
-/datum/spellbook_entry/item/spellblade
- name = "Spellblade"
- desc = "A sword capable of firing blasts of energy which rip targets limb from limb."
- item_path = /obj/item/gun/magic/staff/spellblade
-
-/datum/spellbook_entry/item/breakbow
- name = "Break Bow"
- desc = "A bladed bow that can be split into two swords which attack simultaneously as well as return to their thrower. Comes with a quiver of unlimited, powerful arrows."
- item_path = /obj/item/gun/ballistic/bow/break_bow
- cost = 2
-
-/datum/spellbook_entry/item/breakbow/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
- . = ..()
- if(.)
- new /obj/item/storage/belt/quiver/unlimited(get_turf(user)) // Quiver of limitless arrows
-
-/datum/spellbook_entry/item/staffdoor
- name = "Staff of Door Creation"
- desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass."
- item_path = /obj/item/gun/magic/staff/door
- cost = 1
- category = "Mobility"
-
-/datum/spellbook_entry/item/staffhealing
- name = "Staff of Healing"
- desc = "An altruistic staff that can heal the lame and raise the dead."
- item_path = /obj/item/gun/magic/staff/healing
- cost = 1
- category = "Defensive"
-
-/datum/spellbook_entry/item/lockerstaff
- name = "Staff of the Locker"
- desc = "A staff that shoots lockers. It eats anyone it hits on its way, leaving a welded locker with your victims behind."
- item_path = /obj/item/gun/magic/staff/locker
- category = "Defensive"
-
-/datum/spellbook_entry/item/soulstones
- name = "Six Soul Stone Shards and the spell Artificer"
- desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot."
- item_path = /obj/item/storage/belt/soulstone/full
- category = "Assistance"
-
-/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
- . =..()
- if(.)
- user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(null))
- return .
-
-/datum/spellbook_entry/item/necrostone
- name = "A Necromantic Stone"
- desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command."
- item_path = /obj/item/necromantic_stone
- category = "Assistance"
-
-/datum/spellbook_entry/item/wands
- name = "Wand Assortment"
- desc = "A collection of wands that allow for a wide variety of utility. Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt."
- item_path = /obj/item/storage/belt/wands/full
- category = "Defensive"
-
-/datum/spellbook_entry/item/armor
- name = "Mastercrafted Armor Set"
- desc = "An artifact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space. Additionally provides anti-magic protection, though this may interfere with magic you cast on yourself (i.e Mutate or wands)."
- item_path = /obj/item/clothing/suit/space/hardsuit/wizard
- cost = 1
- category = "Defensive"
-
-/datum/spellbook_entry/item/armor/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
- . = ..()
- if(.)
- new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them.
- new /obj/item/clothing/gloves/color/purple(get_turf(user))//To complete the outfit
-
-/datum/spellbook_entry/item/ranger_cloak
- name = "Ranger Cloak"
- desc = "A cape that makes the wearer quickly invisible while standing still, permitting them to dodge ranged attacks. Moving or dodging projectiles reduces the effect."
- item_path = /obj/item/clothing/neck/cloak/ranger
- cost = 2
- category = "Defensive"
-
-/datum/spellbook_entry/item/contract
- name = "Contract of Apprenticeship"
- desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side."
- item_path = /obj/item/antag_spawner/contract
- category = "Assistance"
-
-/datum/spellbook_entry/item/guardian
- name = "Guardian Deck"
- desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \
- It would be wise to avoid buying these with anything capable of causing you to swap bodies with others."
- item_path = /obj/item/guardiancreator/wizard
- category = "Assistance"
-
-/datum/spellbook_entry/item/guardian/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
- . = ..()
-
-/datum/spellbook_entry/item/rune_crate
- name = "Rune Crate"
- desc = "A wizard specialized in runecrafting is offering two chests full of runes! The problem is, he mixed up the contents of them so you won't know what you will get!"
- item_path = /obj/structure/closet/crate/magic
- category = "Offensive"
- cost = 1
- buy_word = "Order"
-
-/datum/spellbook_entry/item/rune_crate/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
- . = ..()
- if(.)
- new /obj/structure/closet/crate/magic(get_turf(user)) //Two crates full of magic goodies
-
-
-
-/datum/spellbook_entry/item/bloodbottle
- name = "Bottle of Blood"
- desc = "A bottle of magically infused blood, the smell of which will attract extradimensional beings when broken. Be careful though, the kinds of creatures summoned by blood magic are indiscriminate in their killing, and you yourself may become a victim."
- item_path = /obj/item/antag_spawner/slaughter_demon
- limit = 3
- category = "Assistance"
-
-/datum/spellbook_entry/item/hugbottle
- name = "Bottle of Tickles"
- desc = "A bottle of magically infused fun, the smell of which will \
- attract adorable extradimensional beings when broken. These beings \
- are similar to slaughter demons, but they do not permanently kill \
- their victims, instead putting them in an extradimensional hugspace, \
- to be released on the demon's death. Chaotic, but not ultimately \
- damaging. The crew's reaction to the other hand could be very \
- destructive."
- item_path = /obj/item/antag_spawner/slaughter_demon/laughter
- cost = 1 //non-destructive; it's just a jape, sibling!
- limit = 3
- category = "Assistance"
-
-/datum/spellbook_entry/item/mjolnir
- name = "Mjolnir"
- desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power."
- item_path = /obj/item/twohanded/mjollnir
- cost = 3
-
-/datum/spellbook_entry/item/singularity_hammer
- name = "Singularity Hammer"
- desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact."
- item_path = /obj/item/twohanded/singularityhammer
-
-/datum/spellbook_entry/item/demon_chainsaw
- name = "Demonic Chainsaw"
- desc = "A demon that has taken the form of a chainsaw! This demon will grant you eternal life, so long as you feed it blood!"
- item_path = /obj/item/twohanded/required/chainsaw/demon
- cost = 3
-/datum/spellbook_entry/item/battlemage
- name = "Battlemage Armour"
- desc = "An ensorceled suit of armour, protected by a powerful shield. The shield can completely negate sixteen attacks before being permanently depleted. It is not protected against the void of space."
- item_path = /obj/item/clothing/suit/wizrobe/armor
- limit = 1
- category = "Defensive"
-
-/datum/spellbook_entry/item/battlemage/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
- . =..()
- if(.)
- new /obj/item/clothing/head/wizard/armor(get_turf(user))//To complete the outfit
-
-
-/datum/spellbook_entry/item/battlemage_charge
- name = "Battlemage Armour Charges"
- desc = "A powerful defensive rune, it will grant eight additional charges to a suit of battlemage armour."
- item_path = /obj/item/wizard_armour_charge
- category = "Defensive"
- cost = 1
-
-/datum/spellbook_entry/item/warpwhistle
- name = "Warp Whistle"
- desc = "A strange whistle that will transport you to a distant safe place on the station. There is a window of vulnerability at the beginning of every use."
- item_path = /obj/item/warpwhistle
- category = "Mobility"
- cost = 1
-
-/datum/spellbook_entry/summon
- name = "Summon Stuff"
- category = "Rituals"
- refundable = FALSE
- buy_word = "Cast"
- var/active = FALSE
-
-/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book)
- return ..() && !active
-
-/datum/spellbook_entry/summon/GetInfo()
- var/dat =""
- dat += "[name]"
- if(cost>0)
- dat += " Cost:[cost]
"
- else
- dat += " No Cost
"
- dat += "[desc]
"
- if(active)
- dat += "Already cast!
"
- return dat
-
-/datum/spellbook_entry/summon/ghosts
- name = "Summon Ghosts"
- desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you."
- cost = 0
-
-/datum/spellbook_entry/summon/ghosts/IsAvailible()
- if(!SSticker.mode)
- return FALSE
- else
- return TRUE
-
-/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
- new /datum/round_event/wizard/ghost()
- active = TRUE
- to_chat(user, span_notice("You have cast summon ghosts!"))
- playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1)
- return TRUE
-
-/datum/spellbook_entry/summon/portals
- name = "Summon Portal Storm"
- desc = "Terrorize the crew with a portal storm! Whatever crawls out of these portals will be a threat not just to the crew, but you as well!"
- cost = 1
-
-/datum/spellbook_entry/summon/portals/IsAvailible()
- if(!SSticker.mode)
- return FALSE
- else
- return TRUE
-
-/datum/spellbook_entry/summon/portals/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
- var/portaltype = pick("port_shocktroop","port_narsiespawn")
- switch(portaltype)
- if("port_shocktroop")
- var/datum/round_event_control/portal_storm_syndicate/newShocktroopControl = new /datum/round_event_control/portal_storm_syndicate()
- var/datum/round_event/portal_storm/syndicate_shocktroop/newShocktroopStorm = newShocktroopControl.runEvent()
- newShocktroopStorm.setup()
- if("port_narsiespawn")
- var/datum/round_event_control/portal_storm_narsie/newNarsieControl = new /datum/round_event_control/portal_storm_narsie()
- var/datum/round_event/portal_storm/portal_storm_narsie/newNarsieStorm = newNarsieControl.runEvent()
- newNarsieStorm.setup()
- active = TRUE
- to_chat(user, span_notice("You have summoned a portal storm!"))
- playsound(get_turf(user), 'sound/magic/lightningbolt.ogg', 50, 1)
- return TRUE
-
-/datum/spellbook_entry/summon/curse_of_madness
- name = "Curse of Madness"
- desc = "Curses the station, warping the minds of everyone inside, causing lasting traumas. Warning: this spell can affect you if not cast from a safe distance."
- cost = 4
-
-/datum/spellbook_entry/summon/curse_of_madness/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
- active = TRUE
- var/message = stripped_input(user, "Whisper a secret truth to drive your victims to madness.", "Whispers of Madness")
- if(!message)
- return FALSE
- curse_of_madness(user, message)
- to_chat(user, span_notice("You have cast the curse of insanity!"))
- playsound(user, 'sound/magic/mandswap.ogg', 50, 1)
- return TRUE
-
-/obj/item/spellbook
- name = "spell book"
- desc = "An unearthly tome that glows with power."
- icon = 'icons/obj/library.dmi'
- icon_state ="book"
- throw_speed = 2
- throw_range = 5
- w_class = WEIGHT_CLASS_TINY
- var/uses = 10
- var/temp = null
- var/tab = null
- var/mob/living/carbon/human/owner
- var/list/datum/spellbook_entry/entries = list()
- var/list/categories = list()
-
-/obj/item/spellbook/examine(mob/user)
- . = ..()
- if(owner)
- . += {"There is a small signature on the front cover: "[owner]"."}
- else
- . += "It appears to have no author."
-
-/obj/item/spellbook/Initialize()
- . = ..()
- prepare_spells()
-
-/obj/item/spellbook/proc/prepare_spells()
- var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon
- for(var/T in entry_types)
- var/datum/spellbook_entry/E = new T
- if(E.IsAvailible())
- entries |= E
- categories |= E.category
- else
- qdel(E)
- tab = categories[1]
-
-/obj/item/spellbook/attackby(obj/item/O, mob/user, params)
- if(istype(O, /obj/item/antag_spawner/contract))
- var/obj/item/antag_spawner/contract/contract = O
- if(contract.used)
- to_chat(user, span_warning("The contract has been used, you can't get your points back now!"))
- else
- to_chat(user, span_notice("You feed the contract back into the spellbook, refunding your points."))
- uses++
- for(var/datum/spellbook_entry/item/contract/CT in entries)
- if(!isnull(CT.limit))
- CT.limit++
- qdel(O)
- else if(istype(O, /obj/item/antag_spawner/slaughter_demon))
- to_chat(user, span_notice("On second thought, maybe summoning a demon is a bad idea. You refund your points."))
- uses++
- for(var/datum/spellbook_entry/item/bloodbottle/BB in entries)
- if(!isnull(BB.limit))
- BB.limit++
- qdel(O)
-
-/obj/item/spellbook/proc/GetCategoryHeader(category)
- var/dat = ""
- switch(category)
- if("Offensive")
- dat += "Spells and items geared towards debilitating and destroying.
"
- dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
"
- dat += "For spells: the number after the spell name is the cooldown time.
"
- dat += "You can reduce this number by spending more points on the spell.
"
- if("Defensive")
- dat += "Spells and items geared towards improving your survivability or reducing foes' ability to attack.
"
- dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
"
- dat += "For spells: the number after the spell name is the cooldown time.
"
- dat += "You can reduce this number by spending more points on the spell.
"
- if("Mobility")
- dat += "Spells and items geared towards improving your ability to move. It is a good idea to take at least one.
"
- dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
"
- dat += "For spells: the number after the spell name is the cooldown time.
"
- dat += "You can reduce this number by spending more points on the spell.
"
- if("Assistance")
- dat += "Spells and items geared towards bringing in outside forces to aid you or improving upon your other items and abilities.
"
- dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
"
- dat += "For spells: the number after the spell name is the cooldown time.
"
- dat += "You can reduce this number by spending more points on the spell.
"
- if("Challenges")
- dat += "The Wizard Federation typically has hard limits on the potency and number of spells brought to the station based on risk.
"
- dat += "Arming the station against you will increases the risk, but will grant you one more charge for your spellbook.
"
- if("Rituals")
- dat += "These powerful spells change the very fabric of reality. Not always in your favour.
"
- return dat
-
-/obj/item/spellbook/proc/wrap(content)
- var/dat = ""
- dat +="Spellbook"
- dat += {"
-
-
-
- "}
- dat += {"[content]